mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-28 09:24:24 +01:00
Merge branch 'spaces' into dev
This commit is contained in:
commit
1092257526
@ -1 +1 @@
|
||||
2.4.10
|
||||
2.5.0-dev
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,5 +1,18 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## next release
|
||||
- TODO export availabilities (taiga#57)
|
||||
- TODO bug: calendar (github#59)
|
||||
- TODO bug: delete event (github#61)
|
||||
- Ability to create, manage and reserve spaces
|
||||
- Improved admin's interface to create availabilities
|
||||
- Complete rewrote of the reservation cart functionality with improved stability, performance and sustainability
|
||||
- Fix a bug: trainings reservations are not shown in the admin's calendar
|
||||
- Fix a bug: unable to delete an administrator from the system
|
||||
- [TODO DEPLOY] `rake db:migrate`, then `rake db:seed`
|
||||
- [TODO DEPLOY] add the `FABLAB_WITHOUT_SPACES` environment variable
|
||||
- [TODO DEPLOY] `rake fablab:es_add_spaces`
|
||||
|
||||
## v2.4.10 2017 January 9
|
||||
|
||||
- Optimized notifications system
|
||||
|
24
README.md
24
README.md
@ -3,7 +3,7 @@
|
||||
FabManager is the FabLab management solution. It is web-based, open-source and totally free.
|
||||
|
||||
|
||||
##### Table of Contents
|
||||
##### Table of Contents
|
||||
1. [Software stack](#software-stack)
|
||||
2. [Contributing](#contributing)
|
||||
3. [Setup a production environment](#setup-a-production-environment)
|
||||
@ -122,7 +122,7 @@ In you only intend to run fab-manager on your local machine for testing purposes
|
||||
```
|
||||
|
||||
8. Build the database. You may have to follow the steps described in [the PostgreSQL configuration chapter](#setup-fabmanager-in-postgresql) before, if you don't already had done it.
|
||||
**Warning**: **NO NOT** run `rake db:setup` instead of these commands, as this will not run some required raw SQL instructions.
|
||||
**Warning**: **NO NOT** run `rake db:setup` instead of these commands, as this will not run some required raw SQL instructions.
|
||||
|
||||
```bash
|
||||
rake db:create
|
||||
@ -201,6 +201,12 @@ The PDF file name will be of the form "(INVOICE_PREFIX) - (invoice ID) _ (invoic
|
||||
FABLAB_WITHOUT_PLANS
|
||||
|
||||
If set to 'true', the subscription plans will be fully disabled and invisible in the application.
|
||||
It is not recommended to disable plans if at least one subscription was took on the platform.
|
||||
|
||||
FABLAB_WITHOUT_SPACES
|
||||
|
||||
If set to 'false', enable the spaces management and reservation in the application.
|
||||
It is not recommended to disable spaces if at least one space reservation was made on the system.
|
||||
|
||||
DEFAULT_MAIL_FROM
|
||||
|
||||
@ -264,7 +270,7 @@ Please consider that allowing file archives (eg. application/zip) or binary exec
|
||||
|
||||
MAX_IMAGE_SIZE
|
||||
|
||||
Maximum size (in bytes) allowed for image uploaded on the platform.
|
||||
Maximum size (in bytes) allowed for image uploaded on the platform.
|
||||
This parameter concerns events, plans, user's avatars, projects and steps of projects.
|
||||
If this parameter is not specified the maximum size allowed will be 2MB.
|
||||
|
||||
@ -680,12 +686,12 @@ Developers may find information on how to implement their own authentication pro
|
||||
- When running the tests suite with `rake test`, all tests may fail with errors similar to the following:
|
||||
|
||||
Error:
|
||||
...
|
||||
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "..." violates foreign key constraint "fk_rails_..."
|
||||
DETAIL: Key (group_id)=(1) is not present in table "groups".
|
||||
: ...
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:11:in `block in transaction'
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:5:in `transaction'
|
||||
...
|
||||
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "..." violates foreign key constraint "fk_rails_..."
|
||||
DETAIL: Key (group_id)=(1) is not present in table "groups".
|
||||
: ...
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:11:in `block in transaction'
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:5:in `transaction'
|
||||
|
||||
This is due to an ActiveRecord behavior witch disable referential integrity in PostgreSQL to load the fixtures.
|
||||
PostgreSQL will prevent any users to disable referential integrity on the fly if they doesn't have the `SUPERUSER` role.
|
||||
|
@ -80,6 +80,8 @@ config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfig
|
||||
|
||||
// Global config: if true, the whole 'Plans & Subscriptions' feature will be disabled in the application
|
||||
$rootScope.fablabWithoutPlans = Fablab.withoutPlans;
|
||||
// Global config: it true, the whole 'Spaces' features will be disabled in the application
|
||||
$rootScope.fablabWithoutSpaces = Fablab.withoutSpaces;
|
||||
|
||||
// Global function to allow the user to navigate to the previous screen (ie. $state).
|
||||
// If no previous $state were recorded, navigate to the home page
|
||||
|
@ -64,8 +64,8 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t("do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION"
|
||||
title: _t('admin_calendar.confirmation_required')
|
||||
msg: _t("admin_calendar.do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION"
|
||||
, { GENDER:getGender($scope.currentUser), USER:slot.user.name, DATE:moment(slot.start_at).format('L'), TIME:moment(slot.start_at).format('LT'), RESERVATION:slot.reservable.name }
|
||||
, 'messageformat')
|
||||
, ->
|
||||
@ -78,9 +78,9 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
resa.canceled_at = data.canceled_at
|
||||
break
|
||||
# notify the admin
|
||||
growl.success(_t('reservation_was_successfully_cancelled'))
|
||||
growl.success(_t('admin_calendar.reservation_was_successfully_cancelled'))
|
||||
, (data, status) -> # failed
|
||||
growl.error(_t('reservation_cancellation_failed'))
|
||||
growl.error(_t('admin_calendar.reservation_cancellation_failed'))
|
||||
|
||||
|
||||
|
||||
@ -91,16 +91,16 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
##
|
||||
$scope.removeMachine = (machine) ->
|
||||
if $scope.availability.machine_ids.length == 1
|
||||
growl.error(_t('unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'))
|
||||
growl.error(_t('admin_calendar.unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'))
|
||||
else
|
||||
# open a confirmation dialog
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_remove_MACHINE_from_this_slot', {GENDER:getGender($scope.currentUser), MACHINE:machine.name}, "messageformat") + ' ' +
|
||||
_t('this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' +
|
||||
_t('beware_this_cannot_be_reverted')
|
||||
title: _t('admin_calendar.confirmation_required')
|
||||
msg: _t('admin_calendar.do_you_really_want_to_remove_MACHINE_from_this_slot', {GENDER:getGender($scope.currentUser), MACHINE:machine.name}, "messageformat") + ' ' +
|
||||
_t('admin_calendar.this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' +
|
||||
_t('admin_calendar.beware_this_cannot_be_reverted')
|
||||
, ->
|
||||
# the admin has confirmed, remove the machine
|
||||
machines = $scope.availability.machine_ids
|
||||
@ -115,9 +115,9 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
$scope.availability.title = data.title
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
# notify the admin
|
||||
growl.success(_t('the_machine_was_successfully_removed_from_the_slot'))
|
||||
growl.success(_t('admin_calendar.the_machine_was_successfully_removed_from_the_slot'))
|
||||
, (data, status) -> # failed
|
||||
growl.error(_t('deletion_failed'))
|
||||
growl.error(_t('admin_calendar.deletion_failed'))
|
||||
|
||||
|
||||
|
||||
@ -150,6 +150,15 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
resolve:
|
||||
start: -> start
|
||||
end: -> end
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
Machine.query().$promise
|
||||
]
|
||||
trainingsPromise: ['Training', (Training)->
|
||||
Training.query().$promise
|
||||
]
|
||||
spacesPromise: ['Space', (Space)->
|
||||
Space.query().$promise
|
||||
]
|
||||
# when the modal is closed, we send the slot to the server for saving
|
||||
modalInstance.result.then (availability) ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'renderEvent',
|
||||
@ -184,9 +193,9 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
Availability.delete id: event.id, ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents', event.id
|
||||
|
||||
growl.success(_t('the_slot_START-END_has_been_successfully_deleted', {START:moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
|
||||
growl.success(_t('admin_calendar.the_slot_START-END_has_been_successfully_deleted', {START:moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
|
||||
,->
|
||||
growl.error(_t('unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
|
||||
growl.error(_t('admin_calendar.unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member', {START:moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
|
||||
# if the user has only clicked on the event, display its reservations
|
||||
else
|
||||
Availability.reservations {id: event.id}, (reservations) ->
|
||||
@ -227,7 +236,8 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
##
|
||||
# Controller used in the slot creation modal window
|
||||
##
|
||||
Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "Machine", "Availability", "Training", 'Tag', 'growl', '_t', ($scope, $uibModalInstance, moment, start, end, Machine, Availability, Training, Tag, growl, _t) ->
|
||||
Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "machinesPromise", "Availability", "trainingsPromise", "spacesPromise", 'Tag', 'growl', '_t'
|
||||
, ($scope, $uibModalInstance, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, Tag, growl, _t) ->
|
||||
|
||||
## $uibModal parameter
|
||||
$scope.start = start
|
||||
@ -236,14 +246,26 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
$scope.end = end
|
||||
|
||||
## machines list
|
||||
$scope.machines = []
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## trainings list
|
||||
$scope.trainings = []
|
||||
$scope.trainings = trainingsPromise
|
||||
|
||||
## spaces list
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
## machines associated with the created slot
|
||||
$scope.selectedMachines = []
|
||||
|
||||
## training associated with the created slot
|
||||
$scope.selectedTraining = null
|
||||
|
||||
## space associated with the created slot
|
||||
$scope.selectedSpace = null
|
||||
|
||||
## UI step
|
||||
$scope.step = 1
|
||||
|
||||
## the user is not able to edit the ending time of the availability, unless he set the type to 'training'
|
||||
$scope.endDateReadOnly = true
|
||||
|
||||
@ -281,14 +303,16 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
# Callback for the modal window validation: save the slot and closes the modal
|
||||
##
|
||||
$scope.ok = ->
|
||||
if $scope.availability.available_type == "machines"
|
||||
if $scope.availability.available_type == 'machines'
|
||||
if $scope.selectedMachines.length > 0
|
||||
$scope.availability.machine_ids = $scope.selectedMachines.map (m) -> m.id
|
||||
else
|
||||
growl.error(_t('you_should_link_a_training_or_a_machine_to_this_slot'))
|
||||
growl.error(_t('admin_calendar.you_should_select_at_least_a_machine'))
|
||||
return
|
||||
else
|
||||
else if $scope.availability.available_type == 'training'
|
||||
$scope.availability.training_ids = [$scope.selectedTraining.id]
|
||||
else if $scope.availability.available_type == 'space'
|
||||
$scope.availability.space_ids = [$scope.selectedSpace.id]
|
||||
Availability.save
|
||||
availability: $scope.availability
|
||||
, (availability) ->
|
||||
@ -296,6 +320,23 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Move the modal UI to the next step
|
||||
##
|
||||
$scope.next = ->
|
||||
$scope.setNbTotalPlaces() if $scope.step == 1
|
||||
$scope.step++
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Move the modal UI to the next step
|
||||
##
|
||||
$scope.previous = ->
|
||||
$scope.step--
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to cancel the slot creation
|
||||
##
|
||||
@ -304,22 +345,14 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switches the slot type : machine availability or training availability
|
||||
##
|
||||
$scope.changeAvailableType = ->
|
||||
if $scope.availability.available_type == "machines"
|
||||
$scope.availability.available_type = "training"
|
||||
else
|
||||
$scope.availability.available_type = "machines"
|
||||
|
||||
|
||||
|
||||
##
|
||||
# For training avaiabilities, set the maximum number of people allowed to register on this slot
|
||||
##
|
||||
$scope.setNbTotalPlaces = ->
|
||||
$scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places
|
||||
if $scope.availability.available_type == 'training'
|
||||
$scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places
|
||||
else if $scope.availability.available_type == 'space'
|
||||
$scope.availability.nb_total_places = $scope.selectedSpace.default_places
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
@ -328,18 +361,11 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
Machine.query().$promise.then (data)->
|
||||
$scope.machines = data.map (d) ->
|
||||
id: d.id
|
||||
name: d.name
|
||||
Training.query().$promise.then (data)->
|
||||
$scope.trainings = data.map (d) ->
|
||||
id: d.id
|
||||
name: d.name
|
||||
nb_total_places: d.nb_total_places
|
||||
if $scope.trainings.length > 0
|
||||
$scope.selectedTraining = $scope.trainings[0]
|
||||
$scope.setNbTotalPlaces()
|
||||
if $scope.trainings.length > 0
|
||||
$scope.selectedTraining = $scope.trainings[0]
|
||||
if $scope.spaces.length > 0
|
||||
$scope.selectedSpace = $scope.spaces[0]
|
||||
|
||||
Tag.query().$promise.then (data) ->
|
||||
$scope.tags = data
|
||||
|
||||
@ -347,7 +373,7 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
## time must be dividable by 60 minutes (base slot duration). For training availabilities, the user
|
||||
## can configure any duration as it does not matters.
|
||||
$scope.$watch 'availability.available_type', (newValue, oldValue, scope) ->
|
||||
if newValue == 'machines'
|
||||
if newValue == 'machines' or newValue == 'space'
|
||||
$scope.endDateReadOnly = true
|
||||
diff = moment($scope.end).diff($scope.start, 'hours') # the result is rounded down by moment.js
|
||||
$scope.end = moment($scope.start).add(diff, 'hours').toDate()
|
||||
@ -358,8 +384,8 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
## When the start date is changed, if we are configuring a machine availability,
|
||||
## maintain the relative length of the slot (ie. change the end time accordingly)
|
||||
$scope.$watch 'start', (newValue, oldValue, scope) ->
|
||||
# for machine availabilities, adjust the end time
|
||||
if $scope.availability.available_type == 'machines'
|
||||
# for machine or space availabilities, adjust the end time
|
||||
if $scope.availability.available_type == 'machines' or $scope.availability.available_type == 'space'
|
||||
end = moment($scope.end)
|
||||
end.add(moment(newValue).diff(oldValue), 'milliseconds')
|
||||
$scope.end = end.toDate()
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
class PlanController
|
||||
|
||||
constructor: ($scope, groups, plans, machines, prices, partners, CSRF) ->
|
||||
constructor: ($scope, groups, prices, partners, CSRF) ->
|
||||
# protection against request forgery
|
||||
CSRF.setMetaTags()
|
||||
|
||||
@ -15,12 +15,6 @@ class PlanController
|
||||
## groups list
|
||||
$scope.groups = groups
|
||||
|
||||
## plans list
|
||||
$scope.plans = plans
|
||||
|
||||
## machines list
|
||||
$scope.machines = machines
|
||||
|
||||
## users with role 'partner', notifiables for a partner plan
|
||||
$scope.partners = partners.users
|
||||
|
||||
@ -48,38 +42,11 @@ class PlanController
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve a plan from its numeric identifier
|
||||
# @param id {number} plan ID
|
||||
# @returns {Object} Plan, inherits from $resource
|
||||
##
|
||||
$scope.getPlanFromId = (id) ->
|
||||
for plan in $scope.plans
|
||||
if plan.id == id
|
||||
return plan
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve the name of a machine from its ID
|
||||
# @param machine_id {number} machine identifier
|
||||
# @returns {string} Machine's name
|
||||
##
|
||||
$scope.getMachineName = (machine_id) ->
|
||||
for machine in $scope.machines
|
||||
if machine.id == machine_id
|
||||
return machine.name
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the plan creation form
|
||||
##
|
||||
Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal', 'groups', 'plans', 'machines', 'prices', 'partners', 'CSRF', '$state', 'growl', '_t'
|
||||
, ($scope, $uibModal, groups, plans, machines, prices, partners, CSRF, $state, growl, _t) ->
|
||||
Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal', 'groups', 'prices', 'partners', 'CSRF', '$state', 'growl', '_t'
|
||||
, ($scope, $uibModal, groups, prices, partners, CSRF, $state, growl, _t) ->
|
||||
|
||||
|
||||
|
||||
@ -146,7 +113,7 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
$scope.partner.name = "#{user.first_name} #{user.last_name}"
|
||||
$uibModalInstance.close($scope.partner)
|
||||
, (error)->
|
||||
growl.error(_t('unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name'))
|
||||
growl.error(_t('new_plan.unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name'))
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
@ -164,9 +131,9 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
##
|
||||
$scope.afterSubmit = (content) ->
|
||||
if !content.id? and !content.plan_ids?
|
||||
growl.error(_t('unable_to_create_the_subscription_please_try_again'))
|
||||
growl.error(_t('new_plan.unable_to_create_the_subscription_please_try_again'))
|
||||
else
|
||||
growl.success(_t('successfully_created_subscription(s)_dont_forget_to_redefine_prices'))
|
||||
growl.success(_t('new_plan.successfully_created_subscription(s)_dont_forget_to_redefine_prices'))
|
||||
if content.plan_ids?
|
||||
$state.go('app.admin.pricing')
|
||||
else
|
||||
@ -175,7 +142,7 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
|
||||
|
||||
|
||||
new PlanController($scope, groups, plans, machines, prices, partners, CSRF)
|
||||
new PlanController($scope, groups, prices, partners, CSRF)
|
||||
]
|
||||
|
||||
|
||||
@ -183,13 +150,25 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
##
|
||||
# Controller used in the plan edition form
|
||||
##
|
||||
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', 'Plan'
|
||||
, ($scope, groups, plans, planPromise, machines, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, Plan) ->
|
||||
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'spaces', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', 'Plan'
|
||||
, ($scope, groups, plans, planPromise, machines, spaces, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, Plan) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## List of spaces
|
||||
$scope.spaces = spaces
|
||||
|
||||
## List of plans
|
||||
$scope.plans = plans
|
||||
|
||||
## List of machines
|
||||
$scope.machines = machines
|
||||
|
||||
## List of groups
|
||||
$scope.groups = groups
|
||||
|
||||
## current form is used for edition mode
|
||||
$scope.mode = 'edition'
|
||||
|
||||
@ -231,9 +210,9 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
##
|
||||
$scope.afterSubmit = (content) ->
|
||||
if !content.id? and !content.plan_ids?
|
||||
growl.error(_t('unable_to_save_subscription_changes_please_try_again'))
|
||||
growl.error(_t('edit_plan.unable_to_save_subscription_changes_please_try_again'))
|
||||
else
|
||||
growl.success(_t('subscription_successfully_changed'))
|
||||
growl.success(_t('edit_plan.subscription_successfully_changed'))
|
||||
$state.go('app.admin.pricing')
|
||||
|
||||
|
||||
@ -251,6 +230,30 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve the name of a machine from its ID
|
||||
# @param machine_id {number} machine identifier
|
||||
# @returns {string} Machine's name
|
||||
##
|
||||
$scope.getMachineName = (machine_id) ->
|
||||
for machine in $scope.machines
|
||||
if machine.id == machine_id
|
||||
return machine.name
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve the name of a space from its ID
|
||||
# @param space_id {number} space identifier
|
||||
# @returns {string} Space's name
|
||||
##
|
||||
$scope.getSpaceName = (space_id) ->
|
||||
for space in $scope.spaces
|
||||
if space.id == space_id
|
||||
return space.name
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
@ -258,7 +261,7 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
##
|
||||
initialize = ->
|
||||
# Using the PlansController
|
||||
new PlanController($scope, groups, plans, machines, prices, partners, CSRF)
|
||||
new PlanController($scope, groups, prices, partners, CSRF)
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
@ -3,8 +3,8 @@
|
||||
##
|
||||
# Controller used in the prices edition page
|
||||
##
|
||||
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', '_t'
|
||||
, ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, _t) ->
|
||||
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', '_t'
|
||||
, ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, _t) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
## List of machines prices (not considering any plan)
|
||||
@ -37,6 +37,15 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
## List of coupons
|
||||
$scope.coupons = couponsPromise
|
||||
|
||||
## List of spaces
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
## Associate free space hours with subscriptions
|
||||
$scope.spaceCredits = spacesCreditsPromise
|
||||
|
||||
## List of spaces prices (not considering any plan)
|
||||
$scope.spacesPrices = spacesPricesPromise
|
||||
|
||||
## The plans list ordering. Default: by group
|
||||
$scope.orderPlans = 'group_id'
|
||||
|
||||
@ -56,7 +65,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
if data?
|
||||
TrainingsPricing.update({ id: trainingsPricing.id }, { trainings_pricing: { amount: data } }).$promise
|
||||
else
|
||||
_t('please_specify_a_number')
|
||||
_t('pricing.please_specify_a_number')
|
||||
|
||||
##
|
||||
# Retrieve a plan from its given identifier and returns it
|
||||
@ -89,13 +98,13 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
##
|
||||
$scope.showTrainings = (trainings) ->
|
||||
unless angular.isArray(trainings) and trainings.length > 0
|
||||
return _t('none')
|
||||
return _t('pricing.none')
|
||||
|
||||
selected = []
|
||||
angular.forEach $scope.trainings, (t) ->
|
||||
if trainings.indexOf(t.id) >= 0
|
||||
selected.push t.name
|
||||
return if selected.length then selected.join(' | ') else _t('none')
|
||||
return if selected.length then selected.join(' | ') else _t('pricing.none')
|
||||
|
||||
|
||||
|
||||
@ -110,7 +119,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
training_credit_nb: newdata.training_credits
|
||||
, angular.noop() # do nothing in case of success
|
||||
, (error) ->
|
||||
growl.error(_t('an_error_occurred_while_saving_the_number_of_credits'))
|
||||
growl.error(_t('pricing.an_error_occurred_while_saving_the_number_of_credits'))
|
||||
|
||||
# save the associated trainings
|
||||
angular.forEach $scope.trainingCreditsGroups, (original, key) ->
|
||||
@ -126,9 +135,9 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.trainingCredits.splice($scope.trainingCredits.indexOf(tc), 1)
|
||||
$scope.trainingCreditsGroups[planId].splice($scope.trainingCreditsGroups[planId].indexOf(tc.id), 1)
|
||||
, (error) ->
|
||||
growl.error(_t('an_error_occurred_while_deleting_credit_with_the_TRAINING', {TRAINING:tc.creditable.name}))
|
||||
growl.error(_t('pricing.an_error_occurred_while_deleting_credit_with_the_TRAINING', {TRAINING:tc.creditable.name}))
|
||||
else
|
||||
growl.error(_t('an_error_occurred_unable_to_find_the_credit_to_revoke'))
|
||||
growl.error(_t('pricing.an_error_occurred_unable_to_find_the_credit_to_revoke'))
|
||||
|
||||
# iterate through the new credits to add
|
||||
angular.forEach newdata.training_ids, (newTrainingId) ->
|
||||
@ -143,7 +152,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.trainingCreditsGroups[newTc.plan_id].push(newTc.creditable_id)
|
||||
, (error) -> # failed
|
||||
training = getTrainingFromId(newTrainingId)
|
||||
growl.error(_t('an_error_occurred_while_creating_credit_with_the_TRAINING', {TRAINING: training.name}))
|
||||
growl.error(_t('pricing.an_error_occurred_while_creating_credit_with_the_TRAINING', {TRAINING: training.name}))
|
||||
console.error(error)
|
||||
|
||||
|
||||
@ -177,11 +186,16 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
# @param credit {Object} credit object, inherited from $resource
|
||||
##
|
||||
$scope.showCreditableName = (credit) ->
|
||||
selected = _t('not_set')
|
||||
selected = _t('pricing.not_set')
|
||||
if credit and credit.creditable_id
|
||||
angular.forEach $scope.machines, (m)->
|
||||
if m.id == credit.creditable_id
|
||||
selected = m.name+' ( id. '+m.id+' )'
|
||||
if credit.creditable_type == 'Machine'
|
||||
angular.forEach $scope.machines, (m)->
|
||||
if m.id == credit.creditable_id
|
||||
selected = m.name + ' ( id. ' + m.id + ' )'
|
||||
else if credit.creditable_type == 'Space'
|
||||
angular.forEach $scope.spaces, (s)->
|
||||
if s.id == credit.creditable_id
|
||||
selected = s.name
|
||||
return selected
|
||||
|
||||
|
||||
@ -195,27 +209,27 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.saveMachineCredit = (data, id) ->
|
||||
for mc in $scope.machineCredits
|
||||
if mc.plan_id == data.plan_id and mc.creditable_id == data.creditable_id and (id == null or mc.id != id)
|
||||
growl.error(_t('error_a_credit_linking_this_machine_with_that_subscription_already_exists'))
|
||||
growl.error(_t('pricing.error_a_credit_linking_this_machine_with_that_subscription_already_exists'))
|
||||
unless id
|
||||
$scope.machineCredits.pop()
|
||||
return false
|
||||
|
||||
if id?
|
||||
Credit.update {id: id}, credit: data, ->
|
||||
growl.success(_t('changes_have_been_successfully_saved'))
|
||||
growl.success(_t('pricing.changes_have_been_successfully_saved'))
|
||||
else
|
||||
data.creditable_type = 'Machine'
|
||||
Credit.save
|
||||
credit: data
|
||||
, (resp) ->
|
||||
$scope.machineCredits[$scope.machineCredits.length-1].id = resp.id
|
||||
growl.success(_t('credit_was_successfully_saved'))
|
||||
growl.success(_t('pricing.credit_was_successfully_saved'))
|
||||
|
||||
|
||||
##
|
||||
# Removes the newly inserted but not saved machine credit / Cancel the current machine credit modification
|
||||
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||
# @param index {number} theme index in the $scope.machineCredits array
|
||||
# @param index {number} credit index in the $scope.machineCredits array
|
||||
##
|
||||
$scope.cancelMachineCredit = (rowform, index) ->
|
||||
if $scope.machineCredits[index].id?
|
||||
@ -235,6 +249,70 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create a new empty entry in the $scope.spaceCredits array
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.addSpaceCredit = (e)->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$scope.inserted =
|
||||
creditable_type: 'Space'
|
||||
$scope.spaceCredits.push($scope.inserted)
|
||||
$scope.status.isopen = !$scope.status.isopen
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Validation callback when editing space's credits. Save the changes.
|
||||
# This will prevent the creation of two credits associated with the same space and plan.
|
||||
# @param data {Object} space, associated plan and number of credit hours.
|
||||
# @param [id] {number} credit id for edition, create a new credit object if not provided
|
||||
##
|
||||
$scope.saveSpaceCredit = (data, id) ->
|
||||
for sc in $scope.spaceCredits
|
||||
if sc.plan_id == data.plan_id and sc.creditable_id == data.creditable_id and (id == null or sc.id != id)
|
||||
growl.error(_t('pricing.error_a_credit_linking_this_space_with_that_subscription_already_exists'))
|
||||
unless id
|
||||
$scope.spaceCredits.pop()
|
||||
return false
|
||||
|
||||
if id?
|
||||
Credit.update {id: id}, credit: data, ->
|
||||
growl.success(_t('pricing.changes_have_been_successfully_saved'))
|
||||
else
|
||||
data.creditable_type = 'Space'
|
||||
Credit.save
|
||||
credit: data
|
||||
, (resp) ->
|
||||
$scope.spaceCredits[$scope.spaceCredits.length - 1].id = resp.id
|
||||
growl.success(_t('pricing.credit_was_successfully_saved'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Removes the newly inserted but not saved space credit / Cancel the current space credit modification
|
||||
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||
# @param index {number} credit index in the $scope.spaceCredits array
|
||||
##
|
||||
$scope.cancelSpaceCredit = (rowform, index) ->
|
||||
if $scope.spaceCredits[index].id?
|
||||
rowform.$cancel()
|
||||
else
|
||||
$scope.spaceCredits.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Deletes the space credit at the specified index
|
||||
# @param index {number} space credit index in the $scope.spaceCredits array
|
||||
##
|
||||
$scope.removeSpaceCredit = (index) ->
|
||||
Credit.delete $scope.spaceCredits[index]
|
||||
$scope.spaceCredits.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# If the plan does not have a type, return a default value for display purposes
|
||||
# @param type {string|undefined|null} plan's type (eg. 'partner')
|
||||
@ -242,8 +320,8 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
##
|
||||
$scope.getPlanType = (type) ->
|
||||
if type == 'PartnerPlan'
|
||||
return _t('partner')
|
||||
else return _t('standard')
|
||||
return _t('pricing.partner')
|
||||
else return _t('pricing.standard')
|
||||
|
||||
##
|
||||
# Change the plans ordering criterion to the one provided
|
||||
@ -270,7 +348,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
if data?
|
||||
Price.update({ id: price.id }, { price: { amount: data } }).$promise
|
||||
else
|
||||
_t('please_specify_a_number')
|
||||
_t('pricing.please_specify_a_number')
|
||||
|
||||
##
|
||||
# Delete the specified subcription plan
|
||||
@ -284,17 +362,17 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_subscription_plan')
|
||||
title: _t('pricing.confirmation_required')
|
||||
msg: _t('pricing.do_you_really_want_to_delete_this_subscription_plan')
|
||||
, ->
|
||||
# the admin has confirmed, delete the plan
|
||||
Plan.delete {id: id}, (res) ->
|
||||
growl.success(_t('subscription_plan_was_successfully_deleted'))
|
||||
growl.success(_t('pricing.subscription_plan_was_successfully_deleted'))
|
||||
$scope.plans.splice(findItemIdxById(plans, id), 1)
|
||||
|
||||
, (error) ->
|
||||
console.error('[EditPricingController::deletePlan] Error: '+error.statusText) if error.statusText
|
||||
growl.error(_t('unable_to_delete_the_specified_subscription_an_error_occurred'))
|
||||
growl.error(_t('pricing.unable_to_delete_the_specified_subscription_an_error_occurred'))
|
||||
|
||||
|
||||
|
||||
@ -324,8 +402,8 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_coupon')
|
||||
title: _t('pricing.confirmation_required')
|
||||
msg: _t('pricing.do_you_really_want_to_delete_this_coupon')
|
||||
, ->
|
||||
# the admin has confirmed, delete the coupon
|
||||
Coupon.delete {id: id}, (res) ->
|
||||
@ -335,9 +413,9 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
, (error) ->
|
||||
console.error('[EditPricingController::deleteCoupon] Error: '+error.statusText) if error.statusText
|
||||
if error.status == 422
|
||||
growl.error(_t('unable_to_delete_the_specified_coupon_already_in_use'))
|
||||
growl.error(_t('pricing.unable_to_delete_the_specified_coupon_already_in_use'))
|
||||
else
|
||||
growl.error(_t('unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'))
|
||||
growl.error(_t('pricing.unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'))
|
||||
|
||||
|
||||
|
||||
@ -363,10 +441,10 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
## Callback to validate sending of the coupon
|
||||
$scope.ok = ->
|
||||
Coupon.send {coupon_code: coupon.code, user_id: $scope.ctrl.member.id}, (res) ->
|
||||
growl.success(_t('coupon_successfully_sent_to_USER', {USER: $scope.ctrl.member.name}))
|
||||
growl.success(_t('pricing.coupon_successfully_sent_to_USER', {USER: $scope.ctrl.member.name}))
|
||||
$uibModalInstance.close({user_id: $scope.ctrl.member.id})
|
||||
, (err) ->
|
||||
growl.error(_t('an_error_occurred_unable_to_send_the_coupon'))
|
||||
growl.error(_t('pricing.an_error_occurred_unable_to_send_the_coupon'))
|
||||
|
||||
## Callback to close the modal and cancel the sending process
|
||||
$scope.cancel = ->
|
||||
|
@ -46,6 +46,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
$scope.trainingInformationMessage = { name: 'training_information_message', value: settingsPromise.training_information_message}
|
||||
$scope.subscriptionExplicationsAlert = { name: 'subscription_explications_alert', value: settingsPromise.subscription_explications_alert }
|
||||
$scope.eventExplicationsAlert = {name: 'event_explications_alert', value: settingsPromise.event_explications_alert }
|
||||
$scope.spaceExplicationsAlert = { name: 'space_explications_alert', value: settingsPromise.space_explications_alert }
|
||||
$scope.windowStart = { name: 'booking_window_start', value: settingsPromise.booking_window_start }
|
||||
$scope.windowEnd = { name: 'booking_window_end', value: settingsPromise.booking_window_end }
|
||||
$scope.mainColorSetting = { name: 'main_color', value: settingsPromise.main_color }
|
||||
@ -116,7 +117,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
value = setting.value
|
||||
|
||||
Setting.update { name: setting.name }, { value: value }, (data)->
|
||||
growl.success(_t('customization_of_SETTING_successfully_saved', {SETTING:_t(setting.name)}))
|
||||
growl.success(_t('settings.customization_of_SETTING_successfully_saved', { SETTING:_t('settings.' + setting.name) }))
|
||||
, (error)->
|
||||
console.log(error)
|
||||
|
||||
@ -135,7 +136,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
angular.forEach v, (err)->
|
||||
growl.error(err)
|
||||
else
|
||||
growl.success(_t('file_successfully_updated'))
|
||||
growl.success(_t('settings.file_successfully_updated'))
|
||||
if content.custom_asset.name is 'cgu-file'
|
||||
$scope.cguFile = content.custom_asset
|
||||
$scope.methods.cgu = 'put'
|
||||
|
@ -144,7 +144,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
##
|
||||
# Callback called when the active tab is changed.
|
||||
# recover the current tab and store its value in $scope.selectedIndex
|
||||
# @param tab {Object} elasticsearch statistic structure
|
||||
# @param tab {Object} elasticsearch statistic structure (from statistic_indices table)
|
||||
##
|
||||
$scope.setActiveTab = (tab) ->
|
||||
$scope.selectedIndex = tab
|
||||
@ -160,6 +160,23 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Returns true if the provided tab must be hidden due to some global or local configuration
|
||||
# @param tab {Object} elasticsearch statistic structure (from statistic_indices table)
|
||||
##
|
||||
$scope.hiddenTab = (tab) ->
|
||||
if tab.table
|
||||
if tab.es_type_key == 'subscription' && $rootScope.fablabWithoutPlans
|
||||
true
|
||||
else if tab.es_type_key == 'space' && $rootScope.fablabWithoutSpaces
|
||||
true
|
||||
else
|
||||
false
|
||||
else
|
||||
true
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to validate the filters and send a new request to elastic
|
||||
##
|
||||
|
@ -250,8 +250,9 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
|
||||
|
||||
# shorthands
|
||||
$scope.isAuthenticated = Auth.isAuthenticated;
|
||||
$scope.isAuthorized = AuthService.isAuthorized;
|
||||
$scope.isAuthenticated = Auth.isAuthenticated
|
||||
$scope.isAuthorized = AuthService.isAuthorized
|
||||
$rootScope.login = $scope.login
|
||||
|
||||
|
||||
|
||||
|
@ -4,14 +4,15 @@
|
||||
# Controller used in the public calendar global
|
||||
##
|
||||
|
||||
Application.Controllers.controller "CalendarController", ["$scope", "$state", "$aside", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise',
|
||||
($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise) ->
|
||||
Application.Controllers.controller "CalendarController", ["$scope", "$state", "$aside", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise',
|
||||
($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) ->
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
currentMachineEvent = null
|
||||
machinesPromise.forEach((m) -> m.checked = true)
|
||||
trainingsPromise.forEach((t) -> t.checked = true)
|
||||
spacesPromise.forEach((s) -> s.checked = true)
|
||||
|
||||
## check all formation/machine is select in filter
|
||||
isSelectAll = (type, scope) ->
|
||||
@ -25,6 +26,9 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
## List of machines
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## List of spaces
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
## add availabilities source to event sources
|
||||
$scope.eventSources = []
|
||||
|
||||
@ -34,6 +38,7 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
scope.filter = $scope.filter =
|
||||
trainings: isSelectAll('trainings', scope)
|
||||
machines: isSelectAll('machines', scope)
|
||||
spaces: isSelectAll('spaces', scope)
|
||||
evt: filter.evt
|
||||
dispo: filter.dispo
|
||||
$scope.calendarConfig.events = availabilitySourceUrl()
|
||||
@ -43,6 +48,7 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
$scope.filter =
|
||||
trainings: isSelectAll('trainings', $scope)
|
||||
machines: isSelectAll('machines', $scope)
|
||||
spaces: isSelectAll('spaces', $scope)
|
||||
evt: true
|
||||
dispo: true
|
||||
|
||||
@ -62,15 +68,18 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
$scope.trainings
|
||||
machines: ->
|
||||
$scope.machines
|
||||
spaces: ->
|
||||
$scope.spaces
|
||||
filter: ->
|
||||
$scope.filter
|
||||
toggleFilter: ->
|
||||
$scope.toggleFilter
|
||||
filterAvailabilities: ->
|
||||
$scope.filterAvailabilities
|
||||
controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'filter', 'toggleFilter', 'filterAvailabilities', ($scope, $uibModalInstance, trainings, machines, filter, toggleFilter, filterAvailabilities) ->
|
||||
controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'filter', 'toggleFilter', 'filterAvailabilities', ($scope, $uibModalInstance, trainings, machines, spaces, filter, toggleFilter, filterAvailabilities) ->
|
||||
$scope.trainings = trainings
|
||||
$scope.machines = machines
|
||||
$scope.spaces = spaces
|
||||
$scope.filter = filter
|
||||
|
||||
$scope.toggleFilter = (type, filter) ->
|
||||
@ -94,13 +103,19 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
currentMachineEvent = event
|
||||
calendar.fullCalendar('changeView', 'agendaDay')
|
||||
calendar.fullCalendar('gotoDate', event.start)
|
||||
else if event.available_type == 'space'
|
||||
calendar.fullCalendar('changeView', 'agendaDay')
|
||||
calendar.fullCalendar('gotoDate', event.start)
|
||||
else if event.available_type == 'event'
|
||||
$state.go('app.public.events_show', {id: event.event_id})
|
||||
else if event.available_type == 'training'
|
||||
$state.go('app.public.training_show', {id: event.training_id})
|
||||
else
|
||||
if event.available_type == 'event'
|
||||
$state.go('app.public.events_show', {id: event.event_id})
|
||||
else if event.available_type == 'training'
|
||||
$state.go('app.public.training_show', {id: event.training_id})
|
||||
else
|
||||
if event.machine_id
|
||||
$state.go('app.public.machines_show', {id: event.machine_id})
|
||||
else if event.space_id
|
||||
$state.go('app.public.space_show', {id: event.space_id})
|
||||
|
||||
|
||||
## agendaDay view: disable slotEventOverlap
|
||||
## agendaWeek view: enable slotEventOverlap
|
||||
@ -136,7 +151,8 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
getFilter = ->
|
||||
t = $scope.trainings.filter((t) -> t.checked).map((t) -> t.id)
|
||||
m = $scope.machines.filter((m) -> m.checked).map((m) -> m.id)
|
||||
{t: t, m: m, evt: $scope.filter.evt, dispo: $scope.filter.dispo}
|
||||
s = $scope.spaces.filter((s) -> s.checked).map((s) -> s.id)
|
||||
{t: t, m: m, s: s, evt: $scope.filter.evt, dispo: $scope.filter.dispo}
|
||||
|
||||
availabilitySourceUrl = ->
|
||||
"/api/availabilities/public?#{$.param(getFilter())}"
|
||||
|
@ -93,7 +93,7 @@ _reserveMachine = (machine, e) ->
|
||||
# if the currently logged'in user has completed the training for this machine, or this machine does not require
|
||||
# a prior training, just redirect him to the machine's booking page
|
||||
if machine.current_user_is_training or machine.trainings.length == 0
|
||||
_this.$state.go('app.logged.machines_reserve', {id: machine.id})
|
||||
_this.$state.go('app.logged.machines_reserve', {id: machine.slug})
|
||||
else
|
||||
# otherwise, if a user is authenticated ...
|
||||
if _this.$scope.isAuthenticated()
|
||||
@ -234,7 +234,7 @@ Application.Controllers.controller "EditMachineController", ["$scope", '$state',
|
||||
Application.Controllers.controller "ShowMachineController", ['$scope', '$state', '$uibModal', '$stateParams', '_t', 'Machine', 'growl', 'machinePromise', 'dialogs'
|
||||
, ($scope, $state, $uibModal, $stateParams, _t, Machine, growl, machinePromise, dialogs) ->
|
||||
|
||||
## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list
|
||||
## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list
|
||||
$scope.machine = machinePromise
|
||||
|
||||
##
|
||||
@ -274,20 +274,20 @@ Application.Controllers.controller "ShowMachineController", ['$scope', '$state',
|
||||
# This controller workflow is pretty similar to the trainings reservation controller.
|
||||
##
|
||||
|
||||
Application.Controllers.controller "ReserveMachineController", ["$scope", "$state", '$stateParams', "$uibModal", '_t', "moment", 'Machine', 'Auth', 'dialogs', '$timeout', 'Price', 'Member', 'Availability', 'Slot', 'Setting', 'CustomAsset', 'plansPromise', 'groupsPromise', 'growl', 'machinePromise', 'settingsPromise', 'Wallet', 'helpers', 'uiCalendarConfig', 'CalendarConfig',
|
||||
($scope, $state, $stateParams, $uibModal, _t, moment, Machine, Auth, dialogs, $timeout, Price, Member, Availability, Slot, Setting, CustomAsset, plansPromise, groupsPromise, growl, machinePromise, settingsPromise, Wallet, helpers, uiCalendarConfig, CalendarConfig) ->
|
||||
Application.Controllers.controller "ReserveMachineController", ["$scope", '$stateParams', '_t', "moment", 'Auth', '$timeout', 'Member', 'Availability', 'plansPromise', 'groupsPromise', 'machinePromise', 'settingsPromise', 'uiCalendarConfig', 'CalendarConfig',
|
||||
($scope, $stateParams, _t, moment, Auth, $timeout, Member, Availability, plansPromise, groupsPromise, machinePromise, settingsPromise, uiCalendarConfig, CalendarConfig) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# Slot already booked by the current user
|
||||
# Slot free to be booked
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::MACHINE_COLOR %>'
|
||||
|
||||
# Slot already booked by another user
|
||||
UNAVAILABLE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::MACHINE_IS_RESERVED_BY_USER %>'
|
||||
|
||||
# Slot free to be booked
|
||||
# Slot already booked by the current user
|
||||
BOOKED_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::IS_RESERVED_BY_CURRENT_USER %>'
|
||||
|
||||
|
||||
@ -297,33 +297,31 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
## bind the machine availabilities with full-Calendar events
|
||||
$scope.eventSources = []
|
||||
|
||||
## fullCalendar event. The last selected slot that the user want to book
|
||||
$scope.slotToPlace = null
|
||||
|
||||
## fullCalendar event. An already booked slot that the user want to modify
|
||||
$scope.slotToModify = null
|
||||
|
||||
## indicates the state of the current view : calendar or plans information
|
||||
$scope.plansAreShown = false
|
||||
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## array of fullCalendar events. Slots where the user want to book
|
||||
$scope.eventsReserved = []
|
||||
## the moment when the plan selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.planSelectionTime = null
|
||||
|
||||
## total amount of the bill to pay
|
||||
$scope.amountTotal = 0
|
||||
## mapping of fullCalendar events.
|
||||
$scope.events =
|
||||
reserved: [] # Slots that the user wants to book
|
||||
modifiable: null # Slot that the user wants to change
|
||||
placable: null # Destination slot for the change
|
||||
paid: [] # Slots that were just booked by the user (transaction ok)
|
||||
moved: null # Slots that were just moved by the user (change done) -> {newSlot:* oldSlot: *}
|
||||
|
||||
## total amount of the elements in the cart, without considering any coupon
|
||||
$scope.totalNoCoupon = 0
|
||||
## the moment when the slot selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.selectionTime = null
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
## the last clicked event in the calender
|
||||
$scope.selectedEvent = null
|
||||
|
||||
## is the user allowed to change the date of his booking
|
||||
$scope.enableBookingMove = true
|
||||
## the application global settings
|
||||
$scope.settings = settingsPromise
|
||||
|
||||
## list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = []
|
||||
@ -352,86 +350,87 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
## Global config: message to the end user concerning the subscriptions rules
|
||||
$scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert
|
||||
|
||||
## Gloabl config: message to the end user concerning the machine bookings
|
||||
## Global config: message to the end user concerning the machine bookings
|
||||
$scope.machineExplicationsAlert = settingsPromise.machine_explications_alert
|
||||
|
||||
## Global config: is the user authorized to change his bookings slots?
|
||||
$scope.enableBookingMove = (settingsPromise.booking_move_enable == "true")
|
||||
|
||||
## Global config: delay in hours before a booking while changing the booking slot is forbidden
|
||||
$scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay)
|
||||
|
||||
## Global config: is the user authorized to cancel his bookings?
|
||||
$scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true")
|
||||
|
||||
## Global config: delay in hours before a booking while the cancellation is forbidden
|
||||
$scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay)
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'added to cart'
|
||||
##
|
||||
$scope.markSlotAsAdded = ->
|
||||
$scope.selectedEvent.backgroundColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.selectedEvent.title = _t('i_reserve')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, removing the previously booked slot from the selection
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
# Change the last selected slot's appearence to looks like 'never added to cart'
|
||||
##
|
||||
$scope.removeSlotToModify = (e) ->
|
||||
e.preventDefault()
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
$scope.markSlotAsRemoved = (slot) ->
|
||||
slot.backgroundColor = 'white'
|
||||
slot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
slot.title = ''
|
||||
slot.isValid = false
|
||||
slot.id = null
|
||||
slot.is_reserved = false
|
||||
slot.can_modify = false
|
||||
slot.offered = false
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, cancel the choice of the new slot
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
# Callback when a slot was successfully cancelled. Reset the slot style as 'ready to book'
|
||||
##
|
||||
$scope.removeSlotToPlace = (e)->
|
||||
e.preventDefault()
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
$scope.slotCancelled = ->
|
||||
$scope.markSlotAsRemoved($scope.selectedEvent)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, confirm the modification.
|
||||
# Change the last selected slot's appearence to looks like 'currently looking for a new destination to exchange'
|
||||
##
|
||||
$scope.markSlotAsModifying = ->
|
||||
$scope.selectedEvent.backgroundColor = '#eee'
|
||||
$scope.selectedEvent.title = _t('i_change')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'the slot being exchanged will take this place'
|
||||
##
|
||||
$scope.changeModifyMachineSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.selectedEvent._id
|
||||
$scope.selectedEvent.backgroundColor = '#bbb'
|
||||
$scope.selectedEvent.title = _t('i_shift')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
##
|
||||
$scope.modifyMachineSlot = ->
|
||||
Slot.update {id: $scope.slotToModify.id},
|
||||
slot:
|
||||
start_at: $scope.slotToPlace.start
|
||||
end_at: $scope.slotToPlace.end
|
||||
availability_id: $scope.slotToPlace.availability_id
|
||||
, -> # success
|
||||
$scope.modifiedSlots =
|
||||
newReservedSlot: $scope.slotToPlace
|
||||
oldReservedSlot: $scope.slotToModify
|
||||
$scope.slotToPlace.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.borderColor = $scope.slotToModify.borderColor
|
||||
$scope.slotToPlace.id = $scope.slotToModify.id
|
||||
$scope.slotToPlace.is_reserved = true
|
||||
$scope.slotToPlace.can_modify = true
|
||||
$scope.slotToPlace = null
|
||||
$scope.events.placable.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.borderColor = $scope.events.modifiable.borderColor
|
||||
$scope.events.placable.id = $scope.events.modifiable.id
|
||||
$scope.events.placable.is_reserved = true
|
||||
$scope.events.placable.can_modify = true
|
||||
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify.title = ''
|
||||
$scope.slotToModify.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.slotToModify.id = null
|
||||
$scope.slotToModify.is_reserved = false
|
||||
$scope.slotToModify.can_modify = false
|
||||
$scope.slotToModify = null
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
$scope.events.modifiable.title = ''
|
||||
$scope.events.modifiable.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.events.modifiable.id = null
|
||||
$scope.events.modifiable.is_reserved = false
|
||||
$scope.events.modifiable.can_modify = false
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, (err) -> # failure
|
||||
growl.error(_t('unable_to_change_the_reservation'))
|
||||
console.error(err)
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
@ -439,14 +438,13 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyMachineSlot = ->
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
$scope.events.modifiable.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
@ -455,67 +453,10 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# reservations. (admins only)
|
||||
##
|
||||
$scope.updateMember = ->
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Add the provided slot to the shopping cart (state transition from free to 'about to be reserved')
|
||||
# and increment the total amount of the cart if needed.
|
||||
# @param machineSlot {Object} fullCalendar event object
|
||||
##
|
||||
$scope.validMachineSlot = (machineSlot)->
|
||||
machineSlot.isValid = true
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Remove the provided slot from the shopping cart (state transition from 'about to be reserved' to free)
|
||||
# and decrement the total amount of the cart if needed.
|
||||
# @param machineSlot {Object} fullCalendar event object
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeMachineSlot = (machineSlot, e)->
|
||||
e.preventDefault() if e
|
||||
machineSlot.backgroundColor = 'white'
|
||||
machineSlot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
machineSlot.title = ''
|
||||
machineSlot.isValid = false
|
||||
|
||||
if machineSlot.machine.is_reduced_amount
|
||||
angular.forEach $scope.ctrl.member.machine_credits, (credit)->
|
||||
if credit.machine_id = machineSlot.machine.id
|
||||
credit.hours_used--
|
||||
machineSlot.machine.is_reduced_amount = false
|
||||
|
||||
index = $scope.eventsReserved.indexOf(machineSlot)
|
||||
$scope.eventsReserved.splice(index, 1)
|
||||
if $scope.eventsReserved.length == 0
|
||||
if $scope.plansAreShown
|
||||
$scope.selectedPlan = null
|
||||
$scope.plansAreShown = false
|
||||
updateCartPrice()
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks that every selected slots were added to the shopping cart. Ie. will return false if
|
||||
# any checked slot was not validated by the user.
|
||||
##
|
||||
$scope.machineSlotsValid = ->
|
||||
isValid = true
|
||||
angular.forEach $scope.eventsReserved, (m)->
|
||||
isValid = false if !m.isValid
|
||||
isValid
|
||||
|
||||
|
||||
|
||||
@ -527,30 +468,10 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
e.preventDefault()
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectPlan($scope.selectedPlan)
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Validates the shopping chart and redirect the user to the payment step
|
||||
##
|
||||
$scope.payMachine = ->
|
||||
|
||||
# first, we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
reservation = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan)
|
||||
|
||||
Wallet.getWalletByUser {user_id: $scope.ctrl.member.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount)
|
||||
if $scope.currentUser.role isnt 'admin' and amountToPay > 0
|
||||
payByStripe(reservation)
|
||||
else
|
||||
if $scope.currentUser.role is 'admin' or amountToPay is 0
|
||||
payOnSite(reservation)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.error(_t('please_select_a_member_first'))
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
##
|
||||
@ -564,34 +485,41 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# @param plan {Object} the plan to subscribe
|
||||
##
|
||||
$scope.selectPlan = (plan) ->
|
||||
if $scope.isAuthenticated()
|
||||
angular.forEach $scope.eventsReserved, (machineSlot)->
|
||||
angular.forEach $scope.ctrl.member.machine_credits, (credit)->
|
||||
if credit.machine_id = machineSlot.machine.id
|
||||
credit.hours_used = 0
|
||||
machineSlot.machine.is_reduced_amount = false
|
||||
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
updateCartPrice()
|
||||
# toggle selected plan
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.login null, ->
|
||||
$scope.selectedPlan = plan
|
||||
updateCartPrice()
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks if $scope.slotToModify and $scope.slotToPlace have tag incompatibilities
|
||||
# @returns {boolean} true in case of incompatibility
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
$scope.tagMissmatch = ->
|
||||
for tag in $scope.slotToModify.tags
|
||||
if tag.id not in $scope.slotToPlace.tag_ids
|
||||
return true
|
||||
false
|
||||
$scope.afterPayment = (reservation)->
|
||||
angular.forEach $scope.events.reserved, (machineSlot, key) ->
|
||||
machineSlot.is_reserved = true
|
||||
machineSlot.can_modify = true
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
machineSlot.title = _t('i_ve_reserved')
|
||||
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.currentUser)
|
||||
else
|
||||
machineSlot.title = _t('not_available')
|
||||
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member)
|
||||
machineSlot.backgroundColor = 'white'
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
|
||||
refetchCalendar()
|
||||
|
||||
|
||||
|
||||
@ -609,75 +537,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
|
||||
# watch when a coupon is applied to re-compute the total price
|
||||
$scope.$watch 'coupon.applied', (newValue, oldValue) ->
|
||||
unless newValue == null and oldValue == null
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create an hash map implementing the Reservation specs
|
||||
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
|
||||
# @param slots {Array<Object>} Array of fullCalendar events: slots selected on the calendar
|
||||
# @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation
|
||||
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||
##
|
||||
mkReservation = (member, slots, plan = null) ->
|
||||
reservation =
|
||||
user_id: member.id
|
||||
reservable_id: (slots[0].machine.id if slots.length > 0)
|
||||
reservable_type: 'Machine'
|
||||
slots_attributes: []
|
||||
plan_id: (plan.id if plan)
|
||||
angular.forEach slots, (slot, key) ->
|
||||
reservation.slots_attributes.push
|
||||
start_at: slot.start
|
||||
end_at: slot.end
|
||||
availability_id: slot.availability_id
|
||||
offered: slot.offered || false
|
||||
|
||||
reservation
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
|
||||
# @param reservation {Object} as returned by mkReservation()
|
||||
# @param coupon {Object} Coupon as returned from the API
|
||||
# @return {{reservation:Object, coupon_code:string}}
|
||||
##
|
||||
mkRequestParams = (reservation, coupon) ->
|
||||
params =
|
||||
reservation: reservation
|
||||
coupon_code: (coupon.code if coupon)
|
||||
|
||||
params
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Update the total price of the current selection/reservation
|
||||
##
|
||||
updateCartPrice = ->
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan)
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
$scope.totalNoCoupon = res.price_without_coupon
|
||||
setSlotsDetails(res.details)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.warning(_t('please_select_a_member_first'))
|
||||
$scope.amountTotal = null
|
||||
|
||||
|
||||
setSlotsDetails = (details) ->
|
||||
angular.forEach $scope.eventsReserved, (slot) ->
|
||||
angular.forEach details.slots, (s) ->
|
||||
if moment(s.start_at).isSame(slot.start)
|
||||
slot.promo = s.promo
|
||||
slot.price = s.price
|
||||
|
||||
|
||||
##
|
||||
@ -687,67 +546,8 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# if it's too late).
|
||||
##
|
||||
calendarEventClickCb = (event, jsEvent, view) ->
|
||||
|
||||
if !event.is_reserved && !$scope.slotToModify
|
||||
index = $scope.eventsReserved.indexOf(event)
|
||||
if index == -1
|
||||
event.backgroundColor = FREE_SLOT_BORDER_COLOR
|
||||
event.title = _t('i_reserve')
|
||||
$scope.eventsReserved.push event
|
||||
else
|
||||
$scope.removeMachineSlot(event)
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
else if !event.is_reserved && $scope.slotToModify
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = event
|
||||
event.backgroundColor = '#bbb'
|
||||
event.title = _t('i_shift')
|
||||
else if event.is_reserved and (slotCanBeModified(event) or slotCanBeCanceled(event)) and !$scope.slotToModify and $scope.eventsReserved.length == 0
|
||||
event.movable = slotCanBeModified(event)
|
||||
event.cancelable = slotCanBeCanceled(event)
|
||||
dialogs.confirm
|
||||
templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>'
|
||||
resolve:
|
||||
object: -> event
|
||||
, (type) ->
|
||||
if type == 'move'
|
||||
$scope.modifiedSlots = null
|
||||
$scope.slotToModify = event
|
||||
event.backgroundColor = '#eee'
|
||||
event.title = _t('i_change')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_cancel_this_reservation')
|
||||
, -> # cancel confirmed
|
||||
Slot.cancel {id: event.id}, -> # successfully canceled
|
||||
growl.success _t('reservation_was_cancelled_successfully')
|
||||
$scope.canceledSlot = event
|
||||
$scope.canceledSlot.backgroundColor = 'white'
|
||||
$scope.canceledSlot.title = ''
|
||||
$scope.canceledSlot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.canceledSlot.id = null
|
||||
$scope.canceledSlot.is_reserved = false
|
||||
$scope.canceledSlot.can_modify = false
|
||||
$scope.canceledSlot = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cancellation_failed')
|
||||
, ->
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
updateCartPrice()
|
||||
$scope.selectedEvent = event
|
||||
$scope.selectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
@ -767,201 +567,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
##
|
||||
payByStripe = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
##
|
||||
# Callback to process the payment with Stripe, triggered on button click
|
||||
##
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
growl.error(response.error.message)
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
if response.status == 500
|
||||
$scope.alerts.push
|
||||
msg: response.statusText
|
||||
type: 'danger'
|
||||
else
|
||||
if response.data.card and response.data.card.join('').length > 0
|
||||
$scope.alerts.push
|
||||
msg: response.data.card.join('. ')
|
||||
type: 'danger'
|
||||
else if response.data.payment and response.data.payment.join('').length > 0
|
||||
$scope.alerts.push
|
||||
msg: response.data.payment.join('. ')
|
||||
type: 'danger'
|
||||
$scope.attempting = false
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
##
|
||||
payOnSite = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>'
|
||||
size: 'sm'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) ->
|
||||
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Global price (total of all items)
|
||||
$scope.price = price.price
|
||||
|
||||
# Price to pay (wallet deducted)
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Button label
|
||||
if $scope.amount > 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')($scope.amount)}, "messageformat")
|
||||
else
|
||||
if price.price > 0 and $scope.walletAmount == 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')(price.price)}, "messageformat")
|
||||
else
|
||||
$scope.validButtonName = _t('confirm')
|
||||
|
||||
##
|
||||
# Callback to process the local payment, triggered on button click
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be modified by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeModified = (slot)->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be canceled by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeCanceled = (slot) ->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
##
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
afterPayment = (reservation)->
|
||||
angular.forEach $scope.eventsReserved, (machineSlot, key) ->
|
||||
machineSlot.is_reserved = true
|
||||
machineSlot.can_modify = true
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
machineSlot.title = _t('i_ve_reserved')
|
||||
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.currentUser)
|
||||
else
|
||||
machineSlot.title = _t('not_available')
|
||||
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member)
|
||||
machineSlot.backgroundColor = 'white'
|
||||
$scope.paidMachineSlots = $scope.eventsReserved
|
||||
|
||||
$scope.eventsReserved = []
|
||||
$scope.coupon.applied = null
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# After payment, update the id of the newly reserved slot with the id returned by the server.
|
||||
# This will allow the user to modify the reservation he just booked. The associated user will also be registered
|
||||
@ -979,15 +584,20 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
|
||||
|
||||
##
|
||||
# Search for the requested plan in the provided array and return its price.
|
||||
# @param plansArray {Array} full list of plans
|
||||
# @param planId {Number} plan identifier
|
||||
# @returns {Number|null} price of the given plan or null if not found
|
||||
# Update the calendar's display to render the new attributes of the events
|
||||
##
|
||||
findAmountByPlanId = (plansArray, planId)->
|
||||
for plan in plansArray
|
||||
return plan.amount if plan.plan_id == planId
|
||||
return null
|
||||
updateCalendar = ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Asynchronously fetch the events from the API and refresh the calendar's view with these new events
|
||||
##
|
||||
refetchCalendar = ->
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
|
@ -48,6 +48,13 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
linkIcon: 'credit-card'
|
||||
})
|
||||
|
||||
unless Fablab.withoutSpaces
|
||||
$scope.navLinks.splice(3, 0, {
|
||||
state: 'app.public.spaces_list'
|
||||
linkText: 'reserve_a_space'
|
||||
linkIcon: 'rocket'
|
||||
})
|
||||
|
||||
|
||||
Fablab.adminNavLinks = Fablab.adminNavLinks || []
|
||||
adminNavLinks = [
|
||||
@ -109,4 +116,11 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
].concat(Fablab.adminNavLinks)
|
||||
|
||||
$scope.adminNavLinks = adminNavLinks
|
||||
|
||||
unless Fablab.withoutSpaces
|
||||
$scope.adminNavLinks.splice(7, 0, {
|
||||
state: 'app.public.spaces_list'
|
||||
linkText: 'manage_the_spaces'
|
||||
linkIcon: 'rocket'
|
||||
})
|
||||
]
|
||||
|
518
app/assets/javascripts/controllers/spaces.coffee.erb
Normal file
518
app/assets/javascripts/controllers/spaces.coffee.erb
Normal file
@ -0,0 +1,518 @@
|
||||
|
||||
### COMMON CODE ###
|
||||
|
||||
##
|
||||
# Provides a set of common callback methods to the $scope parameter. These methods are used
|
||||
# in the various spaces' admin controllers.
|
||||
#
|
||||
# Provides :
|
||||
# - $scope.submited(content)
|
||||
# - $scope.cancel()
|
||||
# - $scope.fileinputClass(v)
|
||||
# - $scope.addFile()
|
||||
# - $scope.deleteFile(file)
|
||||
#
|
||||
# Requires :
|
||||
# - $scope.space.space_files_attributes = []
|
||||
# - $state (Ui-Router) [ 'app.public.spaces_list' ]
|
||||
##
|
||||
class SpacesController
|
||||
constructor: ($scope, $state) ->
|
||||
##
|
||||
# For use with ngUpload (https://github.com/twilson63/ngUpload).
|
||||
# Intended to be the callback when the upload is done: any raised error will be stacked in the
|
||||
# $scope.alerts array. If everything goes fine, the user is redirected to the spaces list.
|
||||
# @param content {Object} JSON - The upload's result
|
||||
##
|
||||
$scope.submited = (content) ->
|
||||
if !content.id?
|
||||
$scope.alerts = []
|
||||
angular.forEach content, (v, k)->
|
||||
angular.forEach v, (err)->
|
||||
$scope.alerts.push
|
||||
msg: k+': '+err
|
||||
type: 'danger'
|
||||
else
|
||||
$state.go('app.public.spaces_list')
|
||||
|
||||
##
|
||||
# Changes the current user's view, redirecting him to the spaces list
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.public.spaces_list')
|
||||
|
||||
##
|
||||
# For use with 'ng-class', returns the CSS class name for the uploads previews.
|
||||
# The preview may show a placeholder or the content of the file depending on the upload state.
|
||||
# @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
|
||||
##
|
||||
$scope.fileinputClass = (v)->
|
||||
if v
|
||||
'fileinput-exists'
|
||||
else
|
||||
'fileinput-new'
|
||||
|
||||
##
|
||||
# This will create a single new empty entry into the space attachements list.
|
||||
##
|
||||
$scope.addFile = ->
|
||||
$scope.space.space_files_attributes.push {}
|
||||
|
||||
##
|
||||
# This will remove the given file from the space attachements list. If the file was previously uploaded
|
||||
# to the server, it will be marked for deletion on the server. Otherwise, it will be simply truncated from
|
||||
# the attachements array.
|
||||
# @param file {Object} the file to delete
|
||||
##
|
||||
$scope.deleteFile = (file) ->
|
||||
index = $scope.space.space_files_attributes.indexOf(file)
|
||||
if file.id?
|
||||
file._destroy = true
|
||||
else
|
||||
$scope.space.space_files_attributes.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the public listing page, allowing everyone to see the list of spaces
|
||||
##
|
||||
Application.Controllers.controller 'SpacesController', ['$scope', '$state', 'spacesPromise', ($scope, $state, spacesPromise) ->
|
||||
|
||||
## Retrieve the list of spaces
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
##
|
||||
# Redirect the user to the space details page
|
||||
##
|
||||
$scope.showSpace = (space) ->
|
||||
$state.go('app.public.space_show', { id: space.slug })
|
||||
|
||||
##
|
||||
# Callback to book a reservation for the current space
|
||||
##
|
||||
$scope.reserveSpace = (space) ->
|
||||
$state.go('app.logged.space_reserve', { id: space.slug })
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the space creation page (admin)
|
||||
##
|
||||
Application.Controllers.controller 'NewSpaceController', ['$scope', '$state', 'CSRF',($scope, $state, CSRF) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/spaces/"
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = "post"
|
||||
|
||||
## default space parameters
|
||||
$scope.space =
|
||||
space_files_attributes: []
|
||||
|
||||
## Using the SpacesController
|
||||
new SpacesController($scope, $state)
|
||||
]
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the space edition page (admin)
|
||||
##
|
||||
Application.Controllers.controller 'EditSpaceController', ['$scope', '$state', '$stateParams', 'spacePromise', 'CSRF',($scope, $state, $stateParams, spacePromise, CSRF) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/spaces/" + $stateParams.id
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = "put"
|
||||
|
||||
## space to modify
|
||||
$scope.space = spacePromise
|
||||
|
||||
## Using the SpacesController
|
||||
new SpacesController($scope, $state)
|
||||
]
|
||||
|
||||
Application.Controllers.controller 'ShowSpaceController', ['$scope', '$state', 'spacePromise', '_t', 'dialogs', 'growl', ($scope, $state, spacePromise, _t, dialogs, growl) ->
|
||||
|
||||
## Details of the space witch id/slug is provided in the URL
|
||||
$scope.space = spacePromise
|
||||
|
||||
##
|
||||
# Callback to book a reservation for the current space
|
||||
# @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.reserveSpace = (event) ->
|
||||
event.preventDefault()
|
||||
$state.go('app.logged.space_reserve', { id: $scope.space.slug })
|
||||
|
||||
##
|
||||
# Callback to book a reservation for the current space
|
||||
# @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.deleteSpace = (event) ->
|
||||
event.preventDefault()
|
||||
# check the permissions
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
console.error _t('space_show.unauthorized_operation')
|
||||
else
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('space_show.confirmation_required')
|
||||
msg: _t('space_show.do_you_really_want_to_delete_this_space')
|
||||
, -> # deletion confirmed
|
||||
# delete the machine then redirect to the machines listing
|
||||
$scope.space.$delete ->
|
||||
$state.go('app.public.spaces_list')
|
||||
, (error)->
|
||||
growl.warning(_t('space_show.the_space_cant_be_deleted_because_it_is_already_reserved_by_some_users'))
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the spaces reservation agenda page.
|
||||
# This controller is very similar to the machine reservation controller with one major difference: here, there is many places
|
||||
# per slots.
|
||||
##
|
||||
|
||||
Application.Controllers.controller "ReserveSpaceController", ["$scope", '$stateParams', 'Auth', '$timeout', 'Availability', 'Member', 'availabilitySpacesPromise', 'plansPromise', 'groupsPromise', 'settingsPromise', 'spacePromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $stateParams, Auth, $timeout, Availability, Member, availabilitySpacesPromise, plansPromise, groupsPromise, settingsPromise, spacePromise, _t, uiCalendarConfig, CalendarConfig) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# Color of the selected event backgound
|
||||
SELECTED_EVENT_BG_COLOR = '#ffdd00'
|
||||
|
||||
# Slot free to be booked
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::SPACE_COLOR %>'
|
||||
|
||||
# Slot with reservation from current user
|
||||
RESERVED_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::IS_RESERVED_BY_CURRENT_USER %>'
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## bind the spaces availabilities with full-Calendar events
|
||||
$scope.eventSources = [ { events: availabilitySpacesPromise, textColor: 'black' } ]
|
||||
|
||||
## the user to deal with, ie. the current user for non-admins
|
||||
$scope.ctrl =
|
||||
member: {}
|
||||
|
||||
## list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = []
|
||||
for group in groupsPromise
|
||||
groupObj = { id: group.id, name: group.name, plans: [] }
|
||||
for plan in plansPromise
|
||||
groupObj.plans.push(plan) if plan.group_id == group.id
|
||||
$scope.plansClassifiedByGroup.push(groupObj)
|
||||
|
||||
## mapping of fullCalendar events.
|
||||
$scope.events =
|
||||
reserved: [] # Slots that the user wants to book
|
||||
modifiable: null # Slot that the user wants to change
|
||||
placable: null # Destination slot for the change
|
||||
paid: [] # Slots that were just booked by the user (transaction ok)
|
||||
moved: null # Slots that were just moved by the user (change done) -> {newSlot:* oldSlot: *}
|
||||
|
||||
## the moment when the slot selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.selectionTime = null
|
||||
|
||||
## the last clicked event in the calender
|
||||
$scope.selectedEvent = null
|
||||
|
||||
## indicates the state of the current view : calendar or plans information
|
||||
$scope.plansAreShown = false
|
||||
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## the moment when the plan selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.planSelectionTime = null
|
||||
|
||||
## Selected space
|
||||
$scope.space = spacePromise
|
||||
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig = CalendarConfig
|
||||
minTime: moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss'))
|
||||
maxTime: moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss'))
|
||||
eventClick: (event, jsEvent, view) ->
|
||||
calendarEventClickCb(event, jsEvent, view)
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element, view)
|
||||
|
||||
## Application global settings
|
||||
$scope.settings = settingsPromise
|
||||
|
||||
## Global config: message to the end user concerning the subscriptions rules
|
||||
$scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert
|
||||
|
||||
## Global config: message to the end user concerning the space reservation
|
||||
$scope.spaceExplicationsAlert = settingsPromise.space_explications_alert
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'added to cart'
|
||||
##
|
||||
$scope.markSlotAsAdded = ->
|
||||
$scope.selectedEvent.backgroundColor = SELECTED_EVENT_BG_COLOR
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'never added to cart'
|
||||
##
|
||||
$scope.markSlotAsRemoved = (slot) ->
|
||||
slot.backgroundColor = 'white'
|
||||
slot.title = ''
|
||||
slot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
slot.id = null
|
||||
slot.isValid = false
|
||||
slot.is_reserved = false
|
||||
slot.can_modify = false
|
||||
slot.offered = false
|
||||
slot.is_completed = false if slot.is_completed
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when a slot was successfully cancelled. Reset the slot style as 'ready to book'
|
||||
##
|
||||
$scope.slotCancelled = ->
|
||||
$scope.markSlotAsRemoved($scope.selectedEvent)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'currently looking for a new destination to exchange'
|
||||
##
|
||||
$scope.markSlotAsModifying = ->
|
||||
$scope.selectedEvent.backgroundColor = '#eee'
|
||||
$scope.selectedEvent.title = _t('space_reserve.i_change')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'the slot being exchanged will take this place'
|
||||
##
|
||||
$scope.changeModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.selectedEvent._id
|
||||
$scope.selectedEvent.backgroundColor = '#bbb'
|
||||
$scope.selectedEvent.title = _t('space_reserve.i_shift')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
##
|
||||
$scope.modifyTrainingSlot = ->
|
||||
$scope.events.placable.title = _t('space_reserve.i_ve_reserved')
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.borderColor = $scope.events.modifiable.borderColor
|
||||
$scope.events.placable.id = $scope.events.modifiable.id
|
||||
$scope.events.placable.is_reserved = true
|
||||
$scope.events.placable.can_modify = true
|
||||
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
$scope.events.modifiable.title = ''
|
||||
$scope.events.modifiable.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.events.modifiable.id = null
|
||||
$scope.events.modifiable.is_reserved = false
|
||||
$scope.events.modifiable.can_modify = false
|
||||
$scope.events.modifiable.is_completed = false if $scope.events.modifiable.is_completed
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
$scope.events.modifiable.title = _t('space_reserve.i_ve_reserved')
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to deal with the reservations of the user selected in the dropdown list instead of the current user's
|
||||
# reservations. (admins only)
|
||||
##
|
||||
$scope.updateMember = ->
|
||||
if $scope.ctrl.member
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
Availability.spaces {spaceId: $scope.space.id, member_id: $scope.ctrl.member.id}, (spaces) ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents'
|
||||
$scope.eventSources.splice(0, 1,
|
||||
events: spaces
|
||||
textColor: 'black'
|
||||
)
|
||||
# as the events are re-fetched for the new user, we must re-init the cart
|
||||
$scope.events.reserved = []
|
||||
$scope.selectedPlan = null
|
||||
$scope.plansAreShown = false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Add the provided plan to the current shopping cart
|
||||
# @param plan {Object} the plan to subscribe
|
||||
##
|
||||
$scope.selectPlan = (plan) ->
|
||||
# toggle selected plan
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Changes the user current view from the plan subsription screen to the machine reservation agenda
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.doNotSubscribePlan = (e)->
|
||||
e.preventDefault()
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
##
|
||||
$scope.showPlans = ->
|
||||
$scope.plansAreShown = true
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
$scope.afterPayment = (reservation)->
|
||||
angular.forEach $scope.events.paid, (spaceSlot, key) ->
|
||||
spaceSlot.is_reserved = true
|
||||
spaceSlot.can_modify = true
|
||||
spaceSlot.title = _t('space_reserve.i_ve_reserved')
|
||||
spaceSlot.backgroundColor = 'white'
|
||||
spaceSlot.borderColor = RESERVED_SLOT_BORDER_COLOR
|
||||
updateSpaceSlotId(spaceSlot, reservation)
|
||||
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.ctrl.member.training_credits = angular.copy(reservation.user.training_credits)
|
||||
$scope.ctrl.member.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits)
|
||||
Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
|
||||
refetchCalendar()
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
Member.get id: $scope.currentUser.id, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Triggered when the user clicks on a reservation slot in the agenda.
|
||||
# Defines the behavior to adopt depending on the slot status (already booked, free, ready to be reserved ...),
|
||||
# the user's subscription (current or about to be took) and the time (the user cannot modify a booked reservation
|
||||
# if it's too late).
|
||||
# @see http://fullcalendar.io/docs/mouse/eventClick/
|
||||
##
|
||||
calendarEventClickCb = (event, jsEvent, view) ->
|
||||
$scope.selectedEvent = event
|
||||
if $stateParams.id is 'all'
|
||||
$scope.training = event.training
|
||||
$scope.selectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Triggered when fullCalendar tries to graphicaly render an event block.
|
||||
# Append the event tag into the block, just after the event title.
|
||||
# @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
##
|
||||
eventRenderCb = (event, element, view)->
|
||||
if $scope.currentUser.role is 'admin' and event.tags.length > 0
|
||||
html = ''
|
||||
for tag in event.tags
|
||||
html += "<span class='label label-success text-white' title='#{tag.name}'>#{tag.name}</span>"
|
||||
element.find('.fc-time').append(html)
|
||||
return
|
||||
|
||||
|
||||
|
||||
##
|
||||
# After payment, update the id of the newly reserved slot with the id returned by the server.
|
||||
# This will allow the user to modify the reservation he just booked.
|
||||
# @param slot {Object}
|
||||
# @param reservation {Object}
|
||||
##
|
||||
updateSpaceSlotId = (slot, reservation)->
|
||||
angular.forEach reservation.slots, (s)->
|
||||
if slot.start_at == slot.start_at
|
||||
slot.id = s.id
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Update the calendar's display to render the new attributes of the events
|
||||
##
|
||||
updateCalendar = ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Asynchronously fetch the events from the API and refresh the calendar's view with these new events
|
||||
##
|
||||
refetchCalendar = ->
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
]
|
@ -77,8 +77,8 @@ Application.Controllers.controller "ShowTrainingController", ['$scope', '$state'
|
||||
# training can be reserved during the reservation process (the shopping cart may contains only one training and a subscription).
|
||||
##
|
||||
|
||||
Application.Controllers.controller "ReserveTrainingController", ["$scope", "$state", '$stateParams', '$filter', '$compile', "$uibModal", 'Auth', 'dialogs', '$timeout', 'Price', 'Availability', 'Slot', 'Member', 'Setting', 'CustomAsset', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'growl', 'settingsPromise', 'trainingPromise', '_t', 'Wallet', 'helpers', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $state, $stateParams, $filter, $compile, $uibModal, Auth, dialogs, $timeout, Price, Availability, Slot, Member, Setting, CustomAsset, availabilityTrainingsPromise, plansPromise, groupsPromise, growl, settingsPromise, trainingPromise, _t, Wallet, helpers, uiCalendarConfig, CalendarConfig) ->
|
||||
Application.Controllers.controller "ReserveTrainingController", ["$scope", '$stateParams', 'Auth', '$timeout', 'Availability', 'Member', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'settingsPromise', 'trainingPromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $stateParams, Auth, $timeout, Availability, Member, availabilityTrainingsPromise, plansPromise, groupsPromise, settingsPromise, trainingPromise, _t, uiCalendarConfig, CalendarConfig) ->
|
||||
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# Color of the selected event backgound
|
||||
SELECTED_EVENT_BG_COLOR = '#ffdd00'
|
||||
|
||||
# Slot already booked by the current user
|
||||
# Slot free to be booked
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::TRAINING_COLOR %>'
|
||||
|
||||
|
||||
@ -109,39 +109,34 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
groupObj.plans.push(plan) if plan.group_id == group.id
|
||||
$scope.plansClassifiedByGroup.push(groupObj)
|
||||
|
||||
## mapping of fullCalendar events.
|
||||
$scope.events =
|
||||
reserved: [] # Slots that the user wants to book
|
||||
modifiable: null # Slot that the user wants to change
|
||||
placable: null # Destination slot for the change
|
||||
paid: [] # Slots that were just booked by the user (transaction ok)
|
||||
moved: null # Slots that were just moved by the user (change done) -> {newSlot:* oldSlot: *}
|
||||
|
||||
## the moment when the slot selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.selectionTime = null
|
||||
|
||||
## the last clicked event in the calender
|
||||
$scope.selectedEvent = null
|
||||
|
||||
## indicates the state of the current view : calendar or plans information
|
||||
$scope.plansAreShown = false
|
||||
|
||||
## indicates if the selected training was validated (ie. added to the shopping cart)
|
||||
$scope.trainingIsValid = false
|
||||
|
||||
## contains the selected training once it was payed, allows to display a firendly end-of-shopping message
|
||||
$scope.paidTraining = null
|
||||
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## fullCalendar event. Training slot that the user want to book
|
||||
$scope.selectedTraining = null
|
||||
## the moment when the plan selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.planSelectionTime = null
|
||||
|
||||
## fullCalendar event. An already booked slot that the user want to modify
|
||||
$scope.slotToModify = null
|
||||
|
||||
## Once a training reservation was modified, will contains {newReservedSlot:{}, oldReservedSlot:{}}
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
## Selected training unless 'all' trainings are displayed
|
||||
## Selected training
|
||||
$scope.training = trainingPromise
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
|
||||
## Total price of the cart, that the user will pay
|
||||
$scope.amountTotal = 0
|
||||
|
||||
## Total amount of the elements in the cart, without considering any coupon
|
||||
$scope.totalNoCoupon = 0
|
||||
## 'all' OR training's slug
|
||||
$scope.mode = $stateParams.id
|
||||
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig = CalendarConfig
|
||||
@ -149,19 +144,113 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
maxTime: moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss'))
|
||||
eventClick: (event, jsEvent, view) ->
|
||||
calendarEventClickCb(event, jsEvent, view)
|
||||
eventAfterAllRender: (view)->
|
||||
$scope.events = uiCalendarConfig.calendars.calendar.fullCalendar 'clientEvents'
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element, view)
|
||||
|
||||
## Custom settings
|
||||
## Application global settings
|
||||
$scope.settings = settingsPromise
|
||||
|
||||
## Global config: message to the end user concerning the subscriptions rules
|
||||
$scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert
|
||||
|
||||
## Global config: message to the end user concerning the training reservation
|
||||
$scope.trainingExplicationsAlert = settingsPromise.training_explications_alert
|
||||
|
||||
## Global config: message to the end user giving advice about the training reservation
|
||||
$scope.trainingInformationMessage = settingsPromise.training_information_message
|
||||
$scope.enableBookingMove = (settingsPromise.booking_move_enable == "true")
|
||||
$scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay)
|
||||
$scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true")
|
||||
$scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay)
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'added to cart'
|
||||
##
|
||||
$scope.markSlotAsAdded = ->
|
||||
$scope.selectedEvent.backgroundColor = SELECTED_EVENT_BG_COLOR
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'never added to cart'
|
||||
##
|
||||
$scope.markSlotAsRemoved = (slot) ->
|
||||
slot.backgroundColor = 'white'
|
||||
slot.title = slot.training.name
|
||||
slot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
slot.id = null
|
||||
slot.isValid = false
|
||||
slot.is_reserved = false
|
||||
slot.can_modify = false
|
||||
slot.offered = false
|
||||
slot.is_completed = false if slot.is_completed
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when a slot was successfully cancelled. Reset the slot style as 'ready to book'
|
||||
##
|
||||
$scope.slotCancelled = ->
|
||||
$scope.markSlotAsRemoved($scope.selectedEvent)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'currently looking for a new destination to exchange'
|
||||
##
|
||||
$scope.markSlotAsModifying = ->
|
||||
$scope.selectedEvent.backgroundColor = '#eee'
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('i_change')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'the slot being exchanged will take this place'
|
||||
##
|
||||
$scope.changeModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = $scope.events.placable.training.name
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.selectedEvent._id
|
||||
$scope.selectedEvent.backgroundColor = '#bbb'
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('i_shift')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
##
|
||||
$scope.modifyTrainingSlot = ->
|
||||
$scope.events.placable.title = if $scope.currentUser.role isnt 'admin' then $scope.events.placable.training.name + " - " + _t('i_ve_reserved') else $scope.events.placable.training.name
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.borderColor = $scope.events.modifiable.borderColor
|
||||
$scope.events.placable.id = $scope.events.modifiable.id
|
||||
$scope.events.placable.is_reserved = true
|
||||
$scope.events.placable.can_modify = true
|
||||
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
$scope.events.modifiable.title = $scope.events.modifiable.training.name
|
||||
$scope.events.modifiable.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.events.modifiable.id = null
|
||||
$scope.events.modifiable.is_reserved = false
|
||||
$scope.events.modifiable.can_modify = false
|
||||
$scope.events.modifiable.is_completed = false if $scope.events.modifiable.is_completed
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = $scope.events.placable.training.name
|
||||
$scope.events.modifiable.title = if $scope.currentUser.role isnt 'admin' then $scope.events.modifiable.training.name + " - " + _t('i_ve_reserved') else $scope.events.modifiable.training.name
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
@ -173,67 +262,17 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
if $scope.ctrl.member
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
Availability.trainings {trainingId: $stateParams.id, member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
id = if $stateParams.id is 'all' then $stateParams.id else $scope.training.id
|
||||
Availability.trainings {trainingId: id, member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents'
|
||||
$scope.eventSources.push
|
||||
$scope.eventSources.splice(0, 1,
|
||||
events: trainings
|
||||
textColor: 'black'
|
||||
$scope.trainingIsValid = false
|
||||
$scope.paidTraining = null
|
||||
$scope.plansAreShown = false
|
||||
)
|
||||
# as the events are re-fetched for the new user, we must re-init the cart
|
||||
$scope.events.reserved = []
|
||||
$scope.selectedPlan = null
|
||||
$scope.selectedTraining = null
|
||||
$scope.slotToModify = null
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to mark the selected training as validated (add it to the shopping cart).
|
||||
##
|
||||
$scope.validTraining = ->
|
||||
$scope.trainingIsValid = true
|
||||
$scope.updatePrices()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Remove the training from the shopping cart
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeTraining = (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$scope.selectedTraining.backgroundColor = 'white'
|
||||
$scope.selectedTraining = null
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.trainingIsValid = false
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Validates the shopping chart and redirect the user to the payment step
|
||||
##
|
||||
$scope.payTraining = ->
|
||||
|
||||
# first, we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
reservation = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan)
|
||||
|
||||
Wallet.getWalletByUser {user_id: $scope.ctrl.member.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount)
|
||||
if $scope.currentUser.role isnt 'admin' and amountToPay > 0
|
||||
payByStripe(reservation)
|
||||
else
|
||||
if $scope.currentUser.role is 'admin' or amountToPay is 0
|
||||
payOnSite(reservation)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.error(_t('please_select_a_member_first'))
|
||||
|
||||
|
||||
|
||||
@ -242,17 +281,12 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# @param plan {Object} the plan to subscribe
|
||||
##
|
||||
$scope.selectPlan = (plan) ->
|
||||
if $scope.isAuthenticated()
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
$scope.updatePrices()
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
$scope.updatePrices()
|
||||
# toggle selected plan
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.login null, ->
|
||||
$scope.selectedPlan = plan
|
||||
$scope.updatePrices()
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
@ -264,7 +298,9 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
e.preventDefault()
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.updatePrices()
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
@ -272,96 +308,33 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.showPlans = ->
|
||||
$scope.plansAreShown = true
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, removing the previously booked slot from the selection
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeSlotToModify = (e) ->
|
||||
e.preventDefault()
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToModify.training.name + " - " + _t('i_ve_reserved') else $scope.slotToModify.training.name
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, cancel the choice of the new slot
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
$scope.removeSlotToPlace = (e)->
|
||||
e.preventDefault()
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
$scope.afterPayment = (reservation)->
|
||||
$scope.events.paid[0].backgroundColor = 'white'
|
||||
$scope.events.paid[0].is_reserved = true
|
||||
$scope.events.paid[0].can_modify = true
|
||||
updateTrainingSlotId($scope.events.paid[0], reservation)
|
||||
$scope.events.paid[0].borderColor = '#b2e774'
|
||||
$scope.events.paid[0].title = $scope.events.paid[0].training.name + " - " + _t('i_ve_reserved')
|
||||
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.ctrl.member.training_credits = angular.copy(reservation.user.training_credits)
|
||||
$scope.ctrl.member.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits)
|
||||
Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, confirm the modification.
|
||||
##
|
||||
$scope.modifyTrainingSlot = ->
|
||||
Slot.update {id: $scope.slotToModify.slot_id},
|
||||
slot:
|
||||
start_at: $scope.slotToPlace.start
|
||||
end_at: $scope.slotToPlace.end
|
||||
availability_id: $scope.slotToPlace.id
|
||||
, -> # success
|
||||
$scope.modifiedSlots =
|
||||
newReservedSlot: $scope.slotToPlace
|
||||
oldReservedSlot: $scope.slotToModify
|
||||
$scope.slotToPlace.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToPlace.training.name + " - " + _t('i_ve_reserved') else $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.borderColor = $scope.slotToModify.borderColor
|
||||
$scope.slotToPlace.slot_id = $scope.slotToModify.slot_id
|
||||
$scope.slotToPlace.is_reserved = true
|
||||
$scope.slotToPlace.can_modify = true
|
||||
$scope.slotToPlace = null
|
||||
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify.title = $scope.slotToModify.training.name
|
||||
$scope.slotToModify.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.slotToModify.slot_id = null
|
||||
$scope.slotToModify.is_reserved = false
|
||||
$scope.slotToModify.can_modify = false
|
||||
$scope.slotToModify.is_completed = false if $scope.slotToModify.is_completed
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # failure
|
||||
growl.error('an_error_occured_preventing_the_booked_slot_from_being_modified')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyMachineSlot = ->
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToModify.training.name + " - " + _t('i_ve_reserved') else $scope.slotToModify.training.name
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Update the prices, based on the current selection
|
||||
##
|
||||
$scope.updatePrices = ->
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan)
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
$scope.totalNoCoupon = res.price_without_coupon
|
||||
else
|
||||
$scope.amountTotal = null
|
||||
refetchCalendar()
|
||||
|
||||
|
||||
|
||||
@ -375,51 +348,6 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
Member.get id: $scope.currentUser.id, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
|
||||
# watch when a coupon is applied to re-compute the total price
|
||||
$scope.$watch 'coupon.applied', (newValue, oldValue) ->
|
||||
unless newValue == null and oldValue == null
|
||||
$scope.updatePrices()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create an hash map implementing the Reservation specs
|
||||
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
|
||||
# @param training {Object} fullCalendar event: training slot selected on the calendar
|
||||
# @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation
|
||||
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||
##
|
||||
mkReservation = (member, training, plan = null) ->
|
||||
reservation =
|
||||
user_id: member.id
|
||||
reservable_id: training.training.id
|
||||
reservable_type: 'Training'
|
||||
slots_attributes: []
|
||||
plan_id: (plan.id if plan)
|
||||
|
||||
reservation.slots_attributes.push
|
||||
start_at: training.start
|
||||
end_at: training.end
|
||||
availability_id: training.id
|
||||
offered: training.offered || false
|
||||
|
||||
reservation
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
|
||||
# @param reservation {Object} as returned by mkReservation()
|
||||
# @param coupon {Object} Coupon as returned from the API
|
||||
# @return {{reservation:Object, coupon_code:string}}
|
||||
##
|
||||
mkRequestParams = (reservation, coupon) ->
|
||||
params =
|
||||
reservation: reservation
|
||||
coupon_code: (coupon.code if coupon)
|
||||
|
||||
params
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -430,308 +358,26 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# @see http://fullcalendar.io/docs/mouse/eventClick/
|
||||
##
|
||||
calendarEventClickCb = (event, jsEvent, view) ->
|
||||
if $scope.ctrl.member
|
||||
# reserve a training if this training will not be reserved and is not about to move and not is completed
|
||||
if !event.is_reserved && !$scope.slotToModify && !event.is_completed
|
||||
if event != $scope.selectedTraining
|
||||
$scope.selectedTraining = event
|
||||
$scope.selectedTraining.offered = false
|
||||
event.backgroundColor = SELECTED_EVENT_BG_COLOR
|
||||
computeTrainingAmount($scope.selectedTraining)
|
||||
else
|
||||
$scope.selectedTraining = null
|
||||
event.backgroundColor = 'white'
|
||||
$scope.trainingIsValid = false
|
||||
$scope.paidTraining = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
# clean all others events background
|
||||
angular.forEach $scope.events, (e)->
|
||||
if event.id != e.id
|
||||
e.backgroundColor = 'white'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
# two if below for move training reserved
|
||||
# if training isnt reserved and have a training to modify and same training and not complete
|
||||
else if !event.is_reserved && $scope.slotToModify && slotCanBePlaced(event)
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = event.training.name
|
||||
$scope.slotToPlace = event
|
||||
event.backgroundColor = '#bbb'
|
||||
event.title = event.training.name + ' - ' + _t('i_shift')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
# if training reserved can modify
|
||||
else if event.is_reserved and (slotCanBeModified(event) or slotCanBeCanceled(event)) and !$scope.slotToModify and !$scope.selectedTraining
|
||||
event.movable = slotCanBeModified(event)
|
||||
event.cancelable = slotCanBeCanceled(event)
|
||||
if $scope.currentUser.role is 'admin'
|
||||
event.user =
|
||||
name: $scope.ctrl.member.name
|
||||
dialogs.confirm
|
||||
templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>'
|
||||
resolve:
|
||||
object: -> event
|
||||
, (type) -> # success
|
||||
if type == 'move'
|
||||
$scope.modifiedSlots = null
|
||||
$scope.slotToModify = event
|
||||
event.backgroundColor = '#eee'
|
||||
event.title = event.training.name + ' - ' + _t('i_change')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_cancel_this_reservation')
|
||||
, -> # cancel confirmed
|
||||
Slot.cancel {id: event.slot_id}, -> # successfully canceled
|
||||
growl.success _t('reservation_was_successfully_cancelled')
|
||||
$scope.canceledSlot = event
|
||||
$scope.canceledSlot.backgroundColor = 'white'
|
||||
$scope.canceledSlot.title = event.training.name
|
||||
$scope.canceledSlot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.canceledSlot.slot_id = null
|
||||
$scope.canceledSlot.is_reserved = false
|
||||
$scope.canceledSlot.can_modify = false
|
||||
$scope.canceledSlot.is_completed = false if event.is_completed
|
||||
$scope.canceledSlot = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cancellation_failed')
|
||||
, -> # canceled
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
$scope.selectedEvent = event
|
||||
if $stateParams.id is 'all'
|
||||
$scope.training = event.training
|
||||
$scope.selectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When events are rendered, adds attributes for popover and compile
|
||||
# Triggered when fullCalendar tries to graphicaly render an event block.
|
||||
# Append the event tag into the block, just after the event title.
|
||||
# @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
##
|
||||
eventRenderCb = (event, element, view)->
|
||||
# Comment these codes for show a popup of description, because we add feature page of training
|
||||
#element.attr(
|
||||
# 'uib-popover': $filter('humanize')($filter('simpleText')(event.training.description), 70)
|
||||
# 'popover-trigger': 'mouseenter'
|
||||
#)
|
||||
#$compile(element)($scope)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
##
|
||||
payByStripe = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'wallet', 'cgv', 'Auth', 'Reservation', 'helpers', '$filter', 'coupon'
|
||||
($scope, $uibModalInstance, $state, reservation, price, wallet, cgv, Auth, Reservation, helpers, $filter, coupon) ->
|
||||
# User's wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
##
|
||||
# Callback to process the payment with Stripe, triggered on button click
|
||||
##
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
growl.error(response.error.message)
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
if response.data.card
|
||||
$scope.alerts.push
|
||||
msg: response.data.card[0]
|
||||
type: 'danger'
|
||||
else
|
||||
$scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
##
|
||||
payOnSite = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>'
|
||||
size: 'sm'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', '$filter', 'reservation', 'price', 'wallet', 'Auth', 'Reservation', 'helpers', 'coupon'
|
||||
($scope, $uibModalInstance, $state, $filter, reservation, price, wallet, Auth, Reservation, helpers, coupon) ->
|
||||
# User's wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.price = price.price
|
||||
|
||||
# price to pay
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Button label
|
||||
if $scope.amount > 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')($scope.amount)}, "messageformat")
|
||||
else
|
||||
if price.price > 0 and $scope.walletAmount == 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')(price.price)}, "messageformat")
|
||||
else
|
||||
$scope.validButtonName = _t('confirm')
|
||||
|
||||
##
|
||||
# Callback to process the local payment, triggered on button click
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Computes the training amount depending of the member's credit
|
||||
# @param training {Object} training slot
|
||||
##
|
||||
computeTrainingAmount = (training)->
|
||||
# first we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, training) # reservation without any Plan -> we get the training price
|
||||
Price.compute mkRequestParams(r), (res) ->
|
||||
$scope.selectedTrainingAmount = res.price
|
||||
else
|
||||
$scope.selectedTrainingAmount = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
afterPayment = (reservation)->
|
||||
$scope.paidTraining = $scope.selectedTraining
|
||||
$scope.paidTraining.backgroundColor = 'white'
|
||||
$scope.paidTraining.is_reserved = true
|
||||
$scope.paidTraining.can_modify = true
|
||||
updateTrainingSlotId($scope.paidTraining, reservation)
|
||||
$scope.paidTraining.borderColor = '#b2e774'
|
||||
$scope.paidTraining.title = $scope.paidTraining.training.name + " - " + _t('i_ve_reserved')
|
||||
|
||||
$scope.selectedTraining = null
|
||||
$scope.trainingIsValid = false
|
||||
$scope.coupon.applied = null
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.ctrl.member.training_credits = angular.copy(reservation.user.training_credits)
|
||||
$scope.ctrl.member.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits)
|
||||
Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be modified by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeModified = (slot)->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment(new Date())
|
||||
if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be canceled by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeCanceled = (slot) ->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# For booking modifications, checks that the newly selected slot is valid
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBePlaced = (slot)->
|
||||
if slot.training.id == $scope.slotToModify.training.id and !slot.is_completed
|
||||
return true
|
||||
else
|
||||
return false
|
||||
if $scope.currentUser.role is 'admin' and event.tags.length > 0
|
||||
html = ''
|
||||
for tag in event.tags
|
||||
html += "<span class='label label-success text-white' title='#{tag.name}'>#{tag.name}</span>"
|
||||
element.find('.fc-time').append(html)
|
||||
return
|
||||
|
||||
|
||||
|
||||
@ -744,11 +390,29 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
updateTrainingSlotId = (slot, reservation)->
|
||||
angular.forEach reservation.slots, (s)->
|
||||
if slot.start_at == slot.start_at
|
||||
slot.slot_id = s.id
|
||||
slot.id = s.id
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
##
|
||||
# Update the calendar's display to render the new attributes of the events
|
||||
##
|
||||
updateCalendar = ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Asynchronously fetch the events from the API and refresh the calendar's view with these new events
|
||||
##
|
||||
refetchCalendar = ->
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
]
|
||||
|
569
app/assets/javascripts/directives/cart.coffee.erb
Normal file
569
app/assets/javascripts/directives/cart.coffee.erb
Normal file
@ -0,0 +1,569 @@
|
||||
Application.Directives.directive 'cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'helpers', '_t'
|
||||
, ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, helpers, _t) ->
|
||||
{
|
||||
restrict: 'E'
|
||||
scope:
|
||||
slot: '='
|
||||
slotSelectionTime: '='
|
||||
events: '='
|
||||
user: '='
|
||||
modePlans: '='
|
||||
plan: '='
|
||||
planSelectionTime: '='
|
||||
settings: '='
|
||||
onSlotAddedToCart: '='
|
||||
onSlotRemovedFromCart: '='
|
||||
onSlotStartToModify: '='
|
||||
onSlotModifyDestination: '='
|
||||
onSlotModifySuccess: '='
|
||||
onSlotModifyCancel: '='
|
||||
onSlotModifyUnselect: '='
|
||||
onSlotCancelSuccess: '='
|
||||
afterPayment: '='
|
||||
reservableId: '@'
|
||||
reservableType: '@'
|
||||
reservableName: '@'
|
||||
limitToOneSlot: '@'
|
||||
templateUrl: '<%= asset_path "shared/_cart.html" %>'
|
||||
link: ($scope, element, attributes) ->
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## total amount of the bill to pay
|
||||
$scope.amountTotal = 0
|
||||
|
||||
## total amount of the elements in the cart, without considering any coupon
|
||||
$scope.totalNoCoupon = 0
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
|
||||
## Global config: is the user authorized to change his bookings slots?
|
||||
$scope.enableBookingMove = ($scope.settings.booking_move_enable == "true")
|
||||
|
||||
## Global config: delay in hours before a booking while changing the booking slot is forbidden
|
||||
$scope.moveBookingDelay = parseInt($scope.settings.booking_move_delay)
|
||||
|
||||
## Global config: is the user authorized to cancel his bookings?
|
||||
$scope.enableBookingCancel = ($scope.settings.booking_cancel_enable == "true")
|
||||
|
||||
## Global config: delay in hours before a booking while the cancellation is forbidden
|
||||
$scope.cancelBookingDelay = parseInt($scope.settings.booking_cancel_delay)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Add the provided slot to the shopping cart (state transition from free to 'about to be reserved')
|
||||
# and increment the total amount of the cart if needed.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
$scope.validateSlot = (slot)->
|
||||
slot.isValid = true
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Remove the provided slot from the shopping cart (state transition from 'about to be reserved' to free)
|
||||
# and decrement the total amount of the cart if needed.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
# @param index {number} index of the slot in the reservation array
|
||||
# @param [event] {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeSlot = (slot, index, event)->
|
||||
event.preventDefault() if event
|
||||
$scope.events.reserved.splice(index, 1)
|
||||
# if is was the last slot, we remove any plan from the cart
|
||||
if $scope.events.reserved.length == 0
|
||||
$scope.selectedPlan = null
|
||||
$scope.plan = null
|
||||
$scope.modePlans = false
|
||||
$scope.onSlotRemovedFromCart(slot) if typeof $scope.onSlotRemovedFromCart == 'function'
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks that every selected slots were added to the shopping cart. Ie. will return false if
|
||||
# any checked slot was not validated by the user.
|
||||
##
|
||||
$scope.isSlotsValid = ->
|
||||
isValid = true
|
||||
angular.forEach $scope.events.reserved, (m)->
|
||||
isValid = false if !m.isValid
|
||||
isValid
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
##
|
||||
$scope.showPlans = ->
|
||||
# first, we ensure that a user was selected (admin) or logged (member)
|
||||
if Object.keys($scope.user).length > 0
|
||||
$scope.modePlans = true
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user hasn't the admin role
|
||||
growl.error(_t('cart.please_select_a_member_first'))
|
||||
|
||||
|
||||
##
|
||||
# Validates the shopping chart and redirect the user to the payment step
|
||||
##
|
||||
$scope.payCart = ->
|
||||
# first, we check that a user was selected
|
||||
if Object.keys($scope.user).length > 0
|
||||
reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan)
|
||||
|
||||
Wallet.getWalletByUser {user_id: $scope.user.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount)
|
||||
if not $scope.isAdmin() and amountToPay > 0
|
||||
payByStripe(reservation)
|
||||
else
|
||||
if $scope.isAdmin() or amountToPay is 0
|
||||
payOnSite(reservation)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.error(_t('cart.please_select_a_member_first'))
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, confirm the modification.
|
||||
##
|
||||
$scope.modifySlot = ->
|
||||
Slot.update {id: $scope.events.modifiable.id},
|
||||
slot:
|
||||
start_at: $scope.events.placable.start
|
||||
end_at: $scope.events.placable.end
|
||||
availability_id: $scope.events.placable.availability_id
|
||||
, -> # success
|
||||
# -> run the callback
|
||||
$scope.onSlotModifySuccess() if typeof $scope.onSlotModifySuccess == 'function'
|
||||
# -> set the events as successfully moved (to display a summary)
|
||||
$scope.events.moved =
|
||||
newSlot: $scope.events.placable
|
||||
oldSlot: $scope.events.modifiable
|
||||
# -> reset the 'moving' status
|
||||
$scope.events.placable = null
|
||||
$scope.events.modifiable = null
|
||||
, (err) -> # failure
|
||||
growl.error(_t('cart.unable_to_change_the_reservation'))
|
||||
console.error(err)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
# @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.cancelModifySlot = (event) ->
|
||||
event.preventDefault() if event
|
||||
$scope.onSlotModifyCancel() if typeof $scope.onSlotModifyCancel == 'function'
|
||||
$scope.events.placable = null
|
||||
$scope.events.modifiable = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, cancel the choice of the new slot
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeSlotToPlace = (e)->
|
||||
e.preventDefault()
|
||||
$scope.onSlotModifyUnselect() if typeof $scope.onSlotModifyUnselect == 'function'
|
||||
$scope.events.placable = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks if $scope.events.modifiable and $scope.events.placable have tag incompatibilities
|
||||
# @returns {boolean} true in case of incompatibility
|
||||
##
|
||||
$scope.tagMissmatch = ->
|
||||
return false if $scope.events.placable.tag_ids.length == 0
|
||||
for tag in $scope.events.modifiable.tags
|
||||
if tag.id not in $scope.events.placable.tag_ids
|
||||
return true
|
||||
false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Check if the currently logged user has teh 'admin' role?
|
||||
# @returns {boolean}
|
||||
##
|
||||
$scope.isAdmin = ->
|
||||
$rootScope.currentUser and $rootScope.currentUser.role is 'admin'
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the directive is loaded
|
||||
##
|
||||
initialize = ->
|
||||
# What the binded slot
|
||||
$scope.$watch 'slotSelectionTime', (newValue, oldValue) ->
|
||||
if newValue != oldValue
|
||||
slotSelectionChanged()
|
||||
$scope.$watch 'user', (newValue, oldValue) ->
|
||||
if newValue != oldValue
|
||||
resetCartState()
|
||||
updateCartPrice()
|
||||
$scope.$watch 'planSelectionTime', (newValue, oldValue) ->
|
||||
if newValue != oldValue
|
||||
planSelectionChanged()
|
||||
# watch when a coupon is applied to re-compute the total price
|
||||
$scope.$watch 'coupon.applied', (newValue, oldValue) ->
|
||||
unless newValue == null and oldValue == null
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback triggered when the selected slot changed
|
||||
##
|
||||
slotSelectionChanged = ->
|
||||
if $scope.slot
|
||||
if not $scope.slot.is_reserved and not $scope.events.modifiable and not $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
|
||||
index = $scope.events.reserved.indexOf($scope.slot)
|
||||
if index == -1
|
||||
if $scope.limitToOneSlot is 'true' and $scope.events.reserved[0]
|
||||
# if we limit the number of slots in the cart to 1, and there is already
|
||||
# a slot in the cart, we remove it before adding the new one
|
||||
$scope.removeSlot($scope.events.reserved[0], 0)
|
||||
# slot is not in the cart, so we add it
|
||||
$scope.events.reserved.push $scope.slot
|
||||
$scope.onSlotAddedToCart() if typeof $scope.onSlotAddedToCart == 'function'
|
||||
else
|
||||
# slot is in the cart, remove it
|
||||
$scope.removeSlot($scope.slot, index)
|
||||
# in every cases, because a new reservation has started, we reset the cart content
|
||||
resetCartState()
|
||||
# finally, we update the prices
|
||||
updateCartPrice()
|
||||
else if !$scope.slot.is_reserved and !$scope.slot.is_completed and $scope.events.modifiable
|
||||
# slot is not reserved but we are currently modifying a slot
|
||||
# -> we request the calender to change the rendering
|
||||
$scope.onSlotModifyUnselect() if typeof $scope.onSlotModifyUnselect == 'function'
|
||||
# -> then, we re-affect the destination slot
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.slot._id
|
||||
$scope.events.placable = $scope.slot
|
||||
else
|
||||
$scope.events.placable = null
|
||||
else if $scope.slot.is_reserved and $scope.events.modifiable and $scope.slot.is_reserved._id == $scope.events.modifiable._id
|
||||
# slot is reserved and currently modified
|
||||
# -> we cancel the modification
|
||||
$scope.cancelModifySlot()
|
||||
else if $scope.slot.is_reserved and (slotCanBeModified($scope.slot) or slotCanBeCanceled($scope.slot)) and !$scope.events.modifiable and $scope.events.reserved.length == 0
|
||||
# slot is reserved and is ok to be modified or cancelled
|
||||
# but we are not currently running a modification or having any slots in the cart
|
||||
# -> first the affect the modification/cancellation rights attributes to the current slot
|
||||
resetCartState()
|
||||
$scope.slot.movable = slotCanBeModified($scope.slot)
|
||||
$scope.slot.cancelable = slotCanBeCanceled($scope.slot)
|
||||
# -> then, we open a dialog to ask to the user to choose an action
|
||||
dialogs.confirm
|
||||
templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>'
|
||||
resolve:
|
||||
object: -> $scope.slot
|
||||
, (type) ->
|
||||
# the user has choosen an action, so we proceed
|
||||
if type == 'move'
|
||||
$scope.onSlotStartToModify() if typeof $scope.onSlotStartToModify == 'function'
|
||||
$scope.events.modifiable = $scope.slot
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('cart.confirmation_required')
|
||||
msg: _t('cart.do_you_really_want_to_cancel_this_reservation')
|
||||
, -> # cancel confirmed
|
||||
Slot.cancel {id: $scope.slot.id}, -> # successfully canceled
|
||||
growl.success _t('cart.reservation_was_cancelled_successfully')
|
||||
$scope.onSlotCancelSuccess() if typeof $scope.onSlotCancelSuccess == 'function'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cart.cancellation_failed')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Reset the parameters that may lead to a wrong price but leave the content (events added to cart)
|
||||
##
|
||||
resetCartState = ->
|
||||
$scope.selectedPlan = null
|
||||
$scope.coupon.applied = null
|
||||
$scope.events.moved = null
|
||||
$scope.events.paid = []
|
||||
$scope.events.modifiable = null
|
||||
$scope.events.placable = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be modified by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeModified = (slot)->
|
||||
return true if $scope.isAdmin()
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be canceled by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeCanceled = (slot) ->
|
||||
return true if $scope.isAdmin()
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback triggered when the selected slot changed
|
||||
##
|
||||
planSelectionChanged = ->
|
||||
if Auth.isAuthenticated()
|
||||
if $scope.selectedPlan != $scope.plan
|
||||
$scope.selectedPlan = $scope.plan
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
updateCartPrice()
|
||||
else
|
||||
$rootScope.login null, ->
|
||||
$scope.selectedPlan = $scope.plan
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
##
|
||||
# Update the total price of the current selection/reservation
|
||||
##
|
||||
updateCartPrice = ->
|
||||
if Object.keys($scope.user).length > 0
|
||||
r = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan)
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
$scope.totalNoCoupon = res.price_without_coupon
|
||||
setSlotsDetails(res.details)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.warning(_t('cart.please_select_a_member_first'))
|
||||
$scope.amountTotal = null
|
||||
|
||||
|
||||
setSlotsDetails = (details) ->
|
||||
angular.forEach $scope.events.reserved, (slot) ->
|
||||
angular.forEach details.slots, (s) ->
|
||||
if moment(s.start_at).isSame(slot.start)
|
||||
slot.promo = s.promo
|
||||
slot.price = s.price
|
||||
|
||||
|
||||
##
|
||||
# Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
|
||||
# @param reservation {Object} as returned by mkReservation()
|
||||
# @param coupon {Object} Coupon as returned from the API
|
||||
# @return {{reservation:Object, coupon_code:string}}
|
||||
##
|
||||
mkRequestParams = (reservation, coupon) ->
|
||||
params =
|
||||
reservation: reservation
|
||||
coupon_code: (coupon.code if coupon)
|
||||
|
||||
params
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create an hash map implementing the Reservation specs
|
||||
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
|
||||
# @param slots {Array<Object>} Array of fullCalendar events: slots selected on the calendar
|
||||
# @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation
|
||||
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||
##
|
||||
mkReservation = (member, slots, plan = null) ->
|
||||
reservation =
|
||||
user_id: member.id
|
||||
reservable_id: $scope.reservableId
|
||||
reservable_type: $scope.reservableType
|
||||
slots_attributes: []
|
||||
plan_id: (plan.id if plan)
|
||||
angular.forEach slots, (slot, key) ->
|
||||
reservation.slots_attributes.push
|
||||
start_at: slot.start
|
||||
end_at: slot.end
|
||||
availability_id: slot.availability_id
|
||||
offered: slot.offered || false
|
||||
|
||||
reservation
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
##
|
||||
payByStripe = (reservation) ->
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
##
|
||||
# Callback to process the payment with Stripe, triggered on button click
|
||||
##
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
growl.error(response.error.message)
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
if response.status == 500
|
||||
$scope.alerts.push
|
||||
msg: response.statusText
|
||||
type: 'danger'
|
||||
else
|
||||
if response.data.card and response.data.card.join('').length > 0
|
||||
$scope.alerts.push
|
||||
msg: response.data.card.join('. ')
|
||||
type: 'danger'
|
||||
else if response.data.payment and response.data.payment.join('').length > 0
|
||||
$scope.alerts.push
|
||||
msg: response.data.payment.join('. ')
|
||||
type: 'danger'
|
||||
$scope.attempting = false
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
##
|
||||
payOnSite = (reservation) ->
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>'
|
||||
size: 'sm'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) ->
|
||||
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Global price (total of all items)
|
||||
$scope.price = price.price
|
||||
|
||||
# Price to pay (wallet deducted)
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Button label
|
||||
if $scope.amount > 0
|
||||
$scope.validButtonName = _t('cart.confirm_payment_of_html', {ROLE:$rootScope.currentUser.role, AMOUNT:$filter('currency')($scope.amount)}, "messageformat")
|
||||
else
|
||||
if price.price > 0 and $scope.walletAmount == 0
|
||||
$scope.validButtonName = _t('cart.confirm_payment_of_html', {ROLE:$rootScope.currentUser.role, AMOUNT:$filter('currency')(price.price)}, "messageformat")
|
||||
else
|
||||
$scope.validButtonName = _t('confirm')
|
||||
|
||||
##
|
||||
# Callback to process the local payment, triggered on button click
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push({msg: _t('cart.a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Actions to run after the payment was successfull
|
||||
##
|
||||
afterPayment = (reservation) ->
|
||||
# we set the cart content as 'paid' to display a summary of the transaction
|
||||
$scope.events.paid = $scope.events.reserved
|
||||
# we call the external callback if present
|
||||
$scope.afterPayment(reservation) if typeof $scope.afterPayment == 'function'
|
||||
# we reset the coupon and the cart content and we unselect the slot
|
||||
$scope.events.reserved = []
|
||||
$scope.coupon.applied = null
|
||||
$scope.slot = null
|
||||
$scope.selectedPlan = null
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the directive
|
||||
initialize()
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -373,7 +373,7 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.logged.machines_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select',
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input']).$promise
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise
|
||||
]
|
||||
.state 'app.admin.machines_edit',
|
||||
url: '/machines/:id/edit'
|
||||
@ -388,6 +388,97 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.machines_edit', 'app.shared.machine']).$promise
|
||||
]
|
||||
|
||||
# spaces
|
||||
.state 'app.public.spaces_list',
|
||||
url: '/spaces'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/index.html" %>'
|
||||
controller: 'SpacesController'
|
||||
resolve:
|
||||
spacesPromise: ['Space', (Space)->
|
||||
Space.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.spaces_list']).$promise
|
||||
]
|
||||
.state 'app.admin.space_new',
|
||||
url: '/spaces/new'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/new.html" %>'
|
||||
controller: 'NewSpaceController'
|
||||
resolve:
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.space_new', 'app.shared.space']).$promise
|
||||
]
|
||||
.state 'app.public.space_show',
|
||||
url: '/spaces/:id'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/show.html" %>'
|
||||
controller: 'ShowSpaceController'
|
||||
resolve:
|
||||
spacePromise: ['Space', '$stateParams', (Space, $stateParams)->
|
||||
Space.get(id: $stateParams.id).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.space_show']).$promise
|
||||
]
|
||||
.state 'app.admin.space_edit',
|
||||
url: '/spaces/:id/edit'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/edit.html" %>'
|
||||
controller: 'EditSpaceController'
|
||||
resolve:
|
||||
spacePromise: ['Space', '$stateParams', (Space, $stateParams)->
|
||||
Space.get(id: $stateParams.id).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.space_edit', 'app.shared.space']).$promise
|
||||
]
|
||||
.state 'app.logged.space_reserve',
|
||||
url: '/spaces/:id/reserve'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/reserve.html" %>'
|
||||
controller: 'ReserveSpaceController'
|
||||
resolve:
|
||||
spacePromise: ['Space', '$stateParams', (Space, $stateParams)->
|
||||
Space.get(id: $stateParams.id).$promise
|
||||
]
|
||||
availabilitySpacesPromise: ['Availability', '$stateParams', (Availability, $stateParams)->
|
||||
Availability.spaces({spaceId: $stateParams.id}).$promise
|
||||
]
|
||||
plansPromise: ['Plan', (Plan)->
|
||||
Plan.query().$promise
|
||||
]
|
||||
groupsPromise: ['Group', (Group)->
|
||||
Group.query().$promise
|
||||
]
|
||||
settingsPromise: ['Setting', (Setting)->
|
||||
Setting.query(names: "['booking_window_start',
|
||||
'booking_window_end',
|
||||
'booking_move_enable',
|
||||
'booking_move_delay',
|
||||
'booking_cancel_enable',
|
||||
'booking_cancel_delay',
|
||||
'subscription_explications_alert',
|
||||
'space_explications_alert']").$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.logged.space_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select',
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise
|
||||
]
|
||||
|
||||
# trainings
|
||||
.state 'app.public.trainings_list',
|
||||
url: '/trainings'
|
||||
@ -451,7 +542,7 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.logged.trainings_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select',
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input']).$promise
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise
|
||||
]
|
||||
# notifications
|
||||
.state 'app.logged.notifications',
|
||||
@ -549,6 +640,9 @@ angular.module('application.router', ['ui.router']).
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
Machine.query().$promise
|
||||
]
|
||||
spacesPromise: ['Space', (Space) ->
|
||||
Space.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.calendar']).$promise
|
||||
]
|
||||
@ -770,6 +864,15 @@ angular.module('application.router', ['ui.router']).
|
||||
couponsPromise: ['Coupon', (Coupon) ->
|
||||
Coupon.query().$promise
|
||||
]
|
||||
spacesPromise: ['Space', (Space) ->
|
||||
Space.query().$promise
|
||||
]
|
||||
spacesPricesPromise: ['Price', (Price)->
|
||||
Price.query(priceable_type: 'Space', plan_id: 'null').$promise
|
||||
]
|
||||
spacesCreditsPromise: ['Credit', (Credit) ->
|
||||
Credit.query({creditable_type: 'Space'}).$promise
|
||||
]
|
||||
|
||||
# plans
|
||||
.state 'app.admin.plans',
|
||||
@ -778,15 +881,9 @@ angular.module('application.router', ['ui.router']).
|
||||
prices: ['Pricing', (Pricing) ->
|
||||
Pricing.query().$promise
|
||||
]
|
||||
machines: ['Machine', (Machine) ->
|
||||
Machine.query().$promise
|
||||
]
|
||||
groups: ['Group', (Group) ->
|
||||
Group.query().$promise
|
||||
]
|
||||
plans: ['Plan', (Plan) ->
|
||||
Plan.query().$promise
|
||||
]
|
||||
partners: ['User', (User) ->
|
||||
User.query({role: 'partner'}).$promise
|
||||
]
|
||||
@ -807,6 +904,15 @@ angular.module('application.router', ['ui.router']).
|
||||
templateUrl: '<%= asset_path "admin/plans/edit.html" %>'
|
||||
controller: 'EditPlanController'
|
||||
resolve:
|
||||
spaces: ['Space', (Space) ->
|
||||
Space.query().$promise
|
||||
]
|
||||
machines: ['Machine', (Machine) ->
|
||||
Machine.query().$promise
|
||||
]
|
||||
plans: ['Plan', (Plan) ->
|
||||
Plan.query().$promise
|
||||
]
|
||||
planPromise: ['Plan', '$stateParams', (Plan, $stateParams) ->
|
||||
Plan.get({id: $stateParams.id}).$promise
|
||||
]
|
||||
@ -1038,6 +1144,7 @@ angular.module('application.router', ['ui.router']).
|
||||
'training_information_message',
|
||||
'subscription_explications_alert',
|
||||
'event_explications_alert',
|
||||
'space_explications_alert',
|
||||
'booking_window_start',
|
||||
'booking_window_end',
|
||||
'booking_move_enable',
|
||||
|
@ -17,6 +17,11 @@ Application.Services.factory 'Availability', ["$resource", ($resource)->
|
||||
url: '/api/availabilities/trainings/:trainingId'
|
||||
params: {trainingId: "@trainingId"}
|
||||
isArray: true
|
||||
spaces:
|
||||
method: 'GET'
|
||||
url: '/api/availabilities/spaces/:spaceId'
|
||||
params: {spaceId: "@spaceId"}
|
||||
isArray: true
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
||||
|
8
app/assets/javascripts/services/space.coffee
Normal file
8
app/assets/javascripts/services/space.coffee
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'Space', ["$resource", ($resource)->
|
||||
$resource "/api/spaces/:id",
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
@ -5,6 +5,7 @@
|
||||
//.bg-yellow { background-color: $yellow !important; }
|
||||
.bg-token { background-color: rgba(230, 208, 137, 0.49); }
|
||||
.bg-machine { background-color: $beige; }
|
||||
.bg-space { background-color: $cyan }
|
||||
.bg-formation { background-color: $violet; }
|
||||
.bg-event { background-color: $japonica; }
|
||||
.bg-atelier { background-color: $blue; }
|
||||
@ -39,4 +40,5 @@
|
||||
.text-purple { color: $violet !important; }
|
||||
.text-japonica { color: $japonica !important; }
|
||||
.text-beige { color: $beige !important; }
|
||||
.text-cyan { color: $cyan !important; }
|
||||
.text-green, .green { color: #79C84A !important; }
|
||||
|
@ -176,6 +176,7 @@ p, .widget p {
|
||||
.r-n { border-radius: 0 0 0 0; }
|
||||
|
||||
.p-xs { padding: 5px;}
|
||||
.p-s { padding: 10px;}
|
||||
.p-lg { padding: 30px; }
|
||||
.p-l { padding: 16px; }
|
||||
|
||||
|
@ -43,6 +43,7 @@ $blue: $brand-info;
|
||||
$green: $brand-success;
|
||||
$beige: #e4cd78;
|
||||
$violet: #bd7ae9;
|
||||
$cyan: #3fc7ff;
|
||||
$japonica: #dd7e6b;
|
||||
|
||||
$border-color: #dddddd;
|
||||
@ -767,7 +768,7 @@ $panel-footer-padding: $panel-heading-padding !default;
|
||||
$panel-border-radius: $border-radius-large !default;
|
||||
|
||||
// add sleede
|
||||
$panel-border: $border-color !default;
|
||||
$panel-border: $border-color !default;
|
||||
$panel-heading-bg: #fff !default;
|
||||
$panel-footer-bg: #fff !default;
|
||||
|
||||
|
@ -7,14 +7,15 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'calendar_management' }}</h1>
|
||||
<h1 translate>{{ 'admin_calendar.calendar_management' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper">
|
||||
<span class="badge text-sm bg-formation m-t-sm" translate>{{ 'trainings' }}</span><br>
|
||||
<span class="badge text-sm bg-machine" translate>{{ 'machines' }}</span>
|
||||
<section class="heading-actions wrapper" ng-class="{'p-s': !fablabWithoutSpaces}">
|
||||
<span class="badge text-sm bg-formation" ng-class="{'m-t-sm': fablabWithoutSpaces}" translate>{{ 'admin_calendar.trainings' }}</span><br>
|
||||
<span class="badge text-sm bg-machine" translate>{{ 'admin_calendar.machines' }}</span><br>
|
||||
<span class="badge text-sm bg-space" ng-hide="fablabWithoutSpaces" translate>{{ 'admin_calendar.spaces' }}</span>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -31,7 +32,7 @@
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'ongoing_reservations' }}</h3>
|
||||
<h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled" ng-if="reservations.length > 0">
|
||||
@ -42,7 +43,7 @@
|
||||
<span class="btn btn-warning btn-xs" ng-click="cancelBooking(r)" ng-if="!r.canceled_at"><i class="fa fa-times red"></i></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="reservations.length == 0" translate>{{ 'no_reservations' }}</div>
|
||||
<div ng-if="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,7 +51,7 @@
|
||||
<div class="col-sm-12 col-md-12 col-lg-3" ng-if="availability.machine_ids.length > 0">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'machines' }}</h3>
|
||||
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled">
|
||||
|
@ -1,22 +1,41 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red">
|
||||
{{ 'DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
|
||||
{{ 'admin_calendar.DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-center font-sbold" translate>{{ 'you_can_define_a_training_on_that_slot' }}</p>
|
||||
<div>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" ng-model="available_type" ng-change="changeAvailableType()"> {{ 'link_a_training' | translate }}
|
||||
</label>
|
||||
<div class="modal-body" ng-show="step === 1">
|
||||
<label class="m-t-sm" translate>{{ 'admin_calendar.what_kind_of_slot_do_you_want_to_create' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" id="training" name="available_type" value="training" ng-model="availability.available_type">
|
||||
<span translate>{{ 'admin_calendar.training' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" id="machine" name="available_type" value="machines" ng-model="availability.available_type">
|
||||
<span translate>{{ 'admin_calendar.machine' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio" ng-hide="fablabWithoutSpaces">
|
||||
<label>
|
||||
<input type="radio" id="space" name="available_type" value="space" ng-model="availability.available_type" ng-disabled="spaces.length === 0">
|
||||
<span translate>{{ 'admin_calendar.space' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="step === 2">
|
||||
|
||||
<p class="text-center font-sbold m-t" ng-show="availability.available_type == 'machines'"><span class="underline" translate>{{ 'or_' }}</span> {{ '_select_some_machines' | translate }}</p>
|
||||
<div ng-show="availability.available_type == 'machines'">
|
||||
<p class="text-center font-sbold m-t-sm">{{ 'admin_calendar.select_some_machines' | translate }}</p>
|
||||
|
||||
<div class="checkbox" ng-show="availability.available_type == 'machines'">
|
||||
<label class="checkbox" ng-repeat="machine in machines">
|
||||
<input type="checkbox" ng-click="toggleSelection(machine)"> {{machine.name}} <span class="text-xs">(id {{machine.id}})</span>
|
||||
</label>
|
||||
<div class="form-group m-l-xl">
|
||||
<label class="checkbox" ng-repeat="machine in machines">
|
||||
<input type="checkbox" ng-click="toggleSelection(machine)"> {{machine.name}} <span class="text-xs">(id {{machine.id}})</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="availability.available_type == 'training'">
|
||||
@ -24,27 +43,42 @@
|
||||
</select>
|
||||
<div class="row m-t">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-6 control-label" translate>{{ 'number_of_tickets' }}</label>
|
||||
<label class="col-sm-6 control-label" for="nb_places_training" translate>{{ 'admin_calendar.number_of_tickets' }}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" class="form-control" ng-model="availability.nb_total_places">
|
||||
<input type="number" id="nb_places_training" class="form-control" ng-model="availability.nb_total_places">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="timeAdjust" class="m-t-lg">
|
||||
<p class="text-center font-sbold" translate>{{ 'adjust_the_opening_hours' }}</p>
|
||||
|
||||
<div ng-show="availability.available_type == 'space'">
|
||||
<select ng-model="selectedSpace" class="form-control m-t-sm" ng-options="t.name for t in spaces" ng-change="setNbTotalPlaces()">
|
||||
</select>
|
||||
<div class="row m-t">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-6 control-label" for="nb_places_space" translate>{{ 'admin_calendar.number_of_tickets' }}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" id="nb_places_space" class="form-control" ng-model="availability.nb_total_places">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="step === 3">
|
||||
<div id="timeAdjust" class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.adjust_the_opening_hours' }}</p>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-2">
|
||||
<uib-timepicker ng-model="start" hour-step="timepickers.start.hstep" readonly-input="true" minute-step="timepickers.start.mstep" show-meridian="false"></uib-timepicker>
|
||||
</div>
|
||||
<span class="col-md-1 m-t-xl m-l" translate>{{ 'to_time' }}</span>
|
||||
<span class="col-md-1 m-t-xl m-l" translate>{{ 'admin_calendar.to_time' }}</span>
|
||||
<fieldset ng-disabled="endDateReadOnly" class="col-md-5">
|
||||
<uib-timepicker ng-model="end" hour-step="timepickers.end.hstep" readonly-input="true" minute-step="timepickers.end.mstep" show-meridian="false"></uib-timepicker>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tagAssociate" class="m-t-lg">
|
||||
<p class="text-center font-sbold" translate>{{ 'restrict_this_slot_with_labels_(optional)' }}</p>
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.restrict_this_slot_with_labels_(optional)' }}</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<ui-select multiple ng-model="availability.tag_ids" class="form-control">
|
||||
@ -59,7 +93,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer" ng-show="step < 3">
|
||||
<button class="btn btn-info" ng-click="previous()" ng-disabled="step === 1" translate>{{ 'admin_calendar.previous' }}</button>
|
||||
<button class="btn btn-info" ng-click="next()" translate>{{ 'admin_calendar.next' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step === 3">
|
||||
<button class="btn btn-info" ng-click="previous()" translate>{{ 'admin_calendar.previous' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
||||
|
@ -1,19 +1,19 @@
|
||||
<h2 translate>{{ 'general_information' }}</h2>
|
||||
<h2 translate>{{ 'plan_form.general_information' }}</h2>
|
||||
<input type="hidden" name="_method" value="{{method}}">
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$invalid}">
|
||||
<label for="plan[base_name]">{{ 'name' | translate }} *</label>
|
||||
<label for="plan[base_name]">{{ 'plan_form.name' | translate }} *</label>
|
||||
<input type="text" id="plan[base_name]"
|
||||
name="plan[base_name]"
|
||||
class="form-control"
|
||||
ng-maxlength="24"
|
||||
ng-model="plan.base_name"
|
||||
required="required"/>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.required" translate>{{ 'name_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.maxlength" translate>{{ 'name_length_must_be_less_than_24_characters' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.required" translate>{{ 'plan_form.name_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.maxlength" translate>{{ 'plan_form.name_length_must_be_less_than_24_characters' }}</span>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[type]'].$dirty && planForm['plan[type]'].$invalid}">
|
||||
<label for="plan[type]">{{ 'type' | translate }} *</label>
|
||||
<label for="plan[type]">{{ 'plan_form.type' | translate }} *</label>
|
||||
<select id="plan[type]"
|
||||
name="plan[type]"
|
||||
class="form-control"
|
||||
@ -23,40 +23,40 @@
|
||||
<option value="Plan" ng-selected="plan.type == 'Plan'" translate>{{ 'standard' }}</option>
|
||||
<option value="PartnerPlan" ng-selected="plan.type == 'PartnerPlan'" translate>{{ 'partner' }}</option>
|
||||
</select>
|
||||
<span class="help-block error" ng-show="planForm['plan[type]'].$dirty && planForm['plan[type]'].$error.required" translate>{{ 'type_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[type]'].$dirty && planForm['plan[type]'].$error.required" translate>{{ 'plan_form.type_is_required' }}</span>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[group_id]'].$dirty && planForm['plan[group_id]'].$invalid}">
|
||||
<label for="plan[group_id]">{{ 'group' | translate }} *</label>
|
||||
<label for="plan[group_id]">{{ 'plan_form.group' | translate }} *</label>
|
||||
<select id="plan[group_id]"
|
||||
name="plan[group_id]"
|
||||
class="form-control"
|
||||
ng-model="plan.group_id"
|
||||
required="required"
|
||||
ng-disabled="method == 'PATCH'">
|
||||
<option value="all" translate>{{ 'transversal_(all_groups)' }}</option>
|
||||
<option value="all" translate>{{ 'plan_form.transversal_(all_groups)' }}</option>
|
||||
<optgroup label="Groupes">
|
||||
<option ng-repeat="group in groups" ng-value="group.id" ng-selected="plan.group_id == group.id">{{group.name}}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<span class="help-block" ng-show="planForm['plan[group_id]'].$dirty && planForm['plan[group_id]'].$error.required" translate>{{ 'group_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[group_id]'].$dirty && planForm['plan[group_id]'].$error.required" translate>{{ 'plan_form.group_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[interval]'].$dirty && planForm['plan[interval]'].$invalid}">
|
||||
<label for="plan[interval]">{{ 'period' | translate }} *</label>
|
||||
<label for="plan[interval]">{{ 'plan_form.period' | translate }} *</label>
|
||||
<select id="plan[interval]"
|
||||
name="plan[interval]"
|
||||
class="form-control"
|
||||
ng-model="plan.interval"
|
||||
ng-disabled="method == 'PATCH'"
|
||||
required="required">
|
||||
<option value="month" ng-selected="plan.interval == 'month'" translate>{{ 'month' }}</option>
|
||||
<option value="year" ng-selected="plan.interval == 'year'" translate>{{ 'year' }}</option>
|
||||
<option value="month" ng-selected="plan.interval == 'month'" translate>{{ 'plan_form.month' }}</option>
|
||||
<option value="year" ng-selected="plan.interval == 'year'" translate>{{ 'plan_form.year' }}</option>
|
||||
</select>
|
||||
<span class="help-block" ng-show="planForm['plan[interval]'].$dirty && planForm['plan[interval]'].$error.required" translate>{{ 'period_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[interval]'].$dirty && planForm['plan[interval]'].$error.required" translate>{{ 'plan_form.period_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[interval_count]'].$dirty && planForm['plan[interval_count]'].$invalid}">
|
||||
<label for="plan[interval]">{{ 'number_of_periods' | translate }} *</label>
|
||||
<label for="plan[interval]">{{ 'plan_form.number_of_periods' | translate }} *</label>
|
||||
<input id="plan[interval_count]"
|
||||
name="plan[interval_count]"
|
||||
class="form-control"
|
||||
@ -65,12 +65,12 @@
|
||||
ng-disabled="method == 'PATCH'"
|
||||
required="required"
|
||||
min="1"/>
|
||||
<span class="help-block" ng-show="planForm['plan[interval_count]'].$dirty && planForm['plan[interval_count]'].$error.required" translate>{{ 'number_of_periods_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[interval_count]'].$dirty && planForm['plan[interval_count]'].$error.required" translate>{{ 'plan_form.number_of_periods_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group" ng-class="{'has-error': planForm['plan[amount]'].$dirty && planForm['plan[amount]'].$invalid}">
|
||||
<label for="plan[amount]">{{ 'subscription_price' | translate }} *</label>
|
||||
<label for="plan[amount]">{{ 'plan_form.subscription_price' | translate }} *</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{currencySymbol}}</span>
|
||||
<input id="plan[amount]"
|
||||
@ -80,24 +80,24 @@
|
||||
ng-required="true"
|
||||
ng-model="plan.amount"/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="planForm['plan[amount]'].$dirty && planForm['plan[amount]'].$error.required" translate>{{ 'price_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[amount]'].$dirty && planForm['plan[amount]'].$error.required" translate>{{ 'plan_form.price_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label translate>{{ 'visual_prominence_of_the_subscription' }}</label>
|
||||
<label translate>{{ 'plan_form.visual_prominence_of_the_subscription' }}</label>
|
||||
<input ng-model="plan.ui_weight"
|
||||
type="number"
|
||||
name="plan[ui_weight]"
|
||||
class="form-control">
|
||||
<span class="help-block">
|
||||
{{ 'on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list' | translate }}
|
||||
{{ 'an_evelated_number_means_a_higher_prominence' | translate }}
|
||||
{{ 'plan_form.on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list' | translate }}
|
||||
{{ 'plan_form.an_evelated_number_means_a_higher_prominence' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group m-t-md">
|
||||
<label for="plan[is_rolling]" class="control-label m-r-md">{{ 'rolling_subscription' | translate }} *</label>
|
||||
<label for="plan[is_rolling]" class="control-label m-r-md">{{ 'plan_form.rolling_subscription' | translate }} *</label>
|
||||
<input bs-switch
|
||||
ng-model="plan.isRolling"
|
||||
id="plan[is_rolling]"
|
||||
@ -112,8 +112,8 @@
|
||||
<span ng-if="method == 'PATCH'">{{ (plan.is_rolling ? 'yes' : 'no') | translate }}</span>
|
||||
<input type="hidden" name="plan[is_rolling]" value="{{plan.isRolling}}"/>
|
||||
<span class="help-block">
|
||||
{{ 'a_rolling_subscription_will_begin_the_day_of_the_first_training' | translate }}
|
||||
{{ 'otherwise_it_will_begin_as_soon_as_it_is_bought' | translate }}
|
||||
{{ 'plan_form.a_rolling_subscription_will_begin_the_day_of_the_first_training' | translate }}
|
||||
{{ 'plan_form.otherwise_it_will_begin_as_soon_as_it_is_bought' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -121,12 +121,12 @@
|
||||
<!-- PDF description attachement -->
|
||||
<input type="hidden" ng-model="plan.plan_file_attributes.id" name="plan[plan_file_attributes][id]" ng-value="plan.plan_file_attributes.id" />
|
||||
<input type="hidden" ng-model="plan.plan_file_attributes._destroy" name="plan[plan_file_attributes][_destroy]" ng-value="plan.plan_file_attributes._destroy"/>
|
||||
<label class="m-t-md" translate>{{ 'information_sheet' }}</label>
|
||||
<label class="m-t-md" translate>{{ 'plan_form.information_sheet' }}</label>
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(plan.plan_file_attributes)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment || plan.plan_file_attributes.attachment_identifier}}</span>
|
||||
</div>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'attach_an_information_sheet' }}</span>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'plan_form.attach_an_information_sheet' }}</span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span><input type="file"
|
||||
name="plan[plan_file_attributes][attachment]"
|
||||
accept="image/*, application/pdf"></span>
|
||||
@ -135,7 +135,7 @@
|
||||
|
||||
<div class="form-group m-t-md" ng-show="plan.type == 'PartnerPlan' && method != 'PATCH'">
|
||||
<input type="hidden" ng-model="plan.partnerId" name="plan[partner_id]" ng-value="plan.partnerId" />
|
||||
<label for="plan[partner_id]">{{ 'notified_partner' | translate }} *</label>
|
||||
<label for="plan[partner_id]">{{ 'plan_form.notified_partner' | translate }} *</label>
|
||||
<div class="input-group">
|
||||
<select class="form-control"
|
||||
ng-model="plan.partnerId"
|
||||
@ -144,10 +144,10 @@
|
||||
<option value=""></option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="openPartnerNewModal()"><i class="fa fa-user-plus"></i> {{ 'new_user' | translate }}</button>
|
||||
<button class="btn btn-default" type="button" ng-click="openPartnerNewModal()"><i class="fa fa-user-plus"></i> {{ 'plan_form.new_user' | translate }}</button>
|
||||
</span>
|
||||
</div>
|
||||
<span class="help-block" translate>{{ 'as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user' }}</span>
|
||||
<span class="help-block" translate>{{ 'plan_form.as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="plan.partners">
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1>{{ 'subscription_plan' | translate }} {{ plan.base_name }}</h1>
|
||||
<h1>{{ 'edit_plan.subscription_plan' | translate }} {{ plan.base_name }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -30,23 +30,23 @@
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/plans/_form.html' %>'"></ng-include>
|
||||
|
||||
<h2 class="m-t-xl" translate>{{ 'prices' }}</h2>
|
||||
<h2 class="m-t-xl" translate>{{ 'edit_plan.prices' }}</h2>
|
||||
<div class="form-group col-md-6 col-lg-offset-6">
|
||||
<input type="hidden" ng-model="plan.parent" name="plan[parent_id]" ng-value="plan.parent"/>
|
||||
<label for="parentPlan" translate>{{ 'copy_prices_from' }}</label>
|
||||
<label for="parentPlan" translate>{{ 'edit_plan.copy_prices_from' }}</label>
|
||||
<select id="parentPlan" ng-options="plan.id as humanReadablePlanName(plan, groups) for plan in plans" ng-model="plan.parent" ng-change="copyPricesFromPlan()" class="form-control">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
<h3 translate>{{ 'machines' }}</h3>
|
||||
<h3 translate>{{ 'edit_plan.machines' }}</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th translate>{{ 'machine' }}</th>
|
||||
<th translate>{{ 'hourly_rate' }}</th>
|
||||
<th translate>{{ 'edit_plan.machine' }}</th>
|
||||
<th translate>{{ 'edit_plan.hourly_rate' }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr ng-repeat="price in plan.prices">
|
||||
<tr ng-repeat="price in plan.prices" ng-if="price.priceable_type === 'Machine'">
|
||||
<td style="width: 60%;">{{ getMachineName(price.priceable_id) }} (id {{ price.priceable_id }}) *</td>
|
||||
<td>
|
||||
<div class="input-group" ng-class="{'has-error': planForm['plan[prices_attributes][][amount]'].$dirty && planForm['plan[prices_attributes][][amount]'].$invalid}">
|
||||
@ -59,6 +59,27 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 ng-hide="fablabWithoutSpaces" translate>{{ 'edit_plan.spaces' }}</h3>
|
||||
<table class="table" ng-hide="fablabWithoutSpaces">
|
||||
<thead>
|
||||
<th translate>{{ 'edit_plan.space' }}</th>
|
||||
<th translate>{{ 'edit_plan.hourly_rate' }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr ng-repeat="price in plan.prices" ng-if="price.priceable_type === 'Space'">
|
||||
<td style="width: 60%;">{{ getSpaceName(price.priceable_id) }} *</td>
|
||||
<td>
|
||||
<div class="input-group" ng-class="{'has-error': planForm['plan[prices_attributes][][amount]'].$dirty && planForm['plan[prices_attributes][][amount]'].$invalid}">
|
||||
<span class="input-group-addon">{{currencySymbol}}</span>
|
||||
<input type="number" class="form-control" name="plan[prices_attributes][][amount]" ng-value="price.amount" required="required"/>
|
||||
<input type="hidden" class="form-control" name="plan[prices_attributes][][id]" ng-value="price.id"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="planForm.$invalid"/>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'add_a_subscription_plan' }}</h1>
|
||||
<h1 translate>{{ 'new_plan.add_a_subscription_plan' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
<h2 translate>{{ 'list_of_the_coupons' }}</h2>
|
||||
<h2 translate>{{ 'pricing.list_of_the_coupons' }}</h2>
|
||||
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.coupons_new" translate>{{ 'add_a_new_coupon' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.coupons_new" translate>{{ 'pricing.add_a_new_coupon' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'name' }}</th>
|
||||
<th translate>{{ 'discount' }}</th>
|
||||
<th translate>{{ 'nb_of_usages' }}</th>
|
||||
<th translate>{{ 'status' }}</th>
|
||||
<th translate>{{ 'pricing.name' }}</th>
|
||||
<th translate>{{ 'pricing.discount' }}</th>
|
||||
<th translate>{{ 'pricing.nb_of_usages' }}</th>
|
||||
<th translate>{{ 'pricing.status' }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -19,7 +19,7 @@
|
||||
<span ng-show="coupon.type == 'amount_off'">{{coupon.amount_off}} {{currencySymbol}}</span>
|
||||
</td>
|
||||
<td>{{coupon.usages}}</td>
|
||||
<td translate>{{coupon.status}}</td>
|
||||
<td translate>{{'pricing.'+coupon.status}}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-default" ng-click="sendCouponToUser(coupon)"><i class="fa fa-send-o"></i> </button>
|
||||
<button type="button" class="btn btn-default" ui-sref="app.admin.coupons_edit({id:coupon.id})"><i class="fa fa-pencil-square-o"></i></button>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<h2 class="m-t-lg" translate>{{ 'trainings' }}</h2>
|
||||
<h2 class="m-t-lg" translate>{{ 'pricing.trainings' }}</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'subscription' }}</th>
|
||||
<th style="width:10%" translate>{{ 'credits' }}</th>
|
||||
<th style="width:50%" translate>{{ 'related_trainings' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.subscription' }}</th>
|
||||
<th style="width:10%" translate>{{ 'pricing.credits' }}</th>
|
||||
<th style="width:50%" translate>{{ 'pricing.related_trainings' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -43,17 +43,17 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="m-t-lg" translate>{{ 'machines' }}</h2>
|
||||
<h2 class="m-t-lg" translate>{{ 'pricing.machines' }}</h2>
|
||||
<div class="btn-group m-t-md m-b-md">
|
||||
<button type="button" class="btn btn-warning" ng-click="addMachineCredit($event)" translate>{{ 'add_a_machine_credit' }}</button>
|
||||
<button type="button" class="btn btn-warning" ng-click="addMachineCredit($event)" translate>{{ 'pricing.add_a_machine_credit' }}</button>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'machine' }}</th>
|
||||
<th style="width:10%" translate>{{ 'hours' }}</th>
|
||||
<th style="width:50%" translate>{{ 'related_subscriptions' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.machine' }}</th>
|
||||
<th style="width:10%" translate>{{ 'pricing.hours' }}</th>
|
||||
<th style="width:50%" translate>{{ 'pricing.related_subscriptions' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -94,4 +94,56 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="m-t-lg" translate>{{ 'pricing.spaces' }}</h2>
|
||||
<div class="btn-group m-t-md m-b-md">
|
||||
<button type="button" class="btn btn-warning" ng-click="addSpaceCredit($event)" translate>{{ 'pricing.add_a_space_credit' }}</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'pricing.space' }}</th>
|
||||
<th style="width:10%" translate>{{ 'pricing.hours' }}</th>
|
||||
<th style="width:50%" translate>{{ 'pricing.related_subscriptions' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="sc in spaceCredits">
|
||||
<td>
|
||||
<span editable-select="sc.creditable_id" e-name="creditable_id" e-form="rowform" e-ng-options="s.id as s.name for s in spaces" e-required>
|
||||
{{ showCreditableName(sc) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-number="sc.hours" e-name="hours" e-form="rowform" e-required>
|
||||
{{ sc.hours }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-select="sc.plan_id" e-ng-options="p.id as humanReadablePlanName(p, groups, 'short') for p in plans" e-name="plan_id" e-form="rowform">
|
||||
{{ getPlanFromId(sc.plan_id) | humanReadablePlanName: groups: 'short' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form editable-form name="rowform" onbeforesave="saveSpaceCredit($data, sc.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == sc">
|
||||
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelSpaceCredit(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeSpaceCredit($index)">
|
||||
<i class="fa fa-trash-o"></i> {{ 'delete' | translate }} (!)
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'pricing_management' }}</h1>
|
||||
<h1 translate>{{ 'pricing.pricing_management' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -21,23 +21,27 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'subscriptions' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.subscriptions' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/subscriptions.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'trainings' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.trainings' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/trainings.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'machine_hours' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.machine_hours' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/machine_hours.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'credits' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.spaces' | translate }}" ng-hide="fablabWithoutSpaces">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/spaces.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'pricing.credits' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/credits.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'coupons' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.coupons' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/coupons.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<div class="alert alert-warning m-t">
|
||||
{{ 'these_prices_match_machine_hours_rates_' | translate }} <span class="font-bold" translate>{{ '_without_subscriptions' }}</span>.
|
||||
{{ 'pricing.these_prices_match_machine_hours_rates_' | translate }} <span class="font-bold" translate>{{ 'pricing._without_subscriptions' }}</span>.
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'machines' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.machines' }}</th>
|
||||
<th style="width:20%" ng-repeat="group in groups">
|
||||
<span class="text-u-c text-sm">{{group.name}}</span>
|
||||
</th>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red" translate>{{ 'send_a_coupon' }}</h3>
|
||||
<h3 class="text-center red" translate>{{ 'pricing.send_a_coupon' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<select-member></select-member>
|
||||
<div class="widget panel b-a m">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 class="panel-title" translate>{{ 'coupon' }}</h3>
|
||||
<h3 class="panel-title" translate>{{ 'pricing.coupon' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<table>
|
||||
@ -13,12 +13,12 @@
|
||||
<tr><th style="width:60%"></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td translate>{{'code'}}</td> <td>{{coupon.code}}</td></tr>
|
||||
<tr><td translate>{{'discount'}}</td> <td><span ng-show="coupon.type == 'percent_off'">{{coupon.percent_off}} %</span><span ng-show="coupon.type == 'amount_off'">{{coupon.amount_off}} {{currencySymbol}}</span></td></tr>
|
||||
<tr><td translate>{{'validity_per_user'}}</td> <td translate>{{coupon.validity_per_user}}</td></tr>
|
||||
<tr><td translate>{{'valid_until'}}</td> <td>{{coupon.valid_until | amDateFormat:'L'}}</td></tr>
|
||||
<tr><td translate>{{'usages'}}</td> <td>{{coupon.usages}} / {{coupon.max_usages | maxCount}}</td></tr>
|
||||
<tr><td translate>{{'enabled'}}</td> <td>{{coupon.active | booleanFormat}}</td></tr>
|
||||
<tr><td translate>{{'pricing.code'}}</td> <td>{{coupon.code}}</td></tr>
|
||||
<tr><td translate>{{'pricing.discount'}}</td> <td><span ng-show="coupon.type == 'percent_off'">{{coupon.percent_off}} %</span><span ng-show="coupon.type == 'amount_off'">{{coupon.amount_off}} {{currencySymbol}}</span></td></tr>
|
||||
<tr><td translate>{{'pricing.validity_per_user'}}</td> <td translate>{{'pricing.'+coupon.validity_per_user}}</td></tr>
|
||||
<tr><td translate>{{'pricing.valid_until'}}</td> <td>{{coupon.valid_until | amDateFormat:'L'}}</td></tr>
|
||||
<tr><td translate>{{'pricing.usages'}}</td> <td>{{coupon.usages}} / {{coupon.max_usages | maxCount}}</td></tr>
|
||||
<tr><td translate>{{'pricing.enabled'}}</td> <td>{{coupon.active | booleanFormat}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
26
app/assets/templates/admin/pricing/spaces.html.erb
Normal file
26
app/assets/templates/admin/pricing/spaces.html.erb
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="alert alert-warning m-t">
|
||||
{{ 'pricing.these_prices_match_space_hours_rates_' | translate }} <span class="font-bold" translate>{{ 'pricing._without_subscriptions' }}</span>.
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'pricing.spaces' }}</th>
|
||||
<th style="width:20%" ng-repeat="group in groups">
|
||||
<span class="text-u-c text-sm">{{group.name}}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="space in spaces">
|
||||
<td>
|
||||
{{ space.name }}
|
||||
</td>
|
||||
<td ng-repeat="group in groups">
|
||||
<span editable-number="findPriceBy(spacesPrices, space.id, group.id).amount"
|
||||
onbeforesave="updatePrice($data, findPriceBy(spacesPrices, space.id, group.id))">
|
||||
{{ findPriceBy(spacesPrices, space.id, group.id).amount | currency}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -1,21 +1,21 @@
|
||||
<h2 translate>{{ 'list_of_the_subscription_plans' }}</h2>
|
||||
<h2 translate>{{ 'pricing.list_of_the_subscription_plans' }}</h2>
|
||||
|
||||
<div ng-show="fablabWithoutPlans" class="alert alert-warning m-t">
|
||||
{{ 'beware_the_subscriptions_are_disabled_on_this_application' | translate }}
|
||||
{{ 'you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }}
|
||||
<br>{{ 'for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }}
|
||||
{{ 'pricing.beware_the_subscriptions_are_disabled_on_this_application' | translate }}
|
||||
{{ 'pricing.you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }}
|
||||
<br>{{ 'pricing.for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }}
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.plans.new" translate>{{ 'add_a_new_subscription_plan' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.plans.new" translate>{{ 'pricing.add_a_new_subscription_plan' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="" ng-click="setOrderPlans('type')">{{ 'type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='type', 'fa fa-sort-alpha-desc': orderPlans=='-type', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('name')">{{ 'name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='name', 'fa fa-sort-alpha-desc': orderPlans=='-name', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('interval')">{{ 'duration' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-amount-asc': orderPlans=='interval', 'fa fa-sort-amount-desc': orderPlans=='-interval', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('group_id')">{{ 'group' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='group_id', 'fa fa-sort-alpha-desc': orderPlans=='-group_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th class="hidden-xs"><a href="" ng-click="setOrderPlans('ui_weight')">{{ 'prominence' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='ui_weight', 'fa fa-sort-numeric-desc': orderPlans=='-ui_weight', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('amount')">{{ 'price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='amount', 'fa fa-sort-numeric-desc': orderPlans=='-amount', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('type')">{{ 'pricing.type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='type', 'fa fa-sort-alpha-desc': orderPlans=='-type', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('name')">{{ 'pricing.name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='name', 'fa fa-sort-alpha-desc': orderPlans=='-name', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('interval')">{{ 'pricing.duration' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-amount-asc': orderPlans=='interval', 'fa fa-sort-amount-desc': orderPlans=='-interval', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('group_id')">{{ 'pricing.group' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='group_id', 'fa fa-sort-alpha-desc': orderPlans=='-group_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th class="hidden-xs"><a href="" ng-click="setOrderPlans('pricing.ui_weight')">{{ 'pricing.prominence' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='ui_weight', 'fa fa-sort-numeric-desc': orderPlans=='-ui_weight', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('amount')">{{ 'pricing.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='amount', 'fa fa-sort-numeric-desc': orderPlans=='-amount', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'trainings' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.trainings' }}</th>
|
||||
<th style="width:20%" ng-repeat="group in groups">
|
||||
<span class="text-u-c text-sm">{{group.name}}</span>
|
||||
</th>
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
<div class="row m-t-lg m-b-lg">
|
||||
<div class="col-sm-offset-4 col-sm-4">
|
||||
<h1 ng-model="aboutTitleSetting.value" medium-editor options='{"placeholder": "{{ "title_of_the_about_page" | translate }}", "disableToolbar": true, "disableReturn": false}' class="text-u-c"></h1>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<h1 ng-model="aboutTitleSetting.value" medium-editor options='{"placeholder": "{{ "settings.title_of_the_about_page" | translate }}", "disableToolbar": true, "disableReturn": false}' class="text-u-c"></h1>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutTitleSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<div class="text-justify" ng-model="aboutBodySetting.value" medium-editor options='{"placeholder": "{{ "input_the_main_content" | translate }}",
|
||||
<div class="text-justify" ng-model="aboutBodySetting.value" medium-editor options='{"placeholder": "{{ "settings.input_the_main_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -20,12 +20,12 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutBodySetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-2">
|
||||
<div ng-model="aboutContactsSetting.value" medium-editor options='{"placeholder": "{{ "input_the_fablab_contacts" | translate }}",
|
||||
<div ng-model="aboutContactsSetting.value" medium-editor options='{"placeholder": "{{ "settings.input_the_fablab_contacts" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutContactsSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
<div class="panel panel-default m-t-md">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'title' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.title' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row m-t-lg">
|
||||
<div class="col-md-4">
|
||||
<form role="form" novalidate>
|
||||
<label for="fablabName" class="control-label m-r" translate>{{ 'fablab_title' }}</label>
|
||||
<label for="fablabName" class="control-label m-r" translate>{{ 'settings.fablab_title' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><i class="fa fa-font"></i></div>
|
||||
<input type="text" id="fablabName" ng-model="fablabName.value" class="form-control" placeholder="{{ 'fablab_name' | translate }}"/>
|
||||
<input type="text" id="fablabName" ng-model="fablabName.value" class="form-control" placeholder="{{ 'settings.fablab_name' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(fablabName)" translate>{{ 'save' }}</button>
|
||||
@ -19,13 +19,13 @@
|
||||
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<form role="form" novalidate>
|
||||
<h4 class="control-label m-r" translate>{{ 'title_concordance' }}</h4>
|
||||
<h4 class="control-label m-r" translate>{{ 'settings.title_concordance' }}</h4>
|
||||
<div class="form-group">
|
||||
<input type="radio" name="nameGenre" id="nameGenreMale" ng-model="nameGenre.value" ng-value="'male'" />
|
||||
<label for="nameGenreMale">{{ 'male' | translate }} <span style="font-weight: normal">{{ 'eg' | translate }} <cite>{{ 'about' | translate }} <strong translate>{{ 'male_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
<label for="nameGenreMale">{{ 'settings.male' | translate }} <span style="font-weight: normal">{{ 'settings.eg' | translate }} <cite>{{ 'settings.about' | translate }} <strong translate>{{ 'settings.male_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
<br/>
|
||||
<input type="radio" name="nameGenre" id="nameGenreFemale" ng-model="nameGenre.value" ng-value="'female'" />
|
||||
<label for="nameGenreFemale">{{ 'female' | translate }} <span style="font-weight: normal">{{ 'eg' | translate }} <cite>{{ 'about' | translate }} <strong translate>{{ 'female_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
<label for="nameGenreFemale">{{ 'settings.female' | translate }} <span style="font-weight: normal">{{ 'settings.eg' | translate }} <cite>{{ 'settings.about' | translate }} <strong translate>{{ 'settings.female_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(nameGenre)" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
@ -36,15 +36,15 @@
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'customize_information_messages' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.customize_information_messages' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'message_of_the_machine_booking_page' }}</h4>
|
||||
<div ng-model="machineExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.message_of_the_machine_booking_page' }}</h4>
|
||||
<div ng-model="machineExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -52,8 +52,8 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(machineExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'warning_message_of_the_training_booking_page'}}</h4>
|
||||
<div ng-model="trainingExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.warning_message_of_the_training_booking_page'}}</h4>
|
||||
<div ng-model="trainingExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -61,8 +61,8 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(trainingExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'information_message_of_the_training_reservation_page'}}</h4>
|
||||
<div ng-model="trainingInformationMessage.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.information_message_of_the_training_reservation_page'}}</h4>
|
||||
<div ng-model="trainingInformationMessage.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -70,38 +70,46 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(trainingInformationMessage)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'message_of_the_subscriptions_page' }}</h4>
|
||||
<div ng-model="subscriptionExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.message_of_the_subscriptions_page' }}</h4>
|
||||
<div ng-model="subscriptionExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(subscriptionExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'message_of_the_events_page' }}</h4>
|
||||
<div ng-model="eventExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.message_of_the_events_page' }}</h4>
|
||||
<div ng-model="eventExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(eventExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3" ng-hide="fablabWithoutSpaces">
|
||||
<h4 translate>{{ 'settings.message_of_the_spaces_page' }}</h4>
|
||||
<div ng-model="spaceExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(spaceExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'legal_documents'}}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.legal_documents'}}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-warning m-t" translate>
|
||||
{{ 'if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user' }}
|
||||
{{ 'settings.if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user' }}
|
||||
</div>
|
||||
<div class="row">
|
||||
<form class="col-md-6" method="post" action="{{actionUrl.cgv}}" novalidate name="cgvForm" ng-upload="submited(content)" ng-submit="addLoader('cgv')" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="cgv-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.cgv">
|
||||
<label for="tnc_file" class="control-label m-r" translate>{{ 'general_terms_and_conditions_(T&C)' }}</label>
|
||||
<label for="tnc_file" class="control-label m-r" translate>{{ 'settings.general_terms_and_conditions_(T&C)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(cgvFile.custom_asset_file_attributes.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
@ -125,7 +133,7 @@
|
||||
<form class="col-md-6" method="post" action="{{actionUrl.cgu}}" novalidate name="cguForm" ng-upload="submited(content)" ng-submit="addLoader('cgu')" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="cgu-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.cgu">
|
||||
<label for="tos_file" class="control-label m-r" translate>{{ 'terms_of_service_(TOS)' }}</label>
|
||||
<label for="tos_file" class="control-label m-r" translate>{{ 'settings.terms_of_service_(TOS)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(cguFile.custom_asset_file_attributes.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
@ -150,21 +158,21 @@
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'customize_the_graphics' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.customize_the_graphics' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-warning m-t">
|
||||
<span translate>{{ 'for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height' }}</span><br/>
|
||||
<span translate>{{ 'concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels' }}</span><br/>
|
||||
<span translate>{{ 'settings.for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height' }}</span><br/>
|
||||
<span translate>{{ 'settings.concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels' }}</span><br/>
|
||||
<br/>
|
||||
<span translate>{{ 'remember_to_refresh_the_page_for_the_changes_to_take_effect' }}</span>
|
||||
<span translate>{{ 'settings.remember_to_refresh_the_page_for_the_changes_to_take_effect' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form class="custom-logo-container" method="post" action="{{actionUrl.logo}}" novalidate name="logoForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="logo-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.logo">
|
||||
<h3 class="m-l" translate>{{ 'logo_(white_background)' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_(white_background)' }}</h3>
|
||||
<div class="custom-logo" style="background-image: url({{customLogo}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!customLogo" class="img-responsive">
|
||||
<img base-sixty-four-image="customLogo" ng-show="customLogo && customLogo.base64">
|
||||
@ -172,7 +180,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
@ -190,7 +198,7 @@
|
||||
<form class="custom-logo-container" method="post" action="{{actionUrl.logoBlack}}" novalidate name="logoBlackForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="logo-black-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.logoBlack">
|
||||
<h3 class="m-l" translate>{{ 'logo_(black_background)' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_(black_background)' }}</h3>
|
||||
<div class="custom-logo bg-dark" style="background-image: url({{customLogoBlack}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-black-xs" bs-holder ng-show="!customLogoBlack" class="img-responsive">
|
||||
<img base-sixty-four-image="customLogoBlack" ng-show="customLogoBlack && customLogoBlack.base64">
|
||||
@ -198,7 +206,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
@ -216,7 +224,7 @@
|
||||
<form class="custom-favicon-container" method="post" action="{{actionUrl.favicon}}" novalidate name="faviconForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="favicon-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.favicon">
|
||||
<h3 class="m-l" translate>{{ 'favicon' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.favicon' }}</h3>
|
||||
<div class="custom-favicon" style="background-image: url({{customFavicon}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/32x32/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!customFavicon" class="img-responsive">
|
||||
<img base-sixty-four-image="customFavicon" ng-show="customFavicon && customFavicon.base64">
|
||||
@ -224,7 +232,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_favicon' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_favicon' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png,image/x-icon,image/ico,image/vnd.microsoft.icon"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
@ -241,14 +249,14 @@
|
||||
</div>
|
||||
<div class="row m-t m-l-xs">
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ 'main_colour' }}</h4>
|
||||
<h4 translate>{{ 'settings.main_colour' }}</h4>
|
||||
<form role="form" class="form-inline" name="mainColorForm" novalidate>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</div>
|
||||
<input type="text" minicolors ng-model="mainColorSetting.value" class="form-control" placeholder="{{ 'primary' | translate}}"/>
|
||||
<input type="text" minicolors ng-model="mainColorSetting.value" class="form-control" placeholder="{{ 'settings.primary' | translate}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -257,14 +265,14 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ 'secondary_colour' }}</h4>
|
||||
<h4 translate>{{ 'settings.secondary_colour' }}</h4>
|
||||
<form role="form" class="form-inline" name="secondColorForm" novalidate>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</div>
|
||||
<input type="text" minicolors ng-model="secondColorSetting.value" class="form-control" placeholder="{{ 'secondary' | translate}}"/>
|
||||
<input type="text" minicolors ng-model="secondColorSetting.value" class="form-control" placeholder="{{ 'settings.secondary' | translate}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -278,7 +286,7 @@
|
||||
<form class="custom-profile-image-container" method="post" action="{{actionUrl.profileImage}}" novalidate name="profileImageForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="profile-image-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.profileImage">
|
||||
<h3 class="m-l" translate>{{ 'background_picture_of_the_profile_banner' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.background_picture_of_the_profile_banner' }}</h3>
|
||||
<div class="custom-profile-image" style="background-image: url({{profileImage}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!profileImage" class="img-responsive">
|
||||
<img base-sixty-four-image="profileImage" ng-show="profileImage && profileImage.base64">
|
||||
@ -286,7 +294,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_profile_banner' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_profile_banner' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
|
@ -2,21 +2,21 @@
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4 translate>{{ 'news_of_the_home_page' }}</h4>
|
||||
<div ng-model="homeBlogpostSetting.value" class="well" medium-editor options='{"placeholder": "{{ "type_your_news_here" | translate }}",
|
||||
<h4 translate>{{ 'settings.news_of_the_home_page' }}</h4>
|
||||
<div ng-model="homeBlogpostSetting.value" class="well" medium-editor options='{"placeholder": "{{ "settings.type_your_news_here" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]}'></div>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'leave_it_empty_to_not_bring_up_any_news_on_the_home_page' | translate }}</span>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.leave_it_empty_to_not_bring_up_any_news_on_the_home_page' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(homeBlogpostSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4 translate>{{ 'twitter_stream' }}</h4>
|
||||
<h4 translate>{{ 'settings.twitter_stream' }}</h4>
|
||||
<form role="form" class="form-inline" name="twitterForm" novalidate>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-twitter"></i>
|
||||
</div>
|
||||
<input type="text" ng-model="twitterSetting.value" class="form-control" placeholder="{{ 'name_of_the_twitter_account' | translate }}"/>
|
||||
<input type="text" ng-model="twitterSetting.value" class="form-control" placeholder="{{ 'settings.name_of_the_twitter_account' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'customize_the_application' }}</h1>
|
||||
<h1 translate>{{ 'settings.customize_the_application' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -20,18 +20,18 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'general' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.general' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/general.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'home_page' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.home_page' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/home_page.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'about' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.about' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/about.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
<uib-tab heading="{{ 'reservations' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.reservations' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/reservations.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -1,20 +1,20 @@
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'reservations_parameters' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.reservations_parameters' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'confine_the_booking_agenda' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.confine_the_booking_agenda' }}</h3>
|
||||
<div class="col-md-2">
|
||||
<h4 translate>{{ 'opening_time' }}</h4>
|
||||
<h4 translate>{{ 'settings.opening_time' }}</h4>
|
||||
<uib-timepicker ng-model="windowStart.value" hour-step="timepicker.hstep" minute-step="timepicker.mstep" show-meridian="false"></uib-timepicker>
|
||||
</div>
|
||||
<div class="col-md-4 m-t">
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(windowStart)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<h4 translate>{{ 'closing_time' }}</h4>
|
||||
<h4 translate>{{ 'settings.closing_time' }}</h4>
|
||||
<uib-timepicker ng-model="windowEnd.value" hour-step="timepicker.hstep" minute-step="timepicker.mstep" show-meridian="false"></uib-timepicker>
|
||||
</div>
|
||||
<div class="col-md-4 m-t">
|
||||
@ -22,23 +22,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'ability_for_the_users_to_move_their_reservations' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.ability_for_the_users_to_move_their_reservations' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableMove" class="control-label m-r" translate>{{ 'reservations_shifting' }}</label>
|
||||
<label for="enableMove" class="control-label m-r" translate>{{ 'settings.reservations_shifting' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="enableMove.value"
|
||||
id="enableMove"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'enabled' | translate }}"
|
||||
switch-off-text="{{ 'disabled' | translate }}"
|
||||
switch-on-text="{{ 'settings.enabled' | translate }}"
|
||||
switch-off-text="{{ 'settings.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(enableMove)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="enableMove.value">
|
||||
<form class="col-md-4" name="moveDelayForm">
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -51,23 +51,23 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'ability_for_the_users_to_cancel_their_reservations' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.ability_for_the_users_to_cancel_their_reservations' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableCancel" class="control-label m-r" translate>{{ 'reservations_cancelling' }}</label>
|
||||
<label for="enableCancel" class="control-label m-r" translate>{{ 'settings.reservations_cancelling' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="enableCancel.value"
|
||||
id="enableCancel"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'enabled' | translate }}"
|
||||
switch-off-text="{{ 'disabled' | translate }}"
|
||||
switch-on-text="{{ 'settings.enabled' | translate }}"
|
||||
switch-off-text="{{ 'settings.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(enableCancel)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="enableCancel.value">
|
||||
<form class="col-md-4" name="cancelDelayForm">
|
||||
<label for="cancelDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<label for="cancelDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -85,27 +85,27 @@
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'reservations_reminders' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.reservations_reminders' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'notification_sending_before_the_reservation_occurs' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.notification_sending_before_the_reservation_occurs' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableReminder" class="control-label m-r" translate>{{ 'reservations_reminders' }}</label>
|
||||
<label for="enableReminder" class="control-label m-r" translate>{{ 'settings.reservations_reminders' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="enableReminder.value"
|
||||
id="enableReminder"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'enabled' | translate }}"
|
||||
switch-off-text="{{ 'disabled' | translate }}"
|
||||
switch-on-text="{{ 'settings.enabled' | translate }}"
|
||||
switch-off-text="{{ 'settings.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(enableReminder)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="enableReminder.value">
|
||||
<form class="col-md-4" name="reminderDelayForm">
|
||||
<label for="reminderDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<label for="reminderDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -114,7 +114,7 @@
|
||||
<input type="number" class="form-control" id="reminderDelay" ng-model="reminderDelay.value" min="0">
|
||||
</div>
|
||||
<span class="help-block text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'default_value_is_24_hours' | translate }}
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'settings.default_value_is_24_hours' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(reminderDelay)" ng-disabled="reminderDelayForm.$invalid" translate>{{ 'save' }}</button>
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
<uib-tab ng-repeat="stat in statistics" heading="{{stat.label}}" select="setActiveTab(stat)" ng-if="stat.table && !(stat.es_type_key == 'subscription' && fablabWithoutPlans)">
|
||||
<uib-tab ng-repeat="stat in statistics" heading="{{stat.label}}" select="setActiveTab(stat)" ng-hide="hiddenTab(stat)">
|
||||
<form id="filters_form" name="filters_form" class="form-inline m-t-md m-b-lg" novalidate="novalidate">
|
||||
<div id="agePickerPane" class="form-group datepicker-container" style="z-index:102;">
|
||||
<button id="agePickerExpand" class="btn btn-default" type="button" ng-click="agePicker.show = !agePicker.show">
|
||||
|
@ -7,14 +7,14 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md hide-b-r-lg">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'calendar' }}</h1>
|
||||
<h1 translate>{{ 'calendar.calendar' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md hidden-lg">
|
||||
<div class="heading-actions wrapper">
|
||||
<button type="button" class="btn btn-default m-t m-b" ng-click="openFilterAside()">
|
||||
<span class="fa fa-filter"></span> {{ 'filter-calendar' | translate }}
|
||||
<span class="fa fa-filter"></span> {{ 'calendar.filter_calendar' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,7 +38,7 @@
|
||||
<div class="col-lg-3 hidden-md hidden-sm hidden-xs">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'filter-calendar' }}</h3>
|
||||
<h3 translate>{{ 'calendar.filter_calendar' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper calendar-filter">
|
||||
<ng-include src="'<%= asset_path 'calendar/filter.html' %>'"></ng-include>
|
||||
@ -53,7 +53,7 @@
|
||||
<div class="widget">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close($event)"><span>×</span></button>
|
||||
<h1 class="modal-title" translate>{{ 'filter-calendar' }}</h1>
|
||||
<h1 class="modal-title" translate>{{ 'calendar.filter_calendar' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body widget-content calendar-filter calendar-filter-aside">
|
||||
<ng-include src="'<%= asset_path 'calendar/filter.html' %>'"></ng-include>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div>
|
||||
<div class="row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-purple" translate>{{ 'trainings' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-purple" translate>{{ 'calendar.trainings' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.trainings" ng-change="toggleFilter('trainings', filter)">
|
||||
</div>
|
||||
<div ng-repeat="t in trainings" class="row">
|
||||
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="m-t">
|
||||
<div class="row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-beige" translate>{{ 'machines' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-beige" translate>{{ 'calendar.machines' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.machines" ng-change="toggleFilter('machines', filter)">
|
||||
</div>
|
||||
<div ng-repeat="m in machines" class="row">
|
||||
@ -18,11 +18,21 @@
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="m.checked" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-t">
|
||||
<div class="row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-cyan" translate>{{ 'calendar.spaces' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.spaces" ng-change="toggleFilter('spaces', filter)">
|
||||
</div>
|
||||
<div ng-repeat="s in spaces" class="row">
|
||||
<span class="col-md-11 col-sm-11 col-xs-11">{{::s.name}}</span>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="s.checked" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-t row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-japonica" translate>{{ 'events' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-japonica" translate>{{ 'calendar.events' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.evt" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
<div class="m-t row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-black" translate>{{ 'show_no_disponible' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-black" translate>{{ 'calendar.show_unavailables' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.dispo" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
|
@ -29,178 +29,25 @@
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-show="!ctrl.member && currentUser.role == 'admin' && eventsReserved.length == 0 && (!paidMachineSlots || paidMachineSlots.length == 0) && !slotToModify && !modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_one_or_more_slots_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="ctrl.member && !slotToModify && !modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="eventsReserved.length == 0 && (!paidMachineSlots || paidMachineSlots.length == 0)">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_one_or_more_slots_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="eventsReserved.length > 0">
|
||||
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'you_ve_just_selected_the_slot' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light" ng-repeat="machineSlot in eventsReserved">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(machineSlot.start | amDateFormat:'LLLL'), END_TIME:(machineSlot.end | amDateFormat:'LT') } }}</div>
|
||||
<div class="text-base">{{ 'cost_of_a_machine_hour' | translate }} <span ng-class="{'text-blue': !machineSlot.promo, 'red': machineSlot.promo}">{{machineSlot.price | currency}}</span></div>
|
||||
<div ng-show="currentUser.role == 'admin'" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'offer_this_slot' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="machineSlot.offered"
|
||||
id="offerSlot"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate}}"
|
||||
switch-off-text="{{ 'no' | translate}}"
|
||||
switch-animate="true"
|
||||
switch-readonly="{{machineSlot.isValid}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validMachineSlot(machineSlot)" ng-if="!machineSlot.isValid" translate>{{ 'confirm_this_slot' }}</button>
|
||||
</div>
|
||||
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeMachineSlot(machineSlot, $event)" ng-if="machineSlot.isValid" translate>{{ 'remove_this_slot' }}</a></div>
|
||||
</div>
|
||||
|
||||
<coupon show="machineSlotsValid() && (!plansAreShown || selectedPlan)" coupon="coupon.applied" total="totalNoCoupon" user-id="{{ctrl.member.id}}"></coupon>
|
||||
|
||||
<span ng-hide="fablabWithoutPlans">
|
||||
<div ng-if="machineSlotsValid() && !ctrl.member.subscribed_plan" ng-show="!plansAreShown">
|
||||
<p class="font-sbold text-base l-h-2x" translate>{{ 'to_benefit_from_attractive_prices' }}</p>
|
||||
<div><button class="btn btn-warning-full rounded btn-block text-xs" ng-click="showPlans()" translate>{{ 'view_our_subscriptions' }}</button></div>
|
||||
<p class="font-bold text-base text-u-c text-center m-b-xs" translate>{{ 'or' }}</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_ve_just_selected_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body m-b-md">
|
||||
<div class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</div>
|
||||
<div class="text-base">{{ 'cost_of_the_subscription' | translate }} <span class="text-blue">{{selectedPlan.amount | currency}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="eventsReserved.length > 0">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="payMachine()" ng-if="machineSlotsValid() && (!plansAreShown || selectedPlan)">{{ 'confirm_and_pay' | translate }} {{amountTotal | currency}}</button>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="paidMachineSlots">
|
||||
{{ 'you_have_settled_the_following_machine_hours' | translate }} <strong>{{machine.name}}</strong>:
|
||||
|
||||
<div class="well well-warning m-t-sm" ng-repeat="paidSlot in paidMachineSlots">
|
||||
<i class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(paidSlot.start | amDateFormat:'LLLL'), END_TIME:(paidSlot.end | amDateFormat:'LT') } }}</i>
|
||||
<div class="font-sbold">{{ 'cost_of_a_machine_hour' | translate }} {{paidSlot.machine.amount() | currency}}</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_have_settled_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</i>
|
||||
<div class="font-sbold">{{ 'cost_of_the_subscription' | translate }} {{selectedPlan.amount | currency}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t-md font-sbold">{{ 'total_' | translate }} {{amountTotal | currency}}</div>
|
||||
|
||||
<div class="alert alert-success" ng-if="ctrl.member.subscribed_plan">{{ 'thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
{{ 'your_invoice_will_be_available_soon_from_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'dashboard' }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="slotToModify || modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="slotToModify">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'i_want_to_change_the_following_reservation' }}</div>
|
||||
|
||||
<div class="panel panel-warning bg-yellow">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToModify.start | amDateFormat:'LLLL'), END_TIME:(slotToModify.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToModify($event)" translate>{{ 'cancel_my_modification' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_a_new_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info bg-info text-white" ng-if="slotToPlace">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToPlace.start | amDateFormat:'LLLL'), END_TIME:(slotToPlace.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToPlace($event)" translate>{{ 'cancel_my_selection' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="slotToPlace && slotToModify.tags.length > 0 && slotToPlace.tags.length > 0" ng-class="{'panel panel-danger bg-red': tagMissmatch()}">
|
||||
<div class="panel-body">
|
||||
<div id="fromTags">
|
||||
{{ 'tags_of_the_original_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in slotToModify.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
</div><br/>
|
||||
<div id="toTags">
|
||||
{{ 'tags_of_the_destination_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in slotToPlace.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="slotToModify && slotToPlace">
|
||||
<button class="btn btn-invalid btn-default btn-block p-l btn-lg text-u-c r-n text-base" ng-click="cancelModifyMachineSlot()" translate>{{ 'cancel' }}</button>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="modifyMachineSlot()" translate>{{ 'confirm_my_modification' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="modifiedSlots">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'your_booking_slot_was_successfully_moved_from_' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.oldReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.oldReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center font-bold m-b-sm text-u-c" translate>{{ 'to_date' }}</p>
|
||||
|
||||
<div class="panel panel-success bg-success bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.newReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.newReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<cart slot="selectedEvent"
|
||||
slot-selection-time="selectionTime"
|
||||
events="events"
|
||||
user="ctrl.member"
|
||||
mode-plans="plansAreShown"
|
||||
plan="selectedPlan"
|
||||
plan-selection-time="planSelectionTime"
|
||||
settings="settings"
|
||||
on-slot-added-to-cart="markSlotAsAdded"
|
||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||
on-slot-start-to-modify="markSlotAsModifying"
|
||||
on-slot-modify-success="modifyMachineSlot"
|
||||
on-slot-modify-cancel="cancelModifyMachineSlot"
|
||||
on-slot-modify-unselect="changeModifyMachineSlot"
|
||||
on-slot-cancel-success="slotCancelled"
|
||||
after-payment="afterPayment"
|
||||
reservable-id="{{machine.id}}"
|
||||
reservable-type="Machine"
|
||||
reservable-name="{{machine.name}}"></cart>
|
||||
|
||||
<uib-alert type="warning m">
|
||||
<p class="text-sm">
|
||||
|
167
app/assets/templates/shared/_cart.html.erb
Normal file
167
app/assets/templates/shared/_cart.html.erb
Normal file
@ -0,0 +1,167 @@
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="user && !events.modifiable && !events.moved">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'cart.summary' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="events.reserved.length == 0 && (!events.paid || events.paid.length == 0)">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag('fleche-left.png', class: 'fleche-left visible-lg') %>
|
||||
{{ 'cart.select_one_or_more_slots_in_the_calendar' | translate:{SINGLE:limitToOneSlot}:"messageformat" }}</p>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="events.reserved.length > 0">
|
||||
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'cart.you_ve_just_selected_the_slot' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light" ng-repeat="slot in events.reserved">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(slot.start | amDateFormat:'LLLL'), END_TIME:(slot.end | amDateFormat:'LT') } }}</div>
|
||||
<div class="text-base">{{ 'cart.cost_of_TYPE' | translate:{TYPE:reservableType}:"messageformat" }} <span ng-class="{'text-blue': !slot.promo, 'red': slot.promo}">{{slot.price | currency}}</span></div>
|
||||
<div ng-show="isAdmin()" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'cart.offer_this_slot' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="slot.offered"
|
||||
id="offerSlot"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate}}"
|
||||
switch-off-text="{{ 'no' | translate}}"
|
||||
switch-animate="true"
|
||||
switch-readonly="{{slot.isValid}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validateSlot(slot)" ng-if="!slot.isValid" translate>{{ 'cart.confirm_this_slot' }}</button>
|
||||
</div>
|
||||
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlot(slot, $index, $event)" ng-if="slot.isValid" translate>{{ 'cart.remove_this_slot' }}</a></div>
|
||||
</div>
|
||||
|
||||
<coupon show="isSlotsValid() && (!modePlans || selectedPlan)" coupon="coupon.applied" total="totalNoCoupon" user-id="{{user.id}}"></coupon>
|
||||
|
||||
<div ng-hide="fablabWithoutPlans">
|
||||
<div ng-if="isSlotsValid() && !user.subscribed_plan" ng-show="!modePlans">
|
||||
<p class="font-sbold text-base l-h-2x" translate>{{ 'cart.to_benefit_from_attractive_prices' }}</p>
|
||||
<div><button class="btn btn-warning-full rounded btn-block text-xs" ng-click="showPlans()" translate>{{ 'cart.view_our_subscriptions' }}</button></div>
|
||||
<p class="font-bold text-base text-u-c text-center m-b-xs" translate>{{ 'cart.or' }}</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'cart.you_ve_just_selected_a_' | translate }} <br> <span class="font-sbold" translate>{{ 'cart._subscription' }}</span> :</div>
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body m-b-md">
|
||||
<div class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</div>
|
||||
<div class="text-base">{{ 'cart.cost_of_the_subscription' | translate }} <span class="text-blue">{{selectedPlan.amount | currency}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="events.reserved.length > 0">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="payCart()" ng-if="isSlotsValid() && (!modePlans || selectedPlan)">{{ 'cart.confirm_and_pay' | translate }} {{amountTotal | currency}}</button>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="events.paid && events.paid.length > 0">
|
||||
{{ 'cart.you_have_settled_the_following_TYPE' | translate:{TYPE:reservableType}:"messageformat" }} <strong>{{reservableName}}</strong>:
|
||||
|
||||
<div class="well well-warning m-t-sm" ng-repeat="paidSlot in events.paid">
|
||||
<i class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(paidSlot.start | amDateFormat:'LLLL'), END_TIME:(paidSlot.end | amDateFormat:'LT') } }}</i>
|
||||
<div class="font-sbold">{{ 'cart.cost_of_TYPE' | translate:{TYPE:reservableType}:"messageformat" }} {{paidSlot.price | currency}}</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'cart.you_have_settled_a_' | translate }} <br> <span class="font-sbold" translate>{{ 'cart._subscription' }}</span> :</div>
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</i>
|
||||
<div class="font-sbold">{{ 'cart.cost_of_the_subscription' | translate }} {{selectedPlan.amount | currency}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t-md font-sbold">{{ 'cart.total_' | translate }} {{amountTotal | currency}}</div>
|
||||
|
||||
<div class="alert alert-success" ng-if="user.subscribed_plan">{{ 'cart.thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
{{ 'cart.your_invoice_will_be_available_soon_from_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'cart.dashboard' }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="events.modifiable || events.moved">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'cart.summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="events.modifiable">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'cart.i_want_to_change_the_following_reservation' }}</div>
|
||||
|
||||
<div class="panel panel-warning bg-yellow">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.modifiable.start | amDateFormat:'LLLL'), END_TIME:(events.modifiable.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="cancelModifySlot($event)" translate>{{ 'cart.cancel_my_modification' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag('fleche-left.png', class: 'fleche-left visible-lg') %>
|
||||
{{ 'cart.select_a_new_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info bg-info text-white" ng-if="events.placable">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.placable.start | amDateFormat:'LLLL'), END_TIME:(events.placable.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToPlace($event)" translate>{{ 'cart.cancel_my_selection' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="events.placable && (events.modifiable.tags.length > 0 || events.placable.tags.length > 0)" ng-class="{'panel panel-danger bg-red': tagMissmatch()}">
|
||||
<div class="panel-body">
|
||||
<div id="fromTags">
|
||||
{{ 'cart.tags_of_the_original_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in events.modifiable.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
<span ng-show="events.modifiable.tags.length == 0">
|
||||
<span class='label label-warning text-white' title="{{ 'cart.none' | translate }}" translate>{{ 'cart.none' }}</span>
|
||||
</span>
|
||||
</div><br/>
|
||||
<div id="toTags">
|
||||
{{ 'cart.tags_of_the_destination_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in events.placable.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
<span ng-show="events.placable.tags.length == 0">
|
||||
<span class='label label-warning text-white' title="{{ 'cart.none' | translate }}" translate>{{ 'cart.none' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="events.modifiable && events.placable">
|
||||
<button class="btn btn-invalid btn-default btn-block p-l btn-lg text-u-c r-n text-base" ng-click="cancelModifySlot()" translate>{{ 'cancel' }}</button>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="modifySlot()" translate>{{ 'cart.confirm_my_modification' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="events.moved">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'cart.your_booking_slot_was_successfully_moved_from_' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.moved.oldSlot.start | amDateFormat:'LLLL'), END_TIME:(events.moved.oldSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center font-bold m-b-sm text-u-c" translate>{{ 'cart.to_date' }}</p>
|
||||
|
||||
<div class="panel panel-success bg-success bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.moved.newSlot.start | amDateFormat:'LLLL'), END_TIME:(events.moved.newSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
109
app/assets/templates/spaces/_form.html
Normal file
109
app/assets/templates/spaces/_form.html
Normal file
@ -0,0 +1,109 @@
|
||||
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
|
||||
|
||||
<div class="form-group m-b-lg" ng-class="{'has-error': spaceForm['space[name]'].$dirty && spaceForm['space[name]'].$invalid}">
|
||||
<label for="space_name" class="col-sm-2 control-label">{{ 'space.name' | translate }} *</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="space.name"
|
||||
type="text"
|
||||
name="space[name]"
|
||||
class="form-control"
|
||||
id="space_name"
|
||||
placeholder="{{'space.name' | translate}}"
|
||||
required>
|
||||
<span class="help-block" ng-show="spaceForm['space[name]'].$dirty && spaceForm['space[name]'].$error.required" translate>{{ 'space.name_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-lg">
|
||||
<label for="space_image" class="col-sm-2 control-label">{{ 'space.illustration' | translate }} *</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(space.space_image)">
|
||||
<div class="fileinput-new thumbnail" style="width: 334px; height: 250px;">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!space.space_image">
|
||||
</div>
|
||||
<div class="fileinput-preview fileinput-exists thumbnail" style="max-width: 334px;">
|
||||
<img ng-src="{{ space.space_image }}" alt="" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="btn btn-default btn-file">
|
||||
<span class="fileinput-new">{{ 'space.add_an_illustration' | translate }} <i class="fa fa-upload fa-fw"></i></span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span>
|
||||
<input type="file"
|
||||
id="space_image"
|
||||
ng-model="space.space_image"
|
||||
name="space[space_image_attributes][attachment]"
|
||||
accept="image/*"
|
||||
required
|
||||
bs-jasny-fileinput>
|
||||
</span>
|
||||
<a href="#" class="btn btn-danger fileinput-exists" data-dismiss="fileinput" translate>{{ 'delete' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group m-b-xl" ng-class="{'has-error': spaceForm['space[default_places]'].$dirty && spaceForm['space[default_places]'].$invalid}">
|
||||
<label for="default_places" class="col-sm-2 control-label">{{ 'space.default_places' | translate }} *</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number"
|
||||
name="space[default_places]"
|
||||
ng-model="space.default_places"
|
||||
id="default_places"
|
||||
class="form-control"
|
||||
required>
|
||||
<span class="help-block" ng-show="spaceForm['space[default_places]'].$dirty && spaceForm['space[default_places]'].$error.required" translate>{{ 'space.default_places_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label for="space_description" class="col-sm-2 control-label" translate>{{ 'space.description' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden"
|
||||
name="space[description]"
|
||||
ng-value="space.description" />
|
||||
<summernote ng-model="space.description"
|
||||
id="space_description"
|
||||
placeholder=""
|
||||
config="summernoteOpts"
|
||||
name="space[description]">
|
||||
</summernote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label for="space_characteristics" class="col-sm-2 control-label" translate>{{ 'space.characteristics' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden"
|
||||
name="space[characteristics]"
|
||||
ng-value="space.characteristics" />
|
||||
<summernote ng-model="space.characteristics"
|
||||
id="space_characteristics"
|
||||
placeholder=""
|
||||
config="summernoteOpts"
|
||||
name="space[characteristics]">
|
||||
</summernote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label class="col-sm-2 control-label" translate>{{ 'space.attached_files_(pdf)' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div ng-repeat="file in space.space_files_attributes" ng-show="!file._destroy">
|
||||
<input type="hidden" ng-model="file.id" name="space[space_files_attributes][][id]" ng-value="file.id" />
|
||||
<input type="hidden" ng-model="file._destroy" name="space[space_files_attributes][][_destroy]" ng-value="file._destroy"/>
|
||||
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(file.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
|
||||
</div>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'space.attach_a_file' }}</span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span><input type="file" name="space[space_files_attributes][][attachment]" accept=".pdf"></span>
|
||||
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<a class="btn btn-default" ng-click="addFile()" role="button"> {{ 'space.add_an_attachment' | translate }} <i class="fa fa-file-o fa-fw"></i></a>
|
||||
</div>
|
||||
</div>
|
50
app/assets/templates/spaces/edit.html.erb
Normal file
50
app/assets/templates/spaces/edit.html.erb
Normal file
@ -0,0 +1,50 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-md-1 hidden-xs">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1 translate translate-values="{NAME: space.name}">{{ 'space_edit.edit_the_space_NAME' }}</h1>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter" >
|
||||
|
||||
<div class="col-md-9 b-r nopadding">
|
||||
<form role="form"
|
||||
name="spaceForm"
|
||||
class="form-horizontal"
|
||||
action="{{ actionUrl }}"
|
||||
ng-upload="submited(content)"
|
||||
upload-options-enable-rails-csrf="true"
|
||||
unsaved-warning-form
|
||||
novalidate>
|
||||
|
||||
<input name="_method" type="hidden" ng-value="method">
|
||||
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'spaces/_form.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit"
|
||||
value="{{ 'space_edit.validate_the_changes' | translate }}"
|
||||
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
|
||||
ng-disabled="spaceForm.$invalid"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3"/>
|
||||
</div>
|
63
app/assets/templates/spaces/index.html.erb
Normal file
63
app/assets/templates/spaces/index.html.erb
Normal file
@ -0,0 +1,63 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'the_spaces' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized('admin')">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs" ui-sref="app.admin.space_new" role="button" translate>{{ 'add_a_space' }}</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="m-lg">
|
||||
|
||||
<div class="row" ng-repeat="space in (spaces.length/3 | array)">
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="space in spaces.slice(3*$index, 3*$index + 3)">
|
||||
|
||||
|
||||
<div class="widget panel panel-default">
|
||||
<div class="panel-heading picture" ng-if="!space.space_image" ng-click="showMachine(space)">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder class="img-responsive">
|
||||
</div>
|
||||
<div class="panel-heading picture" style="background-image:url({{space.space_image}})" ng-if="space.space_image" ng-click="showMachine(space)">
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h1 class="text-center m-b">{{space.name}}</h1>
|
||||
</div>
|
||||
<div class="panel-footer no-padder">
|
||||
|
||||
<div class="text-center clearfix">
|
||||
<div class="col-sm-6 b-r no-padder">
|
||||
<div class="btn btn-default btn-block no-b padder-v red" ng-click="reserveSpace(space, $event)">
|
||||
<i class="fa fa-bookmark"></i> {{ 'book' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 no-padder">
|
||||
<div class="btn btn-default btn-block padder-v no-b red" ng-click="showSpace(space)">
|
||||
<i class="fa fa-eye"></i> {{ 'consult' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</section>
|
57
app/assets/templates/spaces/new.html.erb
Normal file
57
app/assets/templates/spaces/new.html.erb
Normal file
@ -0,0 +1,57 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-md-1 hidden-xs">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'space_new.add_a_new_space' }}</h1>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter" >
|
||||
|
||||
<div class="col-md-9 b-r nopadding">
|
||||
|
||||
<div class="m-lg alert alert-warning" role="alert">
|
||||
{{ 'space_new.watch_out_when_creating_a_new_space_its_prices_are_initialized_at_0_for_all_subscriptions' | translate}}
|
||||
{{ 'space_new.consider_changing_its_prices_before_creating_any_reservation_slot' | translate }}
|
||||
</div>
|
||||
|
||||
<form role="form"
|
||||
name="spaceForm"
|
||||
class="form-horizontal"
|
||||
action="{{ actionUrl }}"
|
||||
ng-upload="submited(content)"
|
||||
upload-options-enable-rails-csrf="true"
|
||||
unsaved-warning-form
|
||||
novalidate>
|
||||
|
||||
<input name="_method" type="hidden" ng-value="method">
|
||||
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'spaces/_form.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit"
|
||||
value="{{ 'space_new.add_this_space' | translate }}"
|
||||
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
|
||||
ng-disabled="spaceForm.$invalid"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-3"/>
|
||||
</div>
|
59
app/assets/templates/spaces/reserve.html.erb
Normal file
59
app/assets/templates/spaces/reserve.html.erb
Normal file
@ -0,0 +1,59 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate translate-values="{NAME:space.name}">{{ 'space_reserve.planning_of_space_NAME' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter training-reserve">
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="calendar" class="wrapper-lg" ng-show="!plansAreShown"></div>
|
||||
<ng-include ng-if="!fablabWithoutPlans" src="'<%= asset_path 'plans/_plan.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
<cart slot="selectedEvent"
|
||||
slot-selection-time="selectionTime"
|
||||
events="events"
|
||||
user="ctrl.member"
|
||||
mode-plans="plansAreShown"
|
||||
plan="selectedPlan"
|
||||
plan-selection-time="planSelectionTime"
|
||||
settings="settings"
|
||||
on-slot-added-to-cart="markSlotAsAdded"
|
||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||
on-slot-start-to-modify="markSlotAsModifying"
|
||||
on-slot-modify-success="modifyTrainingSlot"
|
||||
on-slot-modify-cancel="cancelModifyTrainingSlot"
|
||||
on-slot-modify-unselect="changeModifyTrainingSlot"
|
||||
on-slot-cancel-success="slotCancelled"
|
||||
after-payment="afterPayment"
|
||||
reservable-id="{{space.id}}"
|
||||
reservable-type="Space"
|
||||
reservable-name="{{space.name}}"></cart>
|
||||
|
||||
|
||||
<uib-alert type="warning m" ng-show="spaceExplicationsAlert">
|
||||
<p class="text-sm">
|
||||
<i class="fa fa-warning"></i>
|
||||
<span ng-bind-html="spaceExplicationsAlert"></span>
|
||||
</p>
|
||||
</uib-alert>
|
||||
|
||||
</div>
|
||||
</div>
|
85
app/assets/templates/spaces/show.html
Normal file
85
app/assets/templates/spaces/show.html
Normal file
@ -0,0 +1,85 @@
|
||||
<div>
|
||||
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1>{{ space.name }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-4 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper">
|
||||
<a ng-click="reserveSpace($event)" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs" ng-if="!isAuthorized('admin')" translate>{{ 'space_show.book_this_space' }}</a>
|
||||
|
||||
<a ui-sref="app.admin.space_edit({id:space.slug})" ng-if="isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs"><i class="fa fa-edit"></i> {{ 'edit' | translate }}</a>
|
||||
<a ng-click="deleteSpace($event)" ng-if="isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row no-gutter">
|
||||
<div class="col-sm-12 col-md-12 col-lg-8 b-r-lg">
|
||||
|
||||
<div class="article wrapper-lg">
|
||||
|
||||
<div class="article-thumbnail" ng-if="space.space_image">
|
||||
<img ng-src="{{space.space_image}}" alt="{{space.name}}" class="img-responsive">
|
||||
</div>
|
||||
|
||||
<p class="intro" ng-bind-html="space.description | toTrusted"></p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-4">
|
||||
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'space_show.characteristics' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg wrapper">
|
||||
<h3></h3>
|
||||
<p ng-bind-html="space.characteristics | toTrusted"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="widget panel b-a m" ng-if="space.space_files_attributes">
|
||||
<div class="panel-heading b-b">
|
||||
<span class="badge bg-warning pull-right">{{space.space_files_attributes.length}}</span>
|
||||
<h3 translate>{{ 'space_show.files_to_download' }}</h3>
|
||||
</div>
|
||||
|
||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||
<li ng-repeat="file in space.space_files_attributes" class="list-group-item no-b clearfix">
|
||||
<a target="_blank" ng-href="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="widget panel b-a m" ng-if="space.space_projects">
|
||||
<div class="panel-heading b-b">
|
||||
<h3 translate>{{ 'space_show.projects_using_the_space' }}</h3>
|
||||
</div>
|
||||
|
||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||
<li ng-repeat="project in space.space_projects" class="list-group-item no-b clearfix">
|
||||
<a ui-sref="app.public.projects_show({id:project.slug})"><i class="fa"> </i> {{project.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
@ -7,15 +7,15 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 ng-hide="training" translate>{{ 'trainings_planning' }}</h1>
|
||||
<h1 ng-show="training"><span translate>{{ 'planning_of' }}</span> {{training.name}}</h1>
|
||||
<h1 ng-show="mode == 'all'" translate>{{ 'trainings_planning' }}</h1>
|
||||
<h1 ng-hide="mode == 'all'"><span translate>{{ 'planning_of' }}</span> {{training.name}}</h1>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs"
|
||||
ui-sref="app.logged.trainings_reserve({id:'all'})"
|
||||
ng-show="training"
|
||||
ng-hide="mode == 'all'"
|
||||
role="button"
|
||||
translate>{{ 'all_trainings' }}</a>
|
||||
</section>
|
||||
@ -33,165 +33,34 @@
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div class="text-center m-t">
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="ctrl.member && !slotToModify && !modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="!selectedTraining && !paidTraining">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_a_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="selectedTraining">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'you_ve_just_selected_the_slot' }}</div>
|
||||
<cart slot="selectedEvent"
|
||||
slot-selection-time="selectionTime"
|
||||
events="events"
|
||||
user="ctrl.member"
|
||||
mode-plans="plansAreShown"
|
||||
plan="selectedPlan"
|
||||
plan-selection-time="planSelectionTime"
|
||||
settings="settings"
|
||||
on-slot-added-to-cart="markSlotAsAdded"
|
||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||
on-slot-start-to-modify="markSlotAsModifying"
|
||||
on-slot-modify-success="modifyTrainingSlot"
|
||||
on-slot-modify-cancel="cancelModifyTrainingSlot"
|
||||
on-slot-modify-unselect="changeModifyTrainingSlot"
|
||||
on-slot-cancel-success="slotCancelled"
|
||||
after-payment="afterPayment"
|
||||
reservable-id="{{training.id}}"
|
||||
reservable-type="Training"
|
||||
reservable-name="{{training.name}}"
|
||||
limit-to-one-slot="true"></cart>
|
||||
|
||||
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(selectedTraining.start | amDateFormat:'LLLL'), END_TIME:(selectedTraining.end | amDateFormat:'LT')} }}</div>
|
||||
<div class="text-base">{{ 'training_cost_' | translate }} <span ng-class="{'text-blue': selectedTraining.training.amount == selectedTrainingAmount, 'red': selectedTraining.training.amount != selectedTrainingAmount}">{{selectedTrainingAmount | currency}}</span></div>
|
||||
<div ng-show="currentUser.role == 'admin'" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'offer_this_training' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="selectedTraining.offered"
|
||||
id="offerSlot"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"
|
||||
switch-readonly="{{trainingIsValid}}"
|
||||
ng-change="updatePrices()"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer no-padder">
|
||||
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validTraining()" ng-if="!trainingIsValid" translate>{{ 'confirm_this_slot' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clear">
|
||||
<a class="pull-right m-t-xs text-u-l" href="#" ng-click="removeTraining($event)" ng-if="trainingIsValid" translate>{{ 'remove_this_slot' }}</a>
|
||||
</div>
|
||||
|
||||
<coupon show="trainingIsValid && (!plansIsShow || selectedPlan)" coupon="coupon.applied" user-id="{{ctrl.member.id}}" total="totalNoCoupon"></coupon>
|
||||
|
||||
<span ng-hide="fablabWithoutPlans">
|
||||
<div ng-if="trainingIsValid && !ctrl.member.subscribed_plan" ng-show="!plansIsShow">
|
||||
<p class="font-sbold text-base l-h-2x" translate>{{ 'to_benefit_from_attractive_prices_and_a_free_training' }}</p>
|
||||
<div><button class="btn btn-warning-full rounded btn-block text-xs" ng-click="showPlans()" translate>{{ 'view_our_subscriptions' }}</button></div>
|
||||
<p class="font-bold text-base text-u-c text-center m-b-xs">ou</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_ve_just_selected_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body m-b-md">
|
||||
<div class="font-sbold text-u-c">{{ selectedPlan | humanReadablePlanName }}</div>
|
||||
<div class="text-base">{{ 'subscription_cost' | translate }} <span class="text-blue">{{selectedPlan.amount | currency}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="selectedTraining">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="payTraining()" ng-if="trainingIsValid && (!plansIsShow || selectedPlan)">{{ 'confirm_and_pay' | translate }} {{amountTotal | currency}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="paidTraining">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_have_settled_the_training' | translate }} <br> <span class="font-sbold">{{paidTraining.training.name}}</span> :
|
||||
</div>
|
||||
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(paidTraining.start | amDateFormat:'LLLL'), END_TIME:(paidTraining.end | amDateFormat:'LT') } }} </i>
|
||||
<div class="font-sbold">{{ 'training_cost_' | translate }} {{paidTraining.training.amount | currency}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_have_settled_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</i>
|
||||
<div class="font-sbold">{{ 'subscription_cost' | translate }} {{selectedPlan.amount | currency}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t-md font-sbold">{{ 'total_' | translate }} {{amountTotal | currency}}</div>
|
||||
<div class="alert alert-success" ng-if="ctrl.member.subscribed_plan">{{ 'thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
{{ 'your_invoice_will_be_available_soon_from_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'dashboard' }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="slotToModify || modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="slotToModify">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'i_want_to_change_the_following_reservation' }}</div>
|
||||
|
||||
<div class="panel panel-warning bg-yellow">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToModify.start | amDateFormat:'LLLL'), END_TIME:(slotToModify.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToModify($event)" translate>{{ 'cancel_my_modification' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_a_new_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info bg-info text-white" ng-if="slotToPlace">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToPlace.start | amDateFormat:'LLLL'), END_TIME:(slotToPlace.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToPlace($event)" translate>{{ 'cancel_my_selection' }}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="slotToModify && slotToPlace">
|
||||
<button class="btn btn-invalid btn-default btn-block p-l btn-lg text-u-c r-n text-base" ng-click="cancelModifyMachineSlot()" translate>{{ 'cancel' }}</button>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="modifyTrainingSlot()" translate>{{ 'confirm_my_modification' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="modifiedSlots">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'your_booking_slot_was_successfully_moved_from_' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.oldReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.oldReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center font-bold m-b-sm text-u-c" translate>{{ 'to_date' }}</p>
|
||||
|
||||
<div class="panel panel-success bg-success bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.newReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.newReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<uib-alert type="info m">
|
||||
<p class="text-sm font-bold">
|
||||
<i class="fa fa-lightbulb-o"></i>
|
||||
|
@ -1,26 +1,36 @@
|
||||
class API::AvailabilitiesController < API::ApiController
|
||||
include FablabConfiguration
|
||||
|
||||
before_action :authenticate_user!, except: [:public]
|
||||
before_action :set_availability, only: [:show, :update, :destroy, :reservations]
|
||||
respond_to :json
|
||||
|
||||
## machine availabilities are divided in multiple slots of 60 minutes
|
||||
## machine/spaces availabilities are divided in multiple slots of 60 minutes
|
||||
SLOT_DURATION = 60
|
||||
|
||||
def index
|
||||
authorize Availability
|
||||
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
|
||||
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
|
||||
@availabilities = Availability.includes(:machines,:tags,:trainings).where.not(available_type: 'event')
|
||||
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces).where.not(available_type: 'event')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
|
||||
if fablab_spaces_deactivated?
|
||||
@availabilities = @availabilities.where.not(available_type: 'space')
|
||||
end
|
||||
end
|
||||
|
||||
def public
|
||||
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
|
||||
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
|
||||
@reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
|
||||
|
||||
# request for 1 single day
|
||||
if in_same_day(start_date, end_date)
|
||||
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots).where(available_type: ['training', 'event'])
|
||||
# trainings, events
|
||||
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots).where(available_type: %w(training event))
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
# machines
|
||||
@machine_availabilities = Availability.includes(:tags, :machines).where(available_type: 'machines')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
@machine_slots = []
|
||||
@ -35,14 +45,38 @@ class API::AvailabilitiesController < API::ApiController
|
||||
end
|
||||
end
|
||||
end
|
||||
@availabilities = [].concat(@training_and_event_availabilities).concat(@machine_slots)
|
||||
|
||||
# spaces
|
||||
@space_availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
|
||||
if params[:s]
|
||||
@space_availabilities.where(available_id: params[:s])
|
||||
end
|
||||
|
||||
@space_slots = []
|
||||
@space_availabilities.each do |a|
|
||||
space = a.spaces.first
|
||||
((a.end_at - a.start_at)/SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*SLOT_DURATION).minutes, end_at: a.start_at + (i*SLOT_DURATION).minutes + SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: space, title: space.name)
|
||||
slot = verify_space_is_reserved(slot, @reservations, current_user, '')
|
||||
@space_slots << slot
|
||||
end
|
||||
end
|
||||
end
|
||||
@availabilities = [].concat(@training_and_event_availabilities).concat(@machine_slots).concat(@space_slots)
|
||||
|
||||
# request for many days (week or month)
|
||||
else
|
||||
|
||||
@availabilities = Availability.includes(:tags, :machines, :trainings, :event, :slots)
|
||||
@availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
@availabilities.each do |a|
|
||||
if a.available_type != 'machines'
|
||||
a = verify_training_event_is_reserved(a, @reservations)
|
||||
if a.available_type == 'training' or a.available_type == 'event'
|
||||
a = verify_training_event_is_reserved(a, @reservations, current_user)
|
||||
elsif a.available_type == 'space'
|
||||
a.is_reserved = is_reserved_availability(a, current_user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -90,7 +124,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
@user = current_user
|
||||
end
|
||||
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
|
||||
@machine = Machine.find(params[:machine_id])
|
||||
@machine = Machine.friendly.find(params[:machine_id])
|
||||
@slots = []
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
|
||||
if @user.is_admin?
|
||||
@ -135,7 +169,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
|
||||
# who made the request?
|
||||
# 1) an admin (he can see all future availabilities)
|
||||
if @user.is_admin?
|
||||
if current_user.is_admin?
|
||||
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines]).where('availabilities.start_at > ?', Time.now)
|
||||
# 2) an user (he cannot see availabilities further than 1 (or 3) months)
|
||||
else
|
||||
@ -146,13 +180,46 @@ class API::AvailabilitiesController < API::ApiController
|
||||
|
||||
# finally, we merge the availabilities with the reservations
|
||||
@availabilities.each do |a|
|
||||
a = verify_training_event_is_reserved(a, @reservations)
|
||||
a = verify_training_event_is_reserved(a, @reservations, @user)
|
||||
end
|
||||
end
|
||||
|
||||
def spaces
|
||||
if params[:member_id]
|
||||
@user = User.find(params[:member_id])
|
||||
else
|
||||
@user = current_user
|
||||
end
|
||||
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
|
||||
@space = Space.friendly.find(params[:space_id])
|
||||
@slots = []
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
|
||||
if @user.is_admin?
|
||||
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND available_type = 'space'", Time.now)
|
||||
else
|
||||
end_at = 1.month.since
|
||||
end_at = 3.months.since if is_subscription_year(@user)
|
||||
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end
|
||||
@availabilities.each do |a|
|
||||
((a.end_at - a.start_at)/SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*SLOT_DURATION).minutes, end_at: a.start_at + (i*SLOT_DURATION).minutes + SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: @space, title: '')
|
||||
slot = verify_space_is_reserved(slot, @reservations, @user, @current_user_role)
|
||||
@slots << slot
|
||||
end
|
||||
end
|
||||
end
|
||||
@slots.each do |s|
|
||||
if s.is_complete? and not s.is_reserved
|
||||
s.title = t('availabilities.not_available')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def reservations
|
||||
authorize Availability
|
||||
@reservation_slots = @availability.slots.includes(reservation: [user: [:profile]]).order('slots.start_at ASC')
|
||||
@reservation_slots = @availability.slots.includes(reservations: [user: [:profile]]).order('slots.start_at ASC')
|
||||
end
|
||||
|
||||
private
|
||||
@ -161,10 +228,20 @@ class API::AvailabilitiesController < API::ApiController
|
||||
end
|
||||
|
||||
def availability_params
|
||||
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, machine_ids: [], training_ids: [], tag_ids: [],
|
||||
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, machine_ids: [], training_ids: [], space_ids: [], tag_ids: [],
|
||||
:machines_attributes => [:id, :_destroy])
|
||||
end
|
||||
|
||||
def is_reserved_availability(availability, user_id)
|
||||
reserved_slots = []
|
||||
availability.slots.each do |s|
|
||||
if s.canceled_at.nil?
|
||||
reserved_slots << s
|
||||
end
|
||||
end
|
||||
reserved_slots.map(&:reservations).flatten.map(&:user_id).include? user_id
|
||||
end
|
||||
|
||||
def is_reserved(start_at, reservations)
|
||||
is_reserved = false
|
||||
reservations.each do |r|
|
||||
@ -184,7 +261,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
slot.is_reserved = true
|
||||
slot.title = "#{slot.machine.name} - #{t('availabilities.not_available')}"
|
||||
slot.can_modify = true if user_role === 'admin'
|
||||
slot.reservation = r
|
||||
slot.reservations.push r
|
||||
end
|
||||
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
|
||||
slot.title = "#{slot.machine.name} - #{t('availabilities.i_ve_reserved')}"
|
||||
@ -197,8 +274,27 @@ class API::AvailabilitiesController < API::ApiController
|
||||
slot
|
||||
end
|
||||
|
||||
def verify_training_event_is_reserved(availability, reservations)
|
||||
user = current_user
|
||||
def verify_space_is_reserved(slot, reservations, user, user_role)
|
||||
reservations.each do |r|
|
||||
r.slots.each do |s|
|
||||
if slot.space.id == r.reservable_id
|
||||
if s.start_at == slot.start_at and s.canceled_at == nil
|
||||
slot.can_modify = true if user_role === 'admin'
|
||||
slot.reservations.push r
|
||||
end
|
||||
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
|
||||
slot.id = s.id
|
||||
slot.title = t('availabilities.i_ve_reserved')
|
||||
slot.can_modify = true
|
||||
slot.is_reserved = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
slot
|
||||
end
|
||||
|
||||
def verify_training_event_is_reserved(availability, reservations, user)
|
||||
reservations.each do |r|
|
||||
r.slots.each do |s|
|
||||
if ((availability.available_type == 'training' and availability.trainings.first.id == r.reservable_id) or (availability.available_type == 'event' and availability.event.id == r.reservable_id)) and s.start_at == availability.start_at and s.canceled_at == nil
|
||||
@ -239,6 +335,12 @@ class API::AvailabilitiesController < API::ApiController
|
||||
availabilities_filtered << a
|
||||
end
|
||||
end
|
||||
# space
|
||||
if params[:s] and a.available_type == 'space'
|
||||
if params[:s].include?(a.spaces.first.id.to_s)
|
||||
availabilities_filtered << a
|
||||
end
|
||||
end
|
||||
# machines
|
||||
if params[:m] and a.available_type == 'machines'
|
||||
if (params[:m].map(&:to_i) & a.machine_ids).any?
|
||||
|
@ -44,7 +44,7 @@ class API::PricesController < API::ApiController
|
||||
@amount = {elements: nil, total: 0, before_coupon: 0}
|
||||
else
|
||||
_reservable = _price_params[:reservable_type].constantize.find(_price_params[:reservable_id])
|
||||
@amount = Price.compute(current_user.is_admin?, _user, _reservable, _price_params[:slots_attributes], _price_params[:plan_id], _price_params[:nb_reserve_places], _price_params[:tickets_attributes], coupon_params[:coupon_code])
|
||||
@amount = Price.compute(current_user.is_admin?, _user, _reservable, _price_params[:slots_attributes] || [], _price_params[:plan_id], _price_params[:nb_reserve_places], _price_params[:tickets_attributes], coupon_params[:coupon_code])
|
||||
end
|
||||
|
||||
|
||||
|
49
app/controllers/api/spaces_controller.rb
Normal file
49
app/controllers/api/spaces_controller.rb
Normal file
@ -0,0 +1,49 @@
|
||||
class API::SpacesController < API::ApiController
|
||||
before_action :authenticate_user!, except: [:index, :show]
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@spaces = Space.includes(:space_image)
|
||||
end
|
||||
|
||||
def show
|
||||
@space = Space.includes(:space_files, :projects).friendly.find(params[:id])
|
||||
end
|
||||
|
||||
def create
|
||||
authorize Space
|
||||
@space = Space.new(space_params)
|
||||
if @space.save
|
||||
render :show, status: :created, location: @space
|
||||
else
|
||||
render json: @space.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize Space
|
||||
@space = get_space
|
||||
if @space.update(space_params)
|
||||
render :show, status: :ok, location: @space
|
||||
else
|
||||
render json: @space.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@space = get_space
|
||||
authorize @space
|
||||
@space.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
def get_space
|
||||
Space.friendly.find(params[:id])
|
||||
end
|
||||
|
||||
def space_params
|
||||
params.require(:space).permit(:name, :description, :characteristics, :default_places, space_image_attributes: [:attachment],
|
||||
space_files_attributes: [:id, :attachment, :_destroy])
|
||||
end
|
||||
end
|
@ -6,7 +6,7 @@ class API::StatisticsController < API::ApiController
|
||||
@statistics = StatisticIndex.all
|
||||
end
|
||||
|
||||
%w(account event machine project subscription training user).each do |path|
|
||||
%w(account event machine project subscription training user space).each do |path|
|
||||
class_eval %{
|
||||
def #{path}
|
||||
authorize :statistic, :#{path}?
|
||||
|
@ -2,4 +2,8 @@ module FablabConfiguration
|
||||
def fablab_plans_deactivated?
|
||||
Rails.application.secrets.fablab_without_plans
|
||||
end
|
||||
|
||||
def fablab_spaces_deactivated?
|
||||
Rails.application.secrets.fablab_without_spaces
|
||||
end
|
||||
end
|
||||
|
@ -1,23 +1,41 @@
|
||||
module AvailabilityHelper
|
||||
MACHINE_COLOR = '#e4cd78'
|
||||
TRAINING_COLOR = '#bd7ae9'
|
||||
SPACE_COLOR = '#3fc7ff'
|
||||
EVENT_COLOR = '#dd7e6b'
|
||||
IS_RESERVED_BY_CURRENT_USER = '#b2e774'
|
||||
MACHINE_IS_RESERVED_BY_USER = '#1d98ec'
|
||||
IS_COMPLETED = '#eeeeee'
|
||||
|
||||
def availability_border_color(availability)
|
||||
if availability.available_type == 'machines'
|
||||
MACHINE_COLOR
|
||||
elsif availability.available_type == 'training'
|
||||
TRAINING_COLOR
|
||||
else
|
||||
EVENT_COLOR
|
||||
case availability.available_type
|
||||
when 'machines'
|
||||
MACHINE_COLOR
|
||||
when 'training'
|
||||
TRAINING_COLOR
|
||||
when 'space'
|
||||
SPACE_COLOR
|
||||
else
|
||||
EVENT_COLOR
|
||||
end
|
||||
end
|
||||
|
||||
def machines_slot_border_color(slot)
|
||||
slot.is_reserved ? (slot.is_reserved_by_current_user ? IS_RESERVED_BY_CURRENT_USER : IS_COMPLETED) : MACHINE_COLOR
|
||||
if slot.is_reserved
|
||||
slot.is_reserved_by_current_user ? IS_RESERVED_BY_CURRENT_USER : IS_COMPLETED
|
||||
else
|
||||
MACHINE_COLOR
|
||||
end
|
||||
end
|
||||
|
||||
def space_slot_border_color(slot)
|
||||
if slot.is_reserved
|
||||
IS_RESERVED_BY_CURRENT_USER
|
||||
elsif slot.is_complete?
|
||||
IS_COMPLETED
|
||||
else
|
||||
SPACE_COLOR
|
||||
end
|
||||
end
|
||||
|
||||
def trainings_events_border_color(availability)
|
||||
@ -26,10 +44,15 @@ module AvailabilityHelper
|
||||
elsif availability.is_completed
|
||||
IS_COMPLETED
|
||||
else
|
||||
if availability.available_type == 'training'
|
||||
TRAINING_COLOR
|
||||
else
|
||||
EVENT_COLOR
|
||||
case availability.available_type
|
||||
when 'training'
|
||||
TRAINING_COLOR
|
||||
when 'event'
|
||||
EVENT_COLOR
|
||||
when 'space'
|
||||
SPACE_COLOR
|
||||
else
|
||||
'#000'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,8 @@
|
||||
class Availability < ActiveRecord::Base
|
||||
|
||||
## machine/spaces availabilities are divided in multiple slots of 60 minutes
|
||||
SLOT_DURATION = 60
|
||||
|
||||
# elastic initialisations
|
||||
include Elasticsearch::Model
|
||||
index_name 'fablab'
|
||||
@ -12,6 +15,9 @@ class Availability < ActiveRecord::Base
|
||||
has_many :trainings_availabilities, dependent: :destroy
|
||||
has_many :trainings, through: :trainings_availabilities
|
||||
|
||||
has_many :spaces_availabilities, dependent: :destroy
|
||||
has_many :spaces, through: :spaces_availabilities
|
||||
|
||||
has_many :slots
|
||||
has_many :reservations, through: :slots
|
||||
|
||||
@ -23,6 +29,7 @@ class Availability < ActiveRecord::Base
|
||||
|
||||
scope :machines, -> { where(available_type: 'machines') }
|
||||
scope :trainings, -> { includes(:trainings).where(available_type: 'training') }
|
||||
scope :spaces, -> { includes(:spaces).where(available_type: 'space') }
|
||||
|
||||
attr_accessor :is_reserved, :slot_id, :can_modify
|
||||
|
||||
@ -43,10 +50,18 @@ class Availability < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def safe_destroy
|
||||
if available_type == 'machines'
|
||||
reservations = Reservation.where(reservable_type: 'Machine', reservable_id: machine_ids).joins(:slots).where('slots.availability_id = ?', id)
|
||||
else
|
||||
reservations = Reservation.where(reservable_type: 'Training', reservable_id: training_ids).joins(:slots).where('slots.availability_id = ?', id)
|
||||
case available_type
|
||||
when 'machines'
|
||||
reservations = Reservation.where(reservable_type: 'Machine', reservable_id: machine_ids).joins(:slots).where('slots.availability_id = ?', id)
|
||||
when 'training'
|
||||
reservations = Reservation.where(reservable_type: 'Training', reservable_id: training_ids).joins(:slots).where('slots.availability_id = ?', id)
|
||||
when 'space'
|
||||
reservations = Reservation.where(reservable_type: 'Space', reservable_id: space_ids).joins(:slots).where('slots.availability_id = ?', id)
|
||||
when 'event'
|
||||
reservations = Reservation.where(reservable_type: 'Event', reservable_id: event&.id).joins(:slots).where('slots.availability_id = ?', id)
|
||||
else
|
||||
STDERR.puts "[safe_destroy] Availability with unknown type #{available_type}"
|
||||
reservations = []
|
||||
end
|
||||
if reservations.size == 0
|
||||
# this update may not call any rails callbacks, that's why we use direct SQL update
|
||||
@ -57,16 +72,29 @@ class Availability < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
## compute the total number of places over the whole space availability
|
||||
def available_space_places
|
||||
if available_type === 'space'
|
||||
((end_at - start_at)/SLOT_DURATION.minutes).to_i * nb_total_places
|
||||
end
|
||||
end
|
||||
|
||||
def title(filter = {})
|
||||
if available_type == 'machines'
|
||||
if filter[:machine_ids]
|
||||
return machines.to_ary.delete_if {|m| !filter[:machine_ids].include?(m.id)}.map(&:name).join(' - ')
|
||||
end
|
||||
return machines.map(&:name).join(' - ')
|
||||
elsif available_type == 'event'
|
||||
event.name
|
||||
else
|
||||
trainings.map(&:name).join(' - ')
|
||||
case available_type
|
||||
when 'machines'
|
||||
if filter[:machine_ids]
|
||||
return machines.to_ary.delete_if {|m| !filter[:machine_ids].include?(m.id)}.map(&:name).join(' - ')
|
||||
end
|
||||
return machines.map(&:name).join(' - ')
|
||||
when 'event'
|
||||
event.name
|
||||
when 'training'
|
||||
trainings.map(&:name).join(' - ')
|
||||
when 'space'
|
||||
spaces.map(&:name).join(' - ')
|
||||
else
|
||||
STDERR.puts "[title] Availability with unknown type #{available_type}"
|
||||
'???'
|
||||
end
|
||||
end
|
||||
|
||||
@ -74,23 +102,23 @@ class Availability < ActiveRecord::Base
|
||||
# if haven't defined a nb_total_places, places are unlimited
|
||||
def is_completed
|
||||
return false if nb_total_places.blank?
|
||||
if available_type == 'training'
|
||||
if available_type == 'training' || available_type == 'space'
|
||||
nb_total_places <= slots.to_a.select {|s| s.canceled_at == nil }.size
|
||||
elsif available_type == 'event'
|
||||
event.nb_free_places == 0
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: refactoring this function for avoid N+1 query
|
||||
def nb_total_places
|
||||
if available_type == 'training'
|
||||
if read_attribute(:nb_total_places).present?
|
||||
read_attribute(:nb_total_places)
|
||||
case available_type
|
||||
when 'training'
|
||||
super.presence || trainings.map {|t| t.nb_total_places}.reduce(:+)
|
||||
when 'event'
|
||||
event.nb_total_places
|
||||
when 'space'
|
||||
super.presence || spaces.map {|s| s.default_places}.reduce(:+)
|
||||
else
|
||||
trainings.first.nb_total_places unless trainings.empty?
|
||||
end
|
||||
elsif available_type == 'event'
|
||||
event.nb_total_places
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -98,12 +126,17 @@ class Availability < ActiveRecord::Base
|
||||
def as_indexed_json
|
||||
json = JSON.parse(to_json)
|
||||
json['hours_duration'] = (end_at - start_at) / (60 * 60)
|
||||
if available_type == 'machines'
|
||||
json['subType'] = machines_availabilities.map{|ma| ma.machine.friendly_id}
|
||||
elsif available_type == 'training'
|
||||
json['subType'] = trainings_availabilities.map{|ta| ta.training.friendly_id}
|
||||
elsif available_type == 'event'
|
||||
json['subType'] = [event.category.friendly_id]
|
||||
case available_type
|
||||
when 'machines'
|
||||
json['subType'] = machines_availabilities.map{|ma| ma.machine.friendly_id}
|
||||
when'training'
|
||||
json['subType'] = trainings_availabilities.map{|ta| ta.training.friendly_id}
|
||||
when 'event'
|
||||
json['subType'] = [event.category.friendly_id]
|
||||
when 'space'
|
||||
json['subType'] = spaces_availabilities.map{|sa| sa.space.friendly_id}
|
||||
else
|
||||
json['subType'] = []
|
||||
end
|
||||
json['bookable_hours'] = json['hours_duration'] * json['subType'].length
|
||||
json['date'] = start_at.to_date
|
||||
|
@ -204,7 +204,9 @@ class Invoice < ActiveRecord::Base
|
||||
|
||||
private
|
||||
def generate_and_send_invoice
|
||||
puts "Creating an InvoiceWorker job to generate the following invoice: id(#{id}), invoiced_id(#{invoiced_id}), invoiced_type(#{invoiced_type}), user_id(#{user_id})"
|
||||
unless Rails.env.test?
|
||||
puts "Creating an InvoiceWorker job to generate the following invoice: id(#{id}), invoiced_id(#{invoiced_id}), invoiced_type(#{invoiced_type}), user_id(#{user_id})"
|
||||
end
|
||||
InvoiceWorker.perform_async(id)
|
||||
end
|
||||
|
||||
|
@ -4,7 +4,7 @@ class MachinesAvailability < ActiveRecord::Base
|
||||
after_destroy :cleanup_availability
|
||||
|
||||
# when the MachinesAvailability is deleted (from Machine destroy cascade), we delete the corresponding
|
||||
# availability if the deleted machine was the last is this availability slot and teh availability is not
|
||||
# availability if the deleted machine was the last of this availability slot, and the availability is not
|
||||
# currently being destroyed.
|
||||
def cleanup_availability
|
||||
unless availability.destroying
|
||||
|
@ -4,6 +4,7 @@ class Plan < ActiveRecord::Base
|
||||
has_many :credits, dependent: :destroy
|
||||
has_many :training_credits, -> {where(creditable_type: 'Training')}, class_name: 'Credit'
|
||||
has_many :machine_credits, -> {where(creditable_type: 'Machine')}, class_name: 'Credit'
|
||||
has_many :space_credits, -> {where(creditable_type: 'Space')}, class_name: 'Credit'
|
||||
has_many :subscriptions
|
||||
has_one :plan_image, as: :viewable, dependent: :destroy
|
||||
has_one :plan_file, as: :viewable, dependent: :destroy
|
||||
@ -18,6 +19,7 @@ class Plan < ActiveRecord::Base
|
||||
after_update :update_stripe_plan, if: :amount_changed?
|
||||
after_create :create_stripe_plan, unless: :skip_create_stripe_plan
|
||||
after_create :create_machines_prices
|
||||
after_create :create_spaces_prices
|
||||
after_create :create_statistic_type
|
||||
after_destroy :delete_stripe_plan
|
||||
|
||||
@ -56,6 +58,12 @@ class Plan < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def create_spaces_prices
|
||||
Space.all.each do |space|
|
||||
Price.create(priceable: space, plan: self, group_id: self.group_id, amount: 0)
|
||||
end
|
||||
end
|
||||
|
||||
def duration
|
||||
interval_count.send(interval)
|
||||
end
|
||||
|
@ -31,6 +31,7 @@ class Price < ActiveRecord::Base
|
||||
new_plan_being_bought = true
|
||||
else
|
||||
plan = nil
|
||||
new_plan_being_bought = false
|
||||
end
|
||||
|
||||
# === compute reservation price ===
|
||||
@ -41,13 +42,13 @@ class Price < ActiveRecord::Base
|
||||
when Machine
|
||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
||||
if plan
|
||||
machine_credit = plan.machine_credits.select {|credit| credit.creditable_id == reservable.id}.first
|
||||
if machine_credit
|
||||
hours_available = machine_credit.hours
|
||||
if !new_plan_being_bought
|
||||
user_credit = user.users_credits.find_by(credit_id: machine_credit.id)
|
||||
space_credit = plan.machine_credits.select {|credit| credit.creditable_id == reservable.id}.first
|
||||
if space_credit
|
||||
hours_available = space_credit.hours
|
||||
unless new_plan_being_bought
|
||||
user_credit = user.users_credits.find_by(credit_id: space_credit.id)
|
||||
if user_credit
|
||||
hours_available = machine_credit.hours - user_credit.hours_used
|
||||
hours_available = space_credit.hours - user_credit.hours_used
|
||||
end
|
||||
end
|
||||
slots.each_with_index do |slot, index|
|
||||
@ -69,18 +70,18 @@ class Price < ActiveRecord::Base
|
||||
amount = reservable.amount_by_group(user.group_id).amount
|
||||
if plan
|
||||
# Return True if the subscription link a training credit for training reserved by the user
|
||||
training_is_creditable = plan.training_credits.select {|credit| credit.creditable_id == reservable.id}.size > 0
|
||||
space_is_creditable = plan.training_credits.select {|credit| credit.creditable_id == reservable.id}.size > 0
|
||||
|
||||
# Training reserved by the user is free when :
|
||||
|
||||
# |-> the user already has a current subscription and if training_is_creditable is true and has at least one credit available.
|
||||
# |-> the user already has a current subscription and if space_is_creditable is true and has at least one credit available.
|
||||
if !new_plan_being_bought
|
||||
if user.training_credits.size < plan.training_credit_nb and training_is_creditable
|
||||
if user.training_credits.size < plan.training_credit_nb and space_is_creditable
|
||||
amount = 0
|
||||
end
|
||||
# |-> the user buys a new subscription and if training_is_creditable is true.
|
||||
# |-> the user buys a new subscription and if space_is_creditable is true.
|
||||
else
|
||||
if training_is_creditable
|
||||
if space_is_creditable
|
||||
amount = 0
|
||||
end
|
||||
end
|
||||
@ -99,6 +100,34 @@ class Price < ActiveRecord::Base
|
||||
_amount += get_slot_price(amount, slot, admin, _elements)
|
||||
end
|
||||
|
||||
# Space reservation
|
||||
when Space
|
||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
||||
|
||||
if plan
|
||||
space_credit = plan.space_credits.select {|credit| credit.creditable_id == reservable.id}.first
|
||||
if space_credit
|
||||
hours_available = space_credit.hours
|
||||
unless new_plan_being_bought
|
||||
user_credit = user.users_credits.find_by(credit_id: space_credit.id)
|
||||
if user_credit
|
||||
hours_available = space_credit.hours - user_credit.hours_used
|
||||
end
|
||||
end
|
||||
slots.each_with_index do |slot, index|
|
||||
_amount += get_slot_price(base_amount, slot, admin, _elements, (index < hours_available))
|
||||
end
|
||||
else
|
||||
slots.each do |slot|
|
||||
_amount += get_slot_price(base_amount, slot, admin, _elements)
|
||||
end
|
||||
end
|
||||
else
|
||||
slots.each do |slot|
|
||||
_amount += get_slot_price(base_amount, slot, admin, _elements)
|
||||
end
|
||||
end
|
||||
|
||||
# Unknown reservation type
|
||||
else
|
||||
raise NotImplementedError
|
||||
|
@ -21,6 +21,7 @@ class Project < ActiveRecord::Base
|
||||
accepts_nested_attributes_for :project_caos, allow_destroy: true, reject_if: :all_blank
|
||||
|
||||
has_and_belongs_to_many :machines, join_table: :projects_machines
|
||||
has_and_belongs_to_many :spaces, join_table: :projects_spaces
|
||||
has_and_belongs_to_many :components, join_table: :projects_components
|
||||
has_and_belongs_to_many :themes, join_table: :projects_themes
|
||||
|
||||
|
@ -2,7 +2,10 @@ class Reservation < ActiveRecord::Base
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
|
||||
belongs_to :user
|
||||
has_many :slots, dependent: :destroy
|
||||
|
||||
has_many :slots_reservations, dependent: :destroy
|
||||
has_many :slots, through: :slots_reservations
|
||||
|
||||
accepts_nested_attributes_for :slots, allow_destroy: true
|
||||
belongs_to :reservable, polymorphic: true
|
||||
|
||||
@ -126,6 +129,34 @@ class Reservation < ActiveRecord::Base
|
||||
self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description)
|
||||
end
|
||||
|
||||
# === Space reservation ===
|
||||
when Space
|
||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
||||
users_credits_manager = UsersCredits::Manager.new(reservation: self, plan: plan)
|
||||
|
||||
slots.each_with_index do |slot, index|
|
||||
description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
|
||||
ii_amount = base_amount # ii_amount default to base_amount
|
||||
|
||||
if users_credits_manager.will_use_credits?
|
||||
ii_amount = (index < users_credits_manager.free_hours_count) ? 0 : base_amount
|
||||
end
|
||||
|
||||
ii_amount = 0 if slot.offered and on_site # if it's a local payment and slot is offered free
|
||||
|
||||
unless on_site # if it's local payment then do not create Stripe::InvoiceItem
|
||||
ii = Stripe::InvoiceItem.create(
|
||||
customer: user.stp_customer_id,
|
||||
amount: ii_amount,
|
||||
currency: Rails.application.secrets.stripe_currency,
|
||||
description: description
|
||||
)
|
||||
invoice_items << ii
|
||||
end
|
||||
self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description)
|
||||
end
|
||||
|
||||
# === Unknown reservation type ===
|
||||
else
|
||||
raise NotImplementedError
|
||||
@ -164,7 +195,7 @@ class Reservation < ActiveRecord::Base
|
||||
if @wallet_amount_debit != 0 and !on_site
|
||||
invoice_items << Stripe::InvoiceItem.create(
|
||||
customer: user.stp_customer_id,
|
||||
amount: -@wallet_amount_debit,
|
||||
amount: -@wallet_amount_debit.to_i,
|
||||
currency: Rails.application.secrets.stripe_currency,
|
||||
description: "wallet -#{@wallet_amount_debit / 100.0}"
|
||||
)
|
||||
@ -347,7 +378,7 @@ class Reservation < ActiveRecord::Base
|
||||
def machine_not_already_reserved
|
||||
already_reserved = false
|
||||
self.slots.each do |slot|
|
||||
same_hour_slots = Slot.joins(:reservation).where(
|
||||
same_hour_slots = Slot.joins(:reservations).where(
|
||||
reservations: { reservable_type: self.reservable_type,
|
||||
reservable_id: self.reservable_id
|
||||
},
|
||||
|
@ -31,7 +31,8 @@ class Setting < ActiveRecord::Base
|
||||
name_genre
|
||||
reminder_enable
|
||||
reminder_delay
|
||||
event_explications_alert )
|
||||
event_explications_alert
|
||||
space_explications_alert )
|
||||
}
|
||||
|
||||
after_update :update_stylesheet if :value_changed?
|
||||
|
@ -1,16 +1,31 @@
|
||||
class Slot < ActiveRecord::Base
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
|
||||
belongs_to :reservation
|
||||
has_many :slots_reservations, dependent: :destroy
|
||||
has_many :reservations, through: :slots_reservations
|
||||
belongs_to :availability
|
||||
|
||||
attr_accessor :is_reserved, :machine, :title, :can_modify, :is_reserved_by_current_user
|
||||
attr_accessor :is_reserved, :machine, :space, :title, :can_modify, :is_reserved_by_current_user
|
||||
|
||||
after_update :set_ex_start_end_dates_attrs, if: :dates_were_modified?
|
||||
after_update :notify_member_and_admin_slot_is_modified, if: :dates_were_modified?
|
||||
|
||||
after_update :notify_member_and_admin_slot_is_canceled, if: :canceled?
|
||||
|
||||
# for backward compatibility
|
||||
def reservation
|
||||
reservations.first
|
||||
end
|
||||
|
||||
def destroy
|
||||
update_column(:destroying, true)
|
||||
super
|
||||
end
|
||||
|
||||
def is_complete?
|
||||
reservations.length >= availability.nb_total_places
|
||||
end
|
||||
|
||||
private
|
||||
def notify_member_and_admin_slot_is_modified
|
||||
NotificationCenter.call type: 'notify_member_slot_is_modified',
|
||||
|
13
app/models/slots_reservation.rb
Normal file
13
app/models/slots_reservation.rb
Normal file
@ -0,0 +1,13 @@
|
||||
class SlotsReservation < ActiveRecord::Base
|
||||
belongs_to :slot
|
||||
belongs_to :reservation
|
||||
after_destroy :cleanup_slots
|
||||
|
||||
# when the SlotsReservation is deleted (from Reservation destroy cascade), we delete the
|
||||
# corresponding slot
|
||||
def cleanup_slots
|
||||
unless slot.destroying
|
||||
slot.destroy
|
||||
end
|
||||
end
|
||||
end
|
58
app/models/space.rb
Normal file
58
app/models/space.rb
Normal file
@ -0,0 +1,58 @@
|
||||
class Space < ActiveRecord::Base
|
||||
extend FriendlyId
|
||||
friendly_id :name, use: :slugged
|
||||
|
||||
validates :name, :default_places, presence: true
|
||||
|
||||
has_one :space_image, as: :viewable, dependent: :destroy
|
||||
accepts_nested_attributes_for :space_image, allow_destroy: true
|
||||
has_many :space_files, as: :viewable, dependent: :destroy
|
||||
accepts_nested_attributes_for :space_files, allow_destroy: true, reject_if: :all_blank
|
||||
|
||||
has_and_belongs_to_many :projects, join_table: :projects_spaces
|
||||
|
||||
has_many :spaces_availabilities
|
||||
has_many :availabilities, through: :spaces_availabilities, dependent: :destroy
|
||||
|
||||
has_many :reservations, as: :reservable, dependent: :destroy
|
||||
|
||||
has_many :prices, as: :priceable, dependent: :destroy
|
||||
has_many :credits, as: :creditable, dependent: :destroy
|
||||
|
||||
after_create :create_statistic_subtype
|
||||
after_create :create_space_prices
|
||||
after_update :update_statistic_subtype, if: :name_changed?
|
||||
after_destroy :remove_statistic_subtype
|
||||
|
||||
|
||||
def create_statistic_subtype
|
||||
index = StatisticIndex.find_by(es_type_key: 'space')
|
||||
StatisticSubType.create!({statistic_types: index.statistic_types, key: self.slug, label: self.name})
|
||||
end
|
||||
|
||||
def update_statistic_subtype
|
||||
index = StatisticIndex.find_by(es_type_key: 'space')
|
||||
subtype = StatisticSubType.joins(statistic_type_sub_types: :statistic_type).find_by(key: self.slug, statistic_types: { statistic_index_id: index.id })
|
||||
subtype.label = self.name
|
||||
subtype.save!
|
||||
end
|
||||
|
||||
def remove_statistic_subtype
|
||||
subtype = StatisticSubType.find_by(key: self.slug)
|
||||
subtype.destroy!
|
||||
end
|
||||
|
||||
def create_space_prices
|
||||
Group.all.each do |group|
|
||||
Price.create(priceable: self, group: group, amount: 0)
|
||||
end
|
||||
|
||||
Plan.all.includes(:group).each do |plan|
|
||||
Price.create(group: plan.group, plan: plan, priceable: self, amount: 0)
|
||||
end
|
||||
end
|
||||
|
||||
def destroyable?
|
||||
reservations.empty?
|
||||
end
|
||||
end
|
3
app/models/space_file.rb
Normal file
3
app/models/space_file.rb
Normal file
@ -0,0 +1,3 @@
|
||||
class SpaceFile < Asset
|
||||
mount_uploader :attachment, SpaceFileUploader
|
||||
end
|
4
app/models/space_image.rb
Normal file
4
app/models/space_image.rb
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
class SpaceImage < Asset
|
||||
mount_uploader :attachment, SpaceImageUploader
|
||||
end
|
14
app/models/spaces_availability.rb
Normal file
14
app/models/spaces_availability.rb
Normal file
@ -0,0 +1,14 @@
|
||||
class SpacesAvailability < ActiveRecord::Base
|
||||
belongs_to :space
|
||||
belongs_to :availability
|
||||
after_destroy :cleanup_availability
|
||||
|
||||
# when the SpacesAvailability is deleted (from Space destroy cascade), we delete the corresponding
|
||||
# availability. We don't use 'dependent: destroy' as we need to prevent conflicts if the destroy came from
|
||||
# the Availability destroy cascade.
|
||||
def cleanup_availability
|
||||
unless availability.destroying
|
||||
availability.safe_destroy
|
||||
end
|
||||
end
|
||||
end
|
9
app/models/stats/space.rb
Normal file
9
app/models/stats/space.rb
Normal file
@ -0,0 +1,9 @@
|
||||
module Stats
|
||||
class Space
|
||||
include Elasticsearch::Persistence::Model
|
||||
include StatConcern
|
||||
include StatReservationConcern
|
||||
|
||||
attribute :spaceId, Integer
|
||||
end
|
||||
end
|
@ -1,4 +1,4 @@
|
||||
|
||||
class TrainingImage < Asset
|
||||
mount_uploader :attachment, MachineImageUploader
|
||||
mount_uploader :attachment, TrainingImageUploader
|
||||
end
|
@ -125,6 +125,8 @@ module PDF
|
||||
### Machine reservation
|
||||
when 'Machine'
|
||||
details += I18n.t('invoices.machine_reservation_DESCRIPTION', DESCRIPTION: item.description)
|
||||
when 'Space'
|
||||
details += I18n.t('invoices.space_reservation_DESCRIPTION', DESCRIPTION: item.description)
|
||||
### Training reservation
|
||||
when 'Training'
|
||||
details += I18n.t('invoices.training_reservation_DESCRIPTION', DESCRIPTION: item.description)
|
||||
|
13
app/policies/space_policy.rb
Normal file
13
app/policies/space_policy.rb
Normal file
@ -0,0 +1,13 @@
|
||||
class SpacePolicy < ApplicationPolicy
|
||||
def create?
|
||||
user.is_admin?
|
||||
end
|
||||
|
||||
def update?
|
||||
user.is_admin?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user.is_admin? and record.destroyable?
|
||||
end
|
||||
end
|
@ -1,6 +1,6 @@
|
||||
class StatisticPolicy < ApplicationPolicy
|
||||
%w(index account event machine project subscription training user scroll export_subscription export_machine
|
||||
export_training export_event export_account export_project export_global).each do |action|
|
||||
%w(index account event machine project subscription training user space scroll export_subscription export_machine
|
||||
export_training export_event export_account export_project export_space export_global).each do |action|
|
||||
define_method "#{action}?" do
|
||||
user.is_admin?
|
||||
end
|
||||
|
@ -39,6 +39,23 @@ class StatisticService
|
||||
end
|
||||
end
|
||||
|
||||
# space list
|
||||
reservations_space_list(options).each do |r|
|
||||
%w(booking hour).each do |type|
|
||||
stat = Stats::Space.new({
|
||||
date: format_date(r.date),
|
||||
type: type,
|
||||
subType: r.space_type,
|
||||
ca: r.ca,
|
||||
spaceId: r.space_id,
|
||||
name: r.space_name,
|
||||
reservationId: r.reservation_id
|
||||
}.merge(user_info_stat(r)))
|
||||
stat.stat = (type == 'booking' ? 1 : r.nb_hours)
|
||||
stat.save
|
||||
end
|
||||
end
|
||||
|
||||
# training list
|
||||
reservations_training_list(options).each do |r|
|
||||
%w(booking hour).each do |type|
|
||||
@ -170,6 +187,27 @@ class StatisticService
|
||||
result
|
||||
end
|
||||
|
||||
|
||||
def reservations_space_list(options = default_options)
|
||||
result = []
|
||||
Reservation
|
||||
.where("reservable_type = 'Space' AND reservations.created_at >= :start_date AND reservations.created_at <= :end_date", options)
|
||||
.eager_load(:slots, user: [:profile, :group], invoice: [:invoice_items])
|
||||
.each do |r|
|
||||
u = r.user
|
||||
result.push OpenStruct.new({
|
||||
date: options[:start_date].to_date,
|
||||
reservation_id: r.id,
|
||||
space_id: r.reservable.id,
|
||||
space_name: r.reservable.name,
|
||||
space_type: r.reservable.slug,
|
||||
nb_hours: r.slots.size,
|
||||
ca: calcul_ca(r.invoice)
|
||||
}.merge(user_info(u))) if r.reservable
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def reservations_training_list(options = default_options)
|
||||
result = []
|
||||
Reservation
|
||||
@ -298,7 +336,7 @@ class StatisticService
|
||||
|
||||
def clean_stat(options = default_options)
|
||||
client = Elasticsearch::Model.client
|
||||
%w{Account Event Machine Project Subscription Training User}.each do |o|
|
||||
%w{Account Event Machine Project Subscription Training User Space}.each do |o|
|
||||
model = "Stats::#{o}".constantize
|
||||
client.delete_by_query(index: model.index_name, type: model.document_type, body: {query: {match: {date: format_date(options[:start_date])}}})
|
||||
end
|
||||
|
@ -40,7 +40,7 @@ class StatisticsExportService
|
||||
File.open(export.file,"w+b") {|f| f.puts content }
|
||||
end
|
||||
|
||||
%w(account event machine project subscription training).each do |path|
|
||||
%w(account event machine project subscription training space).each do |path|
|
||||
class_eval %{
|
||||
def export_#{path}(export)
|
||||
|
||||
|
@ -11,17 +11,19 @@ module UsersCredits
|
||||
if user
|
||||
@manager = Managers::User.new(user)
|
||||
elsif reservation
|
||||
if reservation.reservable_type == "Training"
|
||||
if reservation.reservable_type == 'Training'
|
||||
@manager = Managers::Training.new(reservation, plan)
|
||||
elsif reservation.reservable_type == "Machine"
|
||||
elsif reservation.reservable_type == 'Machine'
|
||||
@manager = Managers::Machine.new(reservation, plan)
|
||||
elsif reservation.reservable_type == "Event"
|
||||
elsif reservation.reservable_type == 'Event'
|
||||
@manager = Managers::Event.new(reservation, plan)
|
||||
elsif reservation.reservable_type == 'Space'
|
||||
@manager = Managers::Space.new(reservation, plan)
|
||||
else
|
||||
raise ArgumentError, "reservation.reservable_type must be Training, Machine or Event"
|
||||
raise ArgumentError, 'reservation.reservable_type must be Training, Machine, Space or Event'
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "you have to pass either a reservation or a user to initialize a UsersCredits::Manager"
|
||||
raise ArgumentError, 'you have to pass either a reservation or a user to initialize a UsersCredits::Manager'
|
||||
end
|
||||
end
|
||||
|
||||
@ -152,5 +154,52 @@ module UsersCredits
|
||||
def update_credits
|
||||
end
|
||||
end
|
||||
|
||||
class Space < Reservation
|
||||
def will_use_credits? # to known if a credit will be used in the context of the given reservation
|
||||
_will_use_credits?[0]
|
||||
end
|
||||
|
||||
def free_hours_count
|
||||
_will_use_credits?[1]
|
||||
end
|
||||
|
||||
def update_credits
|
||||
super
|
||||
|
||||
will_use_credits, free_hours_count, space_credit = _will_use_credits?
|
||||
if will_use_credits
|
||||
users_credit = user.users_credits.find_or_initialize_by(credit_id: space_credit.id)
|
||||
|
||||
if users_credit.new_record?
|
||||
users_credit.hours_used = free_hours_count
|
||||
else
|
||||
users_credit.hours_used += free_hours_count
|
||||
end
|
||||
users_credit.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def _will_use_credits?
|
||||
return false, 0 unless plan
|
||||
|
||||
if space_credit = plan.space_credits.find_by(creditable_id: reservation.reservable_id)
|
||||
users_credit = user.users_credits.find_by(credit_id: space_credit.id)
|
||||
already_used_hours = users_credit ? users_credit.hours_used : 0
|
||||
|
||||
remaining_hours = space_credit.hours - already_used_hours
|
||||
|
||||
free_hours_count = [remaining_hours, reservation.slots.size].min
|
||||
|
||||
if free_hours_count > 0
|
||||
return true, free_hours_count, space_credit
|
||||
else
|
||||
return false, free_hours_count, space_credit
|
||||
end
|
||||
end
|
||||
return false, 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
49
app/uploaders/space_file_uploader.rb
Normal file
49
app/uploaders/space_file_uploader.rb
Normal file
@ -0,0 +1,49 @@
|
||||
class SpaceFileUploader < CarrierWave::Uploader::Base
|
||||
# Include RMagick or MiniMagick support:
|
||||
# include CarrierWave::RMagick
|
||||
#include CarrierWave::MiniMagick
|
||||
include UploadHelper
|
||||
|
||||
# Choose what kind of storage to use for this uploader:
|
||||
storage :file
|
||||
after :remove, :delete_empty_dirs
|
||||
# storage :fog
|
||||
|
||||
# Override the directory where uploaded files will be stored.
|
||||
# This is a sensible default for uploaders that are meant to be mounted:
|
||||
def store_dir
|
||||
"#{base_store_dir}/#{model.id}"
|
||||
end
|
||||
|
||||
def base_store_dir
|
||||
"uploads/#{model.class.to_s.underscore}"
|
||||
end
|
||||
|
||||
# Provide a default URL as a default if there hasn't been a file uploaded:
|
||||
# def default_url
|
||||
# # For Rails 3.1+ asset pipeline compatibility:
|
||||
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
|
||||
#
|
||||
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||
# end
|
||||
|
||||
# Process files as they are uploaded:
|
||||
# process :scale => [200, 300]
|
||||
#
|
||||
# def scale(width, height)
|
||||
# # do something
|
||||
# end
|
||||
|
||||
|
||||
# Add a white list of extensions which are allowed to be uploaded.
|
||||
# For images you might use something like this:
|
||||
def extension_white_list
|
||||
%w(pdf)
|
||||
end
|
||||
|
||||
# Override the filename of the uploaded files:
|
||||
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
||||
#def filename
|
||||
#"avatar.#{file.extension}" if original_filename
|
||||
#end
|
||||
end
|
58
app/uploaders/space_image_uploader.rb
Normal file
58
app/uploaders/space_image_uploader.rb
Normal file
@ -0,0 +1,58 @@
|
||||
class SpaceImageUploader < CarrierWave::Uploader::Base
|
||||
# Include RMagick or MiniMagick support:
|
||||
# include CarrierWave::RMagick
|
||||
include CarrierWave::MiniMagick
|
||||
include UploadHelper
|
||||
|
||||
# Choose what kind of storage to use for this uploader:
|
||||
storage :file
|
||||
after :remove, :delete_empty_dirs
|
||||
# storage :fog
|
||||
|
||||
# Override the directory where uploaded files will be stored.
|
||||
# This is a sensible default for uploaders that are meant to be mounted:
|
||||
|
||||
def store_dir
|
||||
"#{base_store_dir}/#{model.id}"
|
||||
end
|
||||
|
||||
def base_store_dir
|
||||
"uploads/#{model.class.to_s.underscore}"
|
||||
end
|
||||
|
||||
# Provide a default URL as a default if there hasn't been a file uploaded:
|
||||
# def default_url
|
||||
# # For Rails 3.1+ asset pipeline compatibility:
|
||||
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
|
||||
#
|
||||
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||
# end
|
||||
|
||||
# Process files as they are uploaded:
|
||||
# process :scale => [200, 300]
|
||||
#
|
||||
# def scale(width, height)
|
||||
# # do something
|
||||
# end
|
||||
|
||||
# Create different versions of your uploaded files:
|
||||
version :large do
|
||||
process :resize_to_fit => [1000, 700]
|
||||
end
|
||||
|
||||
version :medium do
|
||||
process :resize_to_fit => [700, 400]
|
||||
end
|
||||
|
||||
# Add a white list of extensions which are allowed to be uploaded.
|
||||
# For images you might use something like this:
|
||||
def extension_white_list
|
||||
%w(jpg jpeg gif png)
|
||||
end
|
||||
|
||||
# Override the filename of the uploaded files:
|
||||
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
||||
def filename
|
||||
"space_image.#{file.extension}" if original_filename
|
||||
end
|
||||
end
|
58
app/uploaders/training_image_uploader.rb
Normal file
58
app/uploaders/training_image_uploader.rb
Normal file
@ -0,0 +1,58 @@
|
||||
class TrainingImageUploader < CarrierWave::Uploader::Base
|
||||
# Include RMagick or MiniMagick support:
|
||||
# include CarrierWave::RMagick
|
||||
include CarrierWave::MiniMagick
|
||||
include UploadHelper
|
||||
|
||||
# Choose what kind of storage to use for this uploader:
|
||||
storage :file
|
||||
after :remove, :delete_empty_dirs
|
||||
# storage :fog
|
||||
|
||||
# Override the directory where uploaded files will be stored.
|
||||
# This is a sensible default for uploaders that are meant to be mounted:
|
||||
|
||||
def store_dir
|
||||
"#{base_store_dir}/#{model.id}"
|
||||
end
|
||||
|
||||
def base_store_dir
|
||||
"uploads/#{model.class.to_s.underscore}"
|
||||
end
|
||||
|
||||
# Provide a default URL as a default if there hasn't been a file uploaded:
|
||||
# def default_url
|
||||
# # For Rails 3.1+ asset pipeline compatibility:
|
||||
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
|
||||
#
|
||||
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||
# end
|
||||
|
||||
# Process files as they are uploaded:
|
||||
# process :scale => [200, 300]
|
||||
#
|
||||
# def scale(width, height)
|
||||
# # do something
|
||||
# end
|
||||
|
||||
# Create different versions of your uploaded files:
|
||||
version :large do
|
||||
process :resize_to_fit => [1000, 700]
|
||||
end
|
||||
|
||||
version :medium do
|
||||
process :resize_to_fit => [700, 400]
|
||||
end
|
||||
|
||||
# Add a white list of extensions which are allowed to be uploaded.
|
||||
# For images you might use something like this:
|
||||
def extension_white_list
|
||||
%w(jpg jpeg gif png)
|
||||
end
|
||||
|
||||
# Override the filename of the uploaded files:
|
||||
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
||||
def filename
|
||||
"training_image.#{file.extension}" if original_filename
|
||||
end
|
||||
end
|
@ -5,7 +5,7 @@ json.array!(@availabilities) do |availability|
|
||||
json.textColor 'black'
|
||||
json.backgroundColor 'white'
|
||||
# availability object
|
||||
if availability.try(:available_type)
|
||||
if availability.instance_of? Availability
|
||||
json.title availability.title(@title_filter)
|
||||
if availability.available_type == 'event'
|
||||
json.event_id availability.event.id
|
||||
@ -20,7 +20,7 @@ json.array!(@availabilities) do |availability|
|
||||
json.name t.name
|
||||
end
|
||||
|
||||
if availability.available_type != 'machines'
|
||||
if availability.available_type == 'training' or availability.available_type == 'event'
|
||||
json.borderColor trainings_events_border_color(availability)
|
||||
if availability.is_reserved
|
||||
json.is_reserved true
|
||||
@ -29,20 +29,43 @@ json.array!(@availabilities) do |availability|
|
||||
json.is_completed true
|
||||
json.title "#{availability.title} - #{t('trainings.completed')}"
|
||||
end
|
||||
elsif availability.available_type == 'space'
|
||||
complete = availability.slots.length >= availability.available_space_places
|
||||
json.is_completed complete
|
||||
json.borderColor availability_border_color(availability)
|
||||
if complete
|
||||
json.title "#{availability.title} - #{t('trainings.completed')}"
|
||||
json.borderColor AvailabilityHelper::IS_COMPLETED
|
||||
end
|
||||
if availability.is_reserved
|
||||
json.is_reserved true
|
||||
json.title "#{availability.title} - #{t('trainings.i_ve_reserved')}"
|
||||
end
|
||||
else
|
||||
json.borderColor availability_border_color(availability)
|
||||
end
|
||||
|
||||
# machine slot object ( here => availability = slot )
|
||||
else
|
||||
# slot object ( here => availability = slot )
|
||||
# -> machines / spaces
|
||||
elsif availability.instance_of? Slot
|
||||
json.title availability.title
|
||||
json.machine_id availability.machine.id
|
||||
json.borderColor machines_slot_border_color(availability)
|
||||
json.tag_ids availability.availability.tag_ids
|
||||
json.tags availability.availability.tags do |t|
|
||||
json.id t.id
|
||||
json.name t.name
|
||||
end
|
||||
json.is_reserved availability.is_reserved
|
||||
if availability.try(:machine)
|
||||
json.machine_id availability.machine.id
|
||||
json.borderColor machines_slot_border_color(availability)
|
||||
json.is_reserved availability.is_reserved
|
||||
elsif availability.try(:space)
|
||||
json.space_id availability.space.id
|
||||
json.borderColor space_slot_border_color(availability)
|
||||
json.is_completed availability.is_complete?
|
||||
else
|
||||
json.title 'Unknown slot'
|
||||
end
|
||||
else
|
||||
json.title 'Unknown object'
|
||||
end
|
||||
end
|
||||
|
27
app/views/api/availabilities/spaces.json.jbuilder
Normal file
27
app/views/api/availabilities/spaces.json.jbuilder
Normal file
@ -0,0 +1,27 @@
|
||||
json.array!(@slots) do |slot|
|
||||
json.id slot.id if slot.id
|
||||
json.can_modify slot.can_modify
|
||||
json.title slot.title
|
||||
json.start slot.start_at.iso8601
|
||||
json.end slot.end_at.iso8601
|
||||
json.is_reserved slot.is_reserved
|
||||
json.is_completed slot.is_complete?
|
||||
json.backgroundColor 'white'
|
||||
json.borderColor space_slot_border_color(slot)
|
||||
|
||||
json.availability_id slot.availability_id
|
||||
json.space do
|
||||
json.id slot.space.id
|
||||
json.name slot.space.name
|
||||
end
|
||||
# the user who booked the slot ...
|
||||
json.user do
|
||||
json.id slot.reservation.user.id
|
||||
json.name slot.reservation.user.profile.full_name
|
||||
end if @current_user_role == 'admin' and slot.reservation # ... if the slot was reserved
|
||||
json.tag_ids slot.availability.tag_ids
|
||||
json.tags slot.availability.tags do |t|
|
||||
json.id t.id
|
||||
json.name t.name
|
||||
end
|
||||
end
|
@ -1,6 +1,5 @@
|
||||
json.array!(@availabilities) do |a|
|
||||
json.id a.id
|
||||
json.slot_id a.slot_id if a.slot_id
|
||||
json.id a.slot_id if a.slot_id
|
||||
if a.is_reserved
|
||||
json.is_reserved true
|
||||
json.title "#{a.trainings[0].name}' - #{t('trainings.i_ve_reserved')}"
|
||||
@ -16,6 +15,7 @@ json.array!(@availabilities) do |a|
|
||||
json.backgroundColor 'white'
|
||||
json.can_modify a.can_modify
|
||||
json.nb_total_places a.nb_total_places
|
||||
json.availability_id a.id
|
||||
|
||||
json.training do
|
||||
json.id a.trainings.first.id
|
||||
|
@ -1,4 +1,4 @@
|
||||
json.extract! @machine, :id, :name, :description, :spec, :created_at, :updated_at
|
||||
json.extract! @machine, :id, :name, :description, :spec, :created_at, :updated_at, :slug
|
||||
json.machine_image @machine.machine_image.attachment.large.url if @machine.machine_image
|
||||
json.machine_files_attributes @machine.machine_files do |f|
|
||||
json.id f.id
|
||||
|
4
app/views/api/spaces/index.json.jbuilder
Normal file
4
app/views/api/spaces/index.json.jbuilder
Normal file
@ -0,0 +1,4 @@
|
||||
json.array!(@spaces) do |space|
|
||||
json.extract! space, :id, :name, :description, :slug, :default_places
|
||||
json.space_image space.space_image.attachment.medium.url if space.space_image
|
||||
end
|
12
app/views/api/spaces/show.json.jbuilder
Normal file
12
app/views/api/spaces/show.json.jbuilder
Normal file
@ -0,0 +1,12 @@
|
||||
json.extract! @space, :id, :name, :description, :characteristics, :created_at, :updated_at, :slug, :default_places
|
||||
json.space_image @space.space_image.attachment.large.url if @space.space_image
|
||||
json.space_files_attributes @space.space_files do |f|
|
||||
json.id f.id
|
||||
json.attachment f.attachment_identifier
|
||||
json.attachment_url f.attachment_url
|
||||
end
|
||||
# Unused for the moment. May be used to show a list of projects
|
||||
# using the space in the space_show screen
|
||||
# json.space_projects @space.projects do |p|
|
||||
# json.extract! p, :slug, :name
|
||||
# end
|
@ -4,7 +4,7 @@ json.availabilities @availabilities do |a|
|
||||
json.start_at a.start_at.iso8601
|
||||
json.end_at a.end_at.iso8601
|
||||
json.reservation_users a.slots.map do |slot|
|
||||
json.id slot.reservation.user.id
|
||||
json.id slot.reservation.user_id
|
||||
json.full_name slot.reservation.user.profile.full_name
|
||||
json.is_valid slot.reservation.user.trainings.include?(@training)
|
||||
end
|
||||
|
@ -24,6 +24,7 @@
|
||||
var Fablab = Fablab || {};
|
||||
|
||||
Fablab.withoutPlans = ('<%= Rails.application.secrets.fablab_without_plans %>' == 'true');
|
||||
Fablab.withoutSpaces = ('<%= Rails.application.secrets.fablab_without_spaces %>' != 'false');
|
||||
Fablab.disqusShortname = "<%= Rails.application.secrets.disqus_shortname %>";
|
||||
Fablab.defaultHost = "<%= Rails.application.secrets.default_host %>";
|
||||
Fablab.gaId = "<%= Rails.application.secrets.google_analytics_id %>";
|
||||
|
@ -68,7 +68,7 @@ date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_
|
||||
end
|
||||
|
||||
end
|
||||
# proceed teh 'ca' field if requested
|
||||
# proceed the 'ca' field if requested
|
||||
if index.ca
|
||||
data.push hit['_source']['ca']
|
||||
styles.push nil
|
||||
|
@ -15,7 +15,7 @@ class StatisticsExportWorker
|
||||
service = StatisticsExportService.new
|
||||
method_name = "export_#{export.export_type}"
|
||||
|
||||
if %w(account event machine project subscription training global).include?(export.export_type) and service.respond_to?(method_name)
|
||||
if %w(account event machine project subscription training space global).include?(export.export_type) and service.respond_to?(method_name)
|
||||
service.public_send(method_name, export)
|
||||
|
||||
NotificationCenter.call type: :notify_admin_export_complete,
|
||||
|
@ -12,6 +12,7 @@ STRIPE_CURRENCY: 'eur'
|
||||
|
||||
INVOICE_PREFIX: Demo-FabLab-facture
|
||||
FABLAB_WITHOUT_PLANS: 'false'
|
||||
FABLAB_WITHOUT_SPACES: 'true'
|
||||
|
||||
DEFAULT_MAIL_FROM: Fab Manager Demo <noreply@fab-manager.com>
|
||||
|
||||
|
@ -13,28 +13,38 @@ en:
|
||||
|
||||
calendar:
|
||||
# manage the trainings & machines slots
|
||||
calendar_management: "Calendar management"
|
||||
ongoing_reservations: "Ongoing reservations"
|
||||
no_reservations: "No reservations"
|
||||
do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Do you really want to cancel the {USER}'s reservation, the {DATE} at {TIME}, concerning {RESERVATION}?" # messageFormat interpolation
|
||||
reservation_cancellation_failed: "Reservation cancellation failed."
|
||||
unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Unable to remove the last machine of the slot. Delete the slot rather."
|
||||
do_you_really_want_to_remove_MACHINE_from_this_slot: "Do you really want to remove \"{MACHINE}\" from this slot?" # messageFormat interpolation
|
||||
this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing: "This will prevent any new reservation on this slot but won't cancel those existing."
|
||||
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."
|
||||
deletion_failed: "Deletion failed."
|
||||
DATE_slot: "{{DATE}} slot:" # angular interpolation
|
||||
you_can_define_a_training_on_that_slot: "You can define a training on that slot:"
|
||||
link_a_training: "Link a training"
|
||||
or_: "Or"
|
||||
_select_some_machines: "select some machines"
|
||||
number_of_tickets: "Number of tickets: "
|
||||
adjust_the_opening_hours: "Adjust the opening hours"
|
||||
restrict_this_slot_with_labels_(optional): "Restrict this slot with labels (optional)"
|
||||
the_slot_START-END_has_been_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # angular interpolation
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation
|
||||
you_should_link_a_training_or_a_machine_to_this_slot: "You should link a training or a machine to this slot."
|
||||
admin_calendar:
|
||||
calendar_management: "Calendar management"
|
||||
trainings: "Trainings"
|
||||
machines: "Machines"
|
||||
spaces: "Spaces"
|
||||
ongoing_reservations: "Ongoing reservations"
|
||||
no_reservations: "No reservations"
|
||||
confirmation_required: "Confirmation required"
|
||||
do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Do you really want to cancel the {USER}'s reservation, the {DATE} at {TIME}, concerning {RESERVATION}?" # messageFormat interpolation
|
||||
reservation_was_successfully_cancelled: "Reservation was successfully cancelled."
|
||||
reservation_cancellation_failed: "Reservation cancellation failed."
|
||||
unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Unable to remove the last machine of the slot. Delete the slot rather."
|
||||
do_you_really_want_to_remove_MACHINE_from_this_slot: "Do you really want to remove \"{MACHINE}\" from this slot?" # messageFormat interpolation
|
||||
this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing: "This will prevent any new reservation on this slot but won't cancel those existing."
|
||||
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."
|
||||
deletion_failed: "Deletion failed."
|
||||
DATE_slot: "{{DATE}} slot:" # angular interpolation
|
||||
what_kind_of_slot_do_you_want_to_create: "What kind of slot do you want to create?"
|
||||
training: "Training"
|
||||
machine: "Machine"
|
||||
space: "Space"
|
||||
next: "Next >"
|
||||
previous: "< Previous"
|
||||
select_some_machines: "Select some machines"
|
||||
number_of_tickets: "Number of tickets: "
|
||||
adjust_the_opening_hours: "Adjust the opening hours"
|
||||
to_time: "to" # context: time. eg. "from 18:00 to 21:00"
|
||||
restrict_this_slot_with_labels_(optional): "Restrict this slot with labels (optional)"
|
||||
the_slot_START-END_has_been_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # angular interpolation
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation
|
||||
you_should_select_at_least_a_machine: "You should select at least one machine on this slot."
|
||||
|
||||
project_elements:
|
||||
# management of the projects' components
|
||||
@ -132,55 +142,78 @@ en:
|
||||
|
||||
pricing:
|
||||
# subscriptions, prices, credits and coupons management
|
||||
pricing_management: "Pricing management"
|
||||
list_of_the_subscription_plans: "List of the subscription plans"
|
||||
beware_the_subscriptions_are_disabled_on_this_application: "Beware, the subscriptions are disabled on this application."
|
||||
you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager: "You can create some but they won't be available until the project is redeployed by the server manager."
|
||||
for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later: "For safety reasons, please don't create subscriptions if you don't intend to use them later."
|
||||
add_a_new_subscription_plan: "Add a new subscription plan"
|
||||
duration: "Duration"
|
||||
prominence: "Prominence"
|
||||
machine_hours: "Machine hours"
|
||||
these_prices_match_machine_hours_rates_: "These prices match machine hours rates"
|
||||
_without_subscriptions: "without subscriptions"
|
||||
credits: "Credits"
|
||||
related_trainings: "Related trainings"
|
||||
add_a_machine_credit: "Add a machine credit"
|
||||
machine: "Machine"
|
||||
hours: "Hours"
|
||||
related_subscriptions: "Related subscriptions"
|
||||
please_specify_a_number: "Please specify a number."
|
||||
none: "None" # grammar note: concordance with "training".
|
||||
an_error_occurred_while_saving_the_number_of_credits: "An error occurred while saving the number of credits."
|
||||
an_error_occurred_while_deleting_credit_with_the_TRAINING: "An error occurred while deleting credit with the {{TRAINING}}." # angular interpolation
|
||||
an_error_occurred_unable_to_find_the_credit_to_revoke: "An error occurred : unable to find the credit to revoke."
|
||||
an_error_occurred_while_creating_credit_with_the_TRAINING: "An error occurred while creating credit with the {{TRAINING}}." # angular interpolation
|
||||
not_set: "Not set"
|
||||
error_a_credit_linking_this_machine_with_that_subscription_already_exists: "Error : a credit linking this machine with that subscription already exists."
|
||||
changes_have_been_successfully_saved: "Changes have been successfully saved."
|
||||
credit_was_successfully_saved: "Credit was successfully saved."
|
||||
do_you_really_want_to_delete_this_subscription_plan: "Do you really want to delete this subscription plan?"
|
||||
subscription_plan_was_successfully_deleted: "Subscription plan was successfully deleted."
|
||||
unable_to_delete_the_specified_subscription_an_error_occurred: "Unable to delete the specified subscription, an error occurred."
|
||||
coupons: "Coupons"
|
||||
list_of_the_coupons: "List of the coupons"
|
||||
discount: "Discount"
|
||||
nb_of_usages: "Number of usages"
|
||||
status: "Status"
|
||||
add_a_new_coupon: "Add a new coupon"
|
||||
disabled: "Disabled"
|
||||
expired: "Expired"
|
||||
sold_out: "Sold out"
|
||||
active: "Active"
|
||||
do_you_really_want_to_delete_this_coupon: "Do you really want to delete this coupon?"
|
||||
coupon_was_successfully_deleted: "Coupon was successfully deleted."
|
||||
unable_to_delete_the_specified_coupon_already_in_use: "Unable to delete the specified coupon: it is already used with some invoices."
|
||||
unable_to_delete_the_specified_coupon_an_unexpected_error_occurred: "Unable to delete the specified coupon: an unexpected error occurred."
|
||||
send_a_coupon: "Send a coupon"
|
||||
coupon: "Coupon"
|
||||
usages: "Usages"
|
||||
coupon_successfully_sent_to_USER: "Coupon successfully sent to {{USER}}" # angular interpolation
|
||||
an_error_occurred_unable_to_send_the_coupon: "An unexpected error prevent from sending the coupon."
|
||||
pricing:
|
||||
pricing_management: "Pricing management"
|
||||
subscriptions: "Subscriptions"
|
||||
trainings: "Trainings"
|
||||
list_of_the_subscription_plans: "List of the subscription plans"
|
||||
beware_the_subscriptions_are_disabled_on_this_application: "Beware, the subscriptions are disabled on this application."
|
||||
you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager: "You can create some but they won't be available until the project is redeployed by the server manager."
|
||||
for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later: "For safety reasons, please don't create subscriptions if you don't intend to use them later."
|
||||
add_a_new_subscription_plan: "Add a new subscription plan"
|
||||
type: "Type"
|
||||
partner: "Partner"
|
||||
standard: "Standard"
|
||||
name: "Name"
|
||||
duration: "Duration"
|
||||
group: "Group"
|
||||
prominence: "Prominence"
|
||||
price: "Price"
|
||||
machine_hours: "Machine hours"
|
||||
these_prices_match_machine_hours_rates_: "These prices match machine hours rates"
|
||||
_without_subscriptions: "without subscriptions"
|
||||
machines: "Machines"
|
||||
credits: "Credits"
|
||||
subscription: "Subscription"
|
||||
related_trainings: "Related trainings"
|
||||
add_a_machine_credit: "Add a machine credit"
|
||||
machine: "Machine"
|
||||
hours: "Hours"
|
||||
related_subscriptions: "Related subscriptions"
|
||||
please_specify_a_number: "Please specify a number."
|
||||
none: "None" # grammar note: concordance with "training".
|
||||
an_error_occurred_while_saving_the_number_of_credits: "An error occurred while saving the number of credits."
|
||||
an_error_occurred_while_deleting_credit_with_the_TRAINING: "An error occurred while deleting credit with the {{TRAINING}}." # angular interpolation
|
||||
an_error_occurred_unable_to_find_the_credit_to_revoke: "An error occurred : unable to find the credit to revoke."
|
||||
an_error_occurred_while_creating_credit_with_the_TRAINING: "An error occurred while creating credit with the {{TRAINING}}." # angular interpolation
|
||||
not_set: "Not set"
|
||||
error_a_credit_linking_this_machine_with_that_subscription_already_exists: "Error : a credit linking this machine with that subscription already exists."
|
||||
changes_have_been_successfully_saved: "Changes have been successfully saved."
|
||||
credit_was_successfully_saved: "Credit was successfully saved."
|
||||
do_you_really_want_to_delete_this_subscription_plan: "Do you really want to delete this subscription plan?"
|
||||
subscription_plan_was_successfully_deleted: "Subscription plan was successfully deleted."
|
||||
unable_to_delete_the_specified_subscription_an_error_occurred: "Unable to delete the specified subscription, an error occurred."
|
||||
coupons: "Coupons"
|
||||
list_of_the_coupons: "List of the coupons"
|
||||
discount: "Discount"
|
||||
nb_of_usages: "Number of usages"
|
||||
status: "Status"
|
||||
add_a_new_coupon: "Add a new coupon"
|
||||
disabled: "Disabled"
|
||||
expired: "Expired"
|
||||
sold_out: "Sold out"
|
||||
active: "Active"
|
||||
confirmation_required: "Confirmation required"
|
||||
do_you_really_want_to_delete_this_coupon: "Do you really want to delete this coupon?"
|
||||
coupon_was_successfully_deleted: "Coupon was successfully deleted."
|
||||
unable_to_delete_the_specified_coupon_already_in_use: "Unable to delete the specified coupon: it is already used with some invoices."
|
||||
unable_to_delete_the_specified_coupon_an_unexpected_error_occurred: "Unable to delete the specified coupon: an unexpected error occurred."
|
||||
send_a_coupon: "Send a coupon"
|
||||
coupon: "Coupon"
|
||||
usages: "Usages"
|
||||
coupon_successfully_sent_to_USER: "Coupon successfully sent to {{USER}}" # angular interpolation
|
||||
an_error_occurred_unable_to_send_the_coupon: "An unexpected error prevent from sending the coupon."
|
||||
code: "Code"
|
||||
enabled: "Enabled"
|
||||
validity_per_user: "Validity per user"
|
||||
once: "Just once"
|
||||
forever: "Each use"
|
||||
valid_until: "Valid until (included)"
|
||||
spaces: "Spaces"
|
||||
these_prices_match_space_hours_rates_: "These prices match space hours rates"
|
||||
add_a_space_credit: "Add a Space credit"
|
||||
space: "Espace"
|
||||
error_a_credit_linking_this_space_with_that_subscription_already_exists: "Error : a credit linking this space with that subscription already exists."
|
||||
|
||||
coupons_new:
|
||||
# ajouter un code promotionnel
|
||||
@ -194,19 +227,24 @@ en:
|
||||
plans:
|
||||
new:
|
||||
# add a subscription plan on the platform
|
||||
add_a_subscription_plan: "Add a subscription plan"
|
||||
unable_to_create_the_subscription_please_try_again: "Unable to create the subscription plan. Please try again."
|
||||
successfully_created_subscription(s)_dont_forget_to_redefine_prices: "Subscription(s) successfully created. Don't forget to redefine prices."
|
||||
unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Unable to save this user. Check that there isn't an already defined user with the same name."
|
||||
new_plan:
|
||||
add_a_subscription_plan: "Add a subscription plan"
|
||||
unable_to_create_the_subscription_please_try_again: "Unable to create the subscription plan. Please try again."
|
||||
successfully_created_subscription(s)_dont_forget_to_redefine_prices: "Subscription(s) successfully created. Don't forget to redefine prices."
|
||||
unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Unable to save this user. Check that there isn't an already defined user with the same name."
|
||||
edit:
|
||||
# edit a subscription plan / machine hours prices
|
||||
subscription_plan: "Subscription plan:"
|
||||
prices: "Prices"
|
||||
copy_prices_from: "Copy prices from"
|
||||
machine: "Machine"
|
||||
hourly_rate: "Hourly rate"
|
||||
unable_to_save_subscription_changes_please_try_again: "Unable to save subscription changes. Please try again."
|
||||
subscription_successfully_changed: "Subscription successfully changed."
|
||||
edit_plan:
|
||||
subscription_plan: "Subscription plan:"
|
||||
prices: "Prices"
|
||||
copy_prices_from: "Copy prices from"
|
||||
machines: "Machines"
|
||||
machine: "Machine"
|
||||
hourly_rate: "Hourly rate"
|
||||
spaces: "Spaces"
|
||||
space: "Space"
|
||||
unable_to_save_subscription_changes_please_try_again: "Unable to save subscription changes. Please try again."
|
||||
subscription_successfully_changed: "Subscription successfully changed."
|
||||
|
||||
invoices:
|
||||
# list of all invoices & invoicing parameters
|
||||
@ -476,90 +514,95 @@ en:
|
||||
|
||||
settings:
|
||||
# global application parameters and customization
|
||||
customize_the_application: "Customize the application"
|
||||
general: "General"
|
||||
fablab_title: "FabLab title"
|
||||
fablab_name: "FabLab name"
|
||||
title_concordance: "Title concordance"
|
||||
male: "Male."
|
||||
female: "Female."
|
||||
eg: "eg:"
|
||||
about: "About"
|
||||
male_preposition: "the"
|
||||
female_preposition: "the"
|
||||
customize_information_messages: "Customize information messages"
|
||||
message_of_the_machine_booking_page: "Message of the machine booking page:"
|
||||
type_the_message_content: "Type the message content"
|
||||
warning_message_of_the_training_booking_page: "Warning message of the training booking page:"
|
||||
information_message_of_the_training_reservation_page: "Information message of the training reservation page:"
|
||||
message_of_the_subscriptions_page: "Message of the subscriptions page:"
|
||||
message_of_the_events_page: "Message of the events page:"
|
||||
legal_documents: "Legal documents"
|
||||
if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user: "If these documents are not filled, no consent about them will be asked."
|
||||
general_terms_and_conditions_(T&C): "General terms and conditions (T&C)"
|
||||
terms_of_service_(TOS): "Terms of service (TOS)"
|
||||
customize_the_graphics: "Customize the graphics"
|
||||
for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height: "For an optimal rendering, the logo image must be at the PNG format with a transparent background and an aspect ratio 3.5 wider than the height."
|
||||
concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "Concerning the favicon, it must be at ICO format with a size of 16x16 pixels."
|
||||
remember_to_refresh_the_page_for_the_changes_to_take_effect: "Remember to refresh the page for the changes to take effect."
|
||||
logo_(white_background): "Logo (white background)"
|
||||
change_the_logo: "Change the logo"
|
||||
logo_(black_background): "Logo (black background)"
|
||||
favicon: "Favicon"
|
||||
change_the_favicon: "Change the favicon"
|
||||
main_colour: "Main colour:"
|
||||
primary: "Primary"
|
||||
secondary_colour: "Secondary colour:"
|
||||
secondary: "Secondary"
|
||||
background_picture_of_the_profile_banner: "Background picture of the profile banner"
|
||||
change_the_profile_banner: "Change the profile banner"
|
||||
home_page: "Home page"
|
||||
news_of_the_home_page: "News of the home page:"
|
||||
type_your_news_here: "Type your news here"
|
||||
leave_it_empty_to_not_bring_up_any_news_on_the_home_page: "Leave it empty to not bring up any news on the home page"
|
||||
twitter_stream: "Twitter Stream:"
|
||||
name_of_the_twitter_account: "Name of the Twitter account"
|
||||
title_of_the_about_page: "Title of the About page"
|
||||
shift_enter_to_force_carriage_return: "SHIFT + ENTER to force carriage return"
|
||||
input_the_main_content: "Input the main content"
|
||||
input_the_fablab_contacts: "Input the FabLab contacts"
|
||||
reservations: "Reservations"
|
||||
reservations_parameters: "Reservations parameters"
|
||||
confine_the_booking_agenda: "Confine the booking agenda"
|
||||
opening_time: "Opening time"
|
||||
closing_time: "Closing time"
|
||||
ability_for_the_users_to_move_their_reservations: "Ability for the users to move their reservations"
|
||||
reservations_shifting: "Reservations shifting"
|
||||
prior_period_(hours): "Prior period (hours)"
|
||||
enabled: "Enabled"
|
||||
disabled: "Disabled"
|
||||
ability_for_the_users_to_cancel_their_reservations: "Ability for the users to cancel their reservations"
|
||||
reservations_cancelling: "Reservations cancelling"
|
||||
reservations_reminders: "Reservations reminders"
|
||||
notification_sending_before_the_reservation_occurs: "Notification sending before the reservation occurs"
|
||||
customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation
|
||||
file_successfully_updated: "File successfully updated."
|
||||
name_genre: "title concordance"
|
||||
machine_explications_alert: "explanation message on the machine reservation page"
|
||||
training_explications_alert: "explanation message on the training reservation page"
|
||||
training_information_message: "information message on the machine reservation page"
|
||||
subscription_explications_alert: "explanation message on the subscription page"
|
||||
main_color: "main colour"
|
||||
secondary_color: "secondary colour"
|
||||
home_blogpost: "homepage's brief"
|
||||
twitter_name: "Twitter feed name"
|
||||
about_title: "\"About\" page title"
|
||||
about_body: "\"About\" page content"
|
||||
about_contacts: "\"About\" page contacts"
|
||||
booking_window_start: "opening time"
|
||||
booking_window_end: "closing time"
|
||||
booking_move_enable: "reservation moving enabling"
|
||||
booking_move_delay: "preventive delay of moving"
|
||||
booking_cancel_enable: "reservation canceling enabling"
|
||||
booking_cancel_delay: "preventive delay of canceling"
|
||||
reminder_enable: "reservation reminding enabling"
|
||||
reminder_delay: "delay before sending the reminder"
|
||||
default_value_is_24_hours: "If the field is leaved empty: 24 hours."
|
||||
settings:
|
||||
title: "Title"
|
||||
customize_the_application: "Customize the application"
|
||||
general: "General"
|
||||
fablab_title: "FabLab title"
|
||||
fablab_name: "FabLab name"
|
||||
title_concordance: "Title concordance"
|
||||
male: "Male."
|
||||
female: "Female."
|
||||
eg: "eg:"
|
||||
about: "About"
|
||||
male_preposition: "the"
|
||||
female_preposition: "the"
|
||||
customize_information_messages: "Customize information messages"
|
||||
message_of_the_machine_booking_page: "Message of the machine booking page:"
|
||||
type_the_message_content: "Type the message content"
|
||||
warning_message_of_the_training_booking_page: "Warning message of the training booking page:"
|
||||
information_message_of_the_training_reservation_page: "Information message of the training reservation page:"
|
||||
message_of_the_subscriptions_page: "Message of the subscriptions page:"
|
||||
message_of_the_events_page: "Message of the events page:"
|
||||
message_of_the_spaces_page: "Message of the spaces page:"
|
||||
legal_documents: "Legal documents"
|
||||
if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user: "If these documents are not filled, no consent about them will be asked."
|
||||
general_terms_and_conditions_(T&C): "General terms and conditions (T&C)"
|
||||
terms_of_service_(TOS): "Terms of service (TOS)"
|
||||
customize_the_graphics: "Customize the graphics"
|
||||
for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height: "For an optimal rendering, the logo image must be at the PNG format with a transparent background and an aspect ratio 3.5 wider than the height."
|
||||
concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "Concerning the favicon, it must be at ICO format with a size of 16x16 pixels."
|
||||
remember_to_refresh_the_page_for_the_changes_to_take_effect: "Remember to refresh the page for the changes to take effect."
|
||||
logo_(white_background): "Logo (white background)"
|
||||
change_the_logo: "Change the logo"
|
||||
logo_(black_background): "Logo (black background)"
|
||||
favicon: "Favicon"
|
||||
change_the_favicon: "Change the favicon"
|
||||
main_colour: "Main colour:"
|
||||
primary: "Primary"
|
||||
secondary_colour: "Secondary colour:"
|
||||
secondary: "Secondary"
|
||||
background_picture_of_the_profile_banner: "Background picture of the profile banner"
|
||||
change_the_profile_banner: "Change the profile banner"
|
||||
home_page: "Home page"
|
||||
news_of_the_home_page: "News of the home page:"
|
||||
type_your_news_here: "Type your news here"
|
||||
leave_it_empty_to_not_bring_up_any_news_on_the_home_page: "Leave it empty to not bring up any news on the home page"
|
||||
twitter_stream: "Twitter Stream:"
|
||||
name_of_the_twitter_account: "Name of the Twitter account"
|
||||
title_of_the_about_page: "Title of the About page"
|
||||
shift_enter_to_force_carriage_return: "SHIFT + ENTER to force carriage return"
|
||||
input_the_main_content: "Input the main content"
|
||||
input_the_fablab_contacts: "Input the FabLab contacts"
|
||||
reservations: "Reservations"
|
||||
reservations_parameters: "Reservations parameters"
|
||||
confine_the_booking_agenda: "Confine the booking agenda"
|
||||
opening_time: "Opening time"
|
||||
closing_time: "Closing time"
|
||||
ability_for_the_users_to_move_their_reservations: "Ability for the users to move their reservations"
|
||||
reservations_shifting: "Reservations shifting"
|
||||
prior_period_(hours): "Prior period (hours)"
|
||||
enabled: "Enabled"
|
||||
disabled: "Disabled"
|
||||
ability_for_the_users_to_cancel_their_reservations: "Ability for the users to cancel their reservations"
|
||||
reservations_cancelling: "Reservations cancelling"
|
||||
reservations_reminders: "Reservations reminders"
|
||||
notification_sending_before_the_reservation_occurs: "Notification sending before the reservation occurs"
|
||||
customization_of_SETTING_successfully_saved: "Customization of the {{SETTING}} successfully saved." # angular interpolation
|
||||
file_successfully_updated: "File successfully updated."
|
||||
name_genre: "title concordance"
|
||||
machine_explications_alert: "explanation message on the machine reservation page"
|
||||
training_explications_alert: "explanation message on the training reservation page"
|
||||
training_information_message: "information message on the machine reservation page"
|
||||
subscription_explications_alert: "explanation message on the subscription page"
|
||||
event_explications_alert: "explanation message on the event reservation page"
|
||||
space_explications_alert: "explanation message on the space reservation page"
|
||||
main_color: "main colour"
|
||||
secondary_color: "secondary colour"
|
||||
home_blogpost: "homepage's brief"
|
||||
twitter_name: "Twitter feed name"
|
||||
about_title: "\"About\" page title"
|
||||
about_body: "\"About\" page content"
|
||||
about_contacts: "\"About\" page contacts"
|
||||
booking_window_start: "opening time"
|
||||
booking_window_end: "closing time"
|
||||
booking_move_enable: "reservation moving enabling"
|
||||
booking_move_delay: "preventive delay of moving"
|
||||
booking_cancel_enable: "reservation canceling enabling"
|
||||
booking_cancel_delay: "preventive delay of canceling"
|
||||
reminder_enable: "reservation reminding enabling"
|
||||
reminder_delay: "delay before sending the reminder"
|
||||
default_value_is_24_hours: "If the field is leaved empty: 24 hours."
|
||||
|
||||
open_api_clients:
|
||||
add_new_client: "Create new API client"
|
||||
@ -575,3 +618,17 @@ en:
|
||||
client_successfully_updated: "Client successfully updated."
|
||||
client_successfully_deleted: "Client successfully deleted."
|
||||
access_successfully_revoked: "Access successfully revoked."
|
||||
|
||||
space_new:
|
||||
# create a new space
|
||||
space_new:
|
||||
add_a_new_space: "Add a new space"
|
||||
watch_out_when_creating_a_new_space_its_prices_are_initialized_at_0_for_all_subscriptions: "Watch out! When creating a new space, its prices are initialized at 0 for all subscriptions."
|
||||
consider_changing_its_prices_before_creating_any_reservation_slot: "Consider changing its prices before creating any reservation slot."
|
||||
add_this_space: "Add this space"
|
||||
|
||||
space_edit:
|
||||
# modify an exiting space
|
||||
space_edit:
|
||||
edit_the_space_NAME: "Edit the space: {{NAME}}" # angular interpolation
|
||||
validate_the_changes: "Validate the changes"
|
||||
|
@ -4,7 +4,7 @@ fr:
|
||||
machines_new:
|
||||
# ajout d'une nouvelle machine
|
||||
declare_a_new_machine: "Déclarer une nouvelle machine"
|
||||
watch_out_when_creating_a_new_machine_its_prices_are_initialized_at_0_for_all_subscriptions: "Attention, lors de la création d'une machine, ses tarifs de réservation sont initialisés à zero pour tout les abonnements."
|
||||
watch_out_when_creating_a_new_machine_its_prices_are_initialized_at_0_for_all_subscriptions: "Attention, lors de la création d'une machine, ses tarifs de réservation sont initialisés à zero pour tous les abonnements."
|
||||
consider_changing_them_before_creating_any_reservation_slot: "Pensez à les modifier avant de créer des créneaux pour cette machine."
|
||||
|
||||
machines_edit:
|
||||
@ -13,28 +13,38 @@ fr:
|
||||
|
||||
calendar:
|
||||
# gestion des créneaux machines et formations
|
||||
calendar_management: "Gestion du calendrier"
|
||||
ongoing_reservations: "Réservations en cours"
|
||||
no_reservations: "Aucune réservation"
|
||||
do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir annuler la réservation de {USER}, le {DATE} à {TIME}, concernant {RESERVATION} ?" # messageFormat interpolation
|
||||
reservation_cancellation_failed: "L'annulation de la réservation a échouée."
|
||||
unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Impossible de supprimer la dernière machine du créneau. Supprimez plutôt le créneau."
|
||||
do_you_really_want_to_remove_MACHINE_from_this_slot: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir retirer \"{MACHINE}\" de ce créneau ?" # messageFormat interpolation
|
||||
this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing: "Ceci interdira toute nouvelle réservation de cette machine sur ce créneau mais n'annulera pas les réservation existantes."
|
||||
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."
|
||||
deletion_failed: "La suppression a échouée."
|
||||
DATE_slot: "Créneau du {{DATE}} :" # angular interpolation
|
||||
you_can_define_a_training_on_that_slot: "Vous pouvez définir une formation sur ce créneau :"
|
||||
link_a_training: "Associer une formation"
|
||||
or_: "Ou"
|
||||
_select_some_machines: "sélectionner des machines"
|
||||
number_of_tickets: "Nombre de places : "
|
||||
adjust_the_opening_hours: "Ajuster l'horaire"
|
||||
restrict_this_slot_with_labels_(optional): "Restreindre ce créneau avec des étiquettes (optionnel)"
|
||||
the_slot_START-END_has_been_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # angular interpolation
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation
|
||||
you_should_link_a_training_or_a_machine_to_this_slot: "Vous devriez associer une formation ou une machine à ce créneau."
|
||||
admin_calendar:
|
||||
calendar_management: "Gestion du calendrier"
|
||||
trainings: "Formations"
|
||||
machines: "Machines"
|
||||
spaces: "Espaces"
|
||||
ongoing_reservations: "Réservations en cours"
|
||||
no_reservations: "Aucune réservation"
|
||||
confirmation_required: "Confirmation requise"
|
||||
do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir annuler la réservation de {USER}, le {DATE} à {TIME}, concernant {RESERVATION} ?" # messageFormat interpolation
|
||||
reservation_was_successfully_cancelled: "La réservation a bien été annulée."
|
||||
reservation_cancellation_failed: "L'annulation de la réservation a échouée."
|
||||
unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Impossible de supprimer la dernière machine du créneau. Supprimez plutôt le créneau."
|
||||
do_you_really_want_to_remove_MACHINE_from_this_slot: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir retirer \"{MACHINE}\" de ce créneau ?" # messageFormat interpolation
|
||||
this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing: "Ceci interdira toute nouvelle réservation de cette machine sur ce créneau mais n'annulera pas les réservation existantes."
|
||||
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."
|
||||
deletion_failed: "La suppression a échouée."
|
||||
DATE_slot: "Créneau du {{DATE}} :" # angular interpolation
|
||||
what_kind_of_slot_do_you_want_to_create: "Quel type de créneau voulez-vous créer ?"
|
||||
training: "Formation"
|
||||
machine: "Machine"
|
||||
space: "Espace"
|
||||
next: "Suivant >"
|
||||
previous: "< Précédent"
|
||||
select_some_machines: "Sélectionnez des machines"
|
||||
number_of_tickets: "Nombre de places : "
|
||||
adjust_the_opening_hours: "Ajuster l'horaire"
|
||||
to_time: "à" # context: time. eg. "from 18:00 to 21:00"
|
||||
restrict_this_slot_with_labels_(optional): "Restreindre ce créneau avec des étiquettes (optionnel)"
|
||||
the_slot_START-END_has_been_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # angular interpolation
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation
|
||||
you_should_select_at_least_a_machine: "Vous devriez sélectionne au moins une machine pour ce créneau."
|
||||
|
||||
project_elements:
|
||||
# gestion des éléments constituant les projets
|
||||
@ -132,55 +142,78 @@ fr:
|
||||
|
||||
pricing:
|
||||
# gestion des abonnements, des tarifs, des crédits et des codes promo
|
||||
pricing_management: "Gestion de la tarification"
|
||||
list_of_the_subscription_plans: "Liste des formules d'abonnements"
|
||||
beware_the_subscriptions_are_disabled_on_this_application: "Attention, les abonnements sont désactivés sur cette application."
|
||||
you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager: "Vous pouvez tout de même en créer mais ils ne seront disponibles qu'après un redéploiement du projet par le responsable du serveur."
|
||||
for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later: "Pour des raisons de sécurité, veuillez ne pas créer d'abonnements si vous ne comptez pas les utiliser par la suite."
|
||||
add_a_new_subscription_plan: "Ajouter une nouvelle formule d'abonnement"
|
||||
duration: "Durée"
|
||||
prominence: "Importance"
|
||||
machine_hours: "Heures machines"
|
||||
these_prices_match_machine_hours_rates_: "Ces tarifs correspondent au prix d'une heure machine"
|
||||
_without_subscriptions: "sans abonnement"
|
||||
credits: "Crédits"
|
||||
related_trainings: "Formations associées"
|
||||
add_a_machine_credit: "Ajouter un crédit Machine"
|
||||
machine: "Machine"
|
||||
hours: "Heures"
|
||||
related_subscriptions: "Abonnements associés"
|
||||
please_specify_a_number: "Veuillez spécifier un nombre."
|
||||
none: "Aucune" # grammar note: concordance with "training".
|
||||
an_error_occurred_while_saving_the_number_of_credits: "Une erreur est survenue lors de l'enregistrement du nombre de crédits."
|
||||
an_error_occurred_while_deleting_credit_with_the_TRAINING: "Une erreur est survenue lors de la suppression du crédit avec la {{TRAINING}}." # angular interpolation
|
||||
an_error_occurred_unable_to_find_the_credit_to_revoke: "Une erreur est survenue : impossible de retrouver le crédit à enlever."
|
||||
an_error_occurred_while_creating_credit_with_the_TRAINING: "Une erreur est survenue lors de la création du crédit avec la {{TRAINING}}." # angular interpolation
|
||||
not_set: "Non défini"
|
||||
error_a_credit_linking_this_machine_with_that_subscription_already_exists: "Erreur : un crédit associant cette machine et cet abonnement existe déjà."
|
||||
changes_have_been_successfully_saved: "Les modifications ont bien été enregistrées."
|
||||
credit_was_successfully_saved: "Le crédit a bien été enregistré."
|
||||
do_you_really_want_to_delete_this_subscription_plan: "Êtes-vous sûr(e) de vouloir supprimer cette formule d'abonnement ?"
|
||||
subscription_plan_was_successfully_deleted: "La formule d'abonnement a bien été supprimée."
|
||||
unable_to_delete_the_specified_subscription_an_error_occurred: "Impossible de supprimer l'abonnement spécifié, une erreur s'est produite."
|
||||
coupons: "Codes promotionnels"
|
||||
list_of_the_coupons: "Liste des codes promotionnels"
|
||||
discount: "Réduction"
|
||||
nb_of_usages: "Nombre d'utilisations"
|
||||
status: "Statut"
|
||||
add_a_new_coupon: "Ajouter un code promotionnel"
|
||||
disabled: "Désactivé"
|
||||
expired: "Expiré"
|
||||
sold_out: "Épuisé"
|
||||
active: "Actif"
|
||||
do_you_really_want_to_delete_this_coupon: "Êtes-vous sûr(e) de vouloir supprimer ce code promotionnel ?"
|
||||
coupon_was_successfully_deleted: "Le code promotionnel a bien été supprimé."
|
||||
unable_to_delete_the_specified_coupon_already_in_use: "Impossible de supprimer le code promotionnel : il est utilisé dans des factures."
|
||||
unable_to_delete_the_specified_coupon_an_unexpected_error_occurred: "Impossible de supprimer le code promotionnel : une erreur inattendue s'est produite."
|
||||
send_a_coupon: "Envoyer un code promo"
|
||||
coupon: "Code promo"
|
||||
usages: "Utilisations"
|
||||
coupon_successfully_sent_to_USER: "Le code promotionnel a bien été envoyé à {{USER}}" # angular interpolation
|
||||
an_error_occurred_unable_to_send_the_coupon: "Une erreur inattendue a empêché l'envoi du code promotionnel."
|
||||
pricing:
|
||||
pricing_management: "Gestion de la tarification"
|
||||
subscriptions: "Abonnements"
|
||||
trainings: "Formations"
|
||||
list_of_the_subscription_plans: "Liste des formules d'abonnements"
|
||||
beware_the_subscriptions_are_disabled_on_this_application: "Attention, les abonnements sont désactivés sur cette application."
|
||||
you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager: "Vous pouvez tout de même en créer mais ils ne seront disponibles qu'après un redéploiement du projet par le responsable du serveur."
|
||||
for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later: "Pour des raisons de sécurité, veuillez ne pas créer d'abonnements si vous ne comptez pas les utiliser par la suite."
|
||||
add_a_new_subscription_plan: "Ajouter une nouvelle formule d'abonnement"
|
||||
type: "Type"
|
||||
partner: "Partenaire"
|
||||
standard: "Standard"
|
||||
name: "Nom"
|
||||
duration: "Durée"
|
||||
group: "Groupe"
|
||||
prominence: "Importance"
|
||||
price: "Prix"
|
||||
machine_hours: "Heures machines"
|
||||
these_prices_match_machine_hours_rates_: "Ces tarifs correspondent au prix d'une heure machine"
|
||||
_without_subscriptions: "sans abonnement"
|
||||
machines: "Machines"
|
||||
credits: "Crédits"
|
||||
subscription: "Abonnement"
|
||||
related_trainings: "Formations associées"
|
||||
add_a_machine_credit: "Ajouter un crédit Machine"
|
||||
machine: "Machine"
|
||||
hours: "Heures"
|
||||
related_subscriptions: "Abonnements associés"
|
||||
please_specify_a_number: "Veuillez spécifier un nombre."
|
||||
none: "Aucune" # grammar note: concordance with "training".
|
||||
an_error_occurred_while_saving_the_number_of_credits: "Une erreur est survenue lors de l'enregistrement du nombre de crédits."
|
||||
an_error_occurred_while_deleting_credit_with_the_TRAINING: "Une erreur est survenue lors de la suppression du crédit avec la {{TRAINING}}." # angular interpolation
|
||||
an_error_occurred_unable_to_find_the_credit_to_revoke: "Une erreur est survenue : impossible de retrouver le crédit à enlever."
|
||||
an_error_occurred_while_creating_credit_with_the_TRAINING: "Une erreur est survenue lors de la création du crédit avec la {{TRAINING}}." # angular interpolation
|
||||
not_set: "Non défini"
|
||||
error_a_credit_linking_this_machine_with_that_subscription_already_exists: "Erreur : un crédit associant cette machine et cet abonnement existe déjà."
|
||||
changes_have_been_successfully_saved: "Les modifications ont bien été enregistrées."
|
||||
credit_was_successfully_saved: "Le crédit a bien été enregistré."
|
||||
do_you_really_want_to_delete_this_subscription_plan: "Êtes-vous sûr(e) de vouloir supprimer cette formule d'abonnement ?"
|
||||
subscription_plan_was_successfully_deleted: "La formule d'abonnement a bien été supprimée."
|
||||
unable_to_delete_the_specified_subscription_an_error_occurred: "Impossible de supprimer l'abonnement spécifié, une erreur s'est produite."
|
||||
coupons: "Codes promotionnels"
|
||||
list_of_the_coupons: "Liste des codes promotionnels"
|
||||
discount: "Réduction"
|
||||
nb_of_usages: "Nombre d'utilisations"
|
||||
status: "Statut"
|
||||
add_a_new_coupon: "Ajouter un code promotionnel"
|
||||
disabled: "Désactivé"
|
||||
expired: "Expiré"
|
||||
sold_out: "Épuisé"
|
||||
active: "Actif"
|
||||
confirmation_required: "Confirmation requise"
|
||||
do_you_really_want_to_delete_this_coupon: "Êtes-vous sûr(e) de vouloir supprimer ce code promotionnel ?"
|
||||
coupon_was_successfully_deleted: "Le code promotionnel a bien été supprimé."
|
||||
unable_to_delete_the_specified_coupon_already_in_use: "Impossible de supprimer le code promotionnel : il est utilisé dans des factures."
|
||||
unable_to_delete_the_specified_coupon_an_unexpected_error_occurred: "Impossible de supprimer le code promotionnel : une erreur inattendue s'est produite."
|
||||
send_a_coupon: "Envoyer un code promo"
|
||||
coupon: "Code promo"
|
||||
usages: "Utilisations"
|
||||
coupon_successfully_sent_to_USER: "Le code promotionnel a bien été envoyé à {{USER}}" # angular interpolation
|
||||
an_error_occurred_unable_to_send_the_coupon: "Une erreur inattendue a empêché l'envoi du code promotionnel."
|
||||
code: "Code"
|
||||
enabled: "Activé"
|
||||
validity_per_user: "Validité par utilisateur"
|
||||
once: "Une seule fois"
|
||||
forever: "À chaque utilisation"
|
||||
valid_until: "Valable jusqu'au (inclus)"
|
||||
spaces: "Espaces"
|
||||
these_prices_match_space_hours_rates_: "Ces tarifs correspondent au prix d'une heure espace"
|
||||
add_a_space_credit: "Ajouter un crédit Espace"
|
||||
space: "Espace"
|
||||
error_a_credit_linking_this_space_with_that_subscription_already_exists: "Erreur : un crédit associant cet espace et cet abonnement existe déjà."
|
||||
|
||||
coupons_new:
|
||||
# ajouter un code promotionnel
|
||||
@ -194,19 +227,24 @@ fr:
|
||||
plans:
|
||||
new:
|
||||
# ajouter une formule d'abonnement sur la plate-forme
|
||||
add_a_subscription_plan: "Ajouter une formule d'abonnement"
|
||||
unable_to_create_the_subscription_please_try_again: "L'abonnement n'a pas pu être créé. Veuillez réessayer."
|
||||
successfully_created_subscription(s)_dont_forget_to_redefine_prices: "Création du/des abonnement(s) réussie. N'oubliez pas de redéfinir les tarifs."
|
||||
unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Impossible d'enregistrer cet utilisateur. Vérifiez qu'il n'existe pas déjà un utilisateur du même nom."
|
||||
new_plan:
|
||||
add_a_subscription_plan: "Ajouter une formule d'abonnement"
|
||||
unable_to_create_the_subscription_please_try_again: "L'abonnement n'a pas pu être créé. Veuillez réessayer."
|
||||
successfully_created_subscription(s)_dont_forget_to_redefine_prices: "Création du/des abonnement(s) réussie. N'oubliez pas de redéfinir les tarifs."
|
||||
unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Impossible d'enregistrer cet utilisateur. Vérifiez qu'il n'existe pas déjà un utilisateur du même nom."
|
||||
edit:
|
||||
# modifier une formule d'abonnement / les prix des heures machines
|
||||
subscription_plan: "Formule d'abonnement :"
|
||||
prices: "Tarifs"
|
||||
copy_prices_from: "Copier les prix depuis"
|
||||
machine: "Machine"
|
||||
hourly_rate: "Tarif horaire"
|
||||
unable_to_save_subscription_changes_please_try_again: "Les modifications de l'abonnement n'ont pas pu être enregistrées. Veuillez réessayer."
|
||||
subscription_successfully_changed: "Modification de l'abonnement réussie."
|
||||
edit_plan:
|
||||
subscription_plan: "Formule d'abonnement :"
|
||||
prices: "Tarifs"
|
||||
copy_prices_from: "Copier les prix depuis"
|
||||
machines: "Machines"
|
||||
machine: "Machine"
|
||||
hourly_rate: "Tarif horaire"
|
||||
spaces: "Espaces"
|
||||
space: "Espace"
|
||||
unable_to_save_subscription_changes_please_try_again: "Les modifications de l'abonnement n'ont pas pu être enregistrées. Veuillez réessayer."
|
||||
subscription_successfully_changed: "Modification de l'abonnement réussie."
|
||||
|
||||
invoices:
|
||||
# liste de toutes les factures & paramètres de facturation
|
||||
@ -476,90 +514,95 @@ fr:
|
||||
|
||||
settings:
|
||||
# paramètres globaux de l'application et personnalisation
|
||||
customize_the_application: "Personnalisation de l'application"
|
||||
general: "Général"
|
||||
fablab_title: "Titre du FabLab"
|
||||
fablab_name: "Nom du FabLab"
|
||||
title_concordance: "Accord du titre"
|
||||
male: "Masculin."
|
||||
female: "Féminin."
|
||||
eg: "ex :"
|
||||
about: "A propos"
|
||||
male_preposition: "du"
|
||||
female_preposition: "de la"
|
||||
customize_information_messages: "Personnaliser les messages d'informations"
|
||||
message_of_the_machine_booking_page: "Message sur la page de réservation d'une machine :"
|
||||
type_the_message_content: "Saisir le contenu du message"
|
||||
warning_message_of_the_training_booking_page: "Message d'avertissement sur la page de réservation d'une formation :"
|
||||
information_message_of_the_training_reservation_page: "Message d'information sur la page de réservation d'une formation :"
|
||||
message_of_the_subscriptions_page: "Message sur la page des abonnements :"
|
||||
message_of_the_events_page: "Message sur la page des évènements :"
|
||||
legal_documents: "Documents légaux"
|
||||
if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user: "Si ces documents ne sont pas renseignés, aucun consentement à leur sujet ne sera demandé à l'utilisateur."
|
||||
general_terms_and_conditions_(T&C): "Conditions générales de vente (CGV)"
|
||||
terms_of_service_(TOS): "Conditions générales d'utilisation (CGU)"
|
||||
customize_the_graphics: "Personnaliser la charte graphique"
|
||||
for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height: "Pour un rendu optimal, l'image du logo doit être au format PNG avec un fond transparent et d'un aspect environ 3,5 fois plus long que haut."
|
||||
concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "La favicon devrait quant à elle être au format ICO et d'une taille de 16x16 pixels."
|
||||
remember_to_refresh_the_page_for_the_changes_to_take_effect: "Pensez à rafraîchir la page pour que les modifications prennent effet."
|
||||
logo_(white_background): "Logo (fond blanc)"
|
||||
change_the_logo: "Changer le logo"
|
||||
logo_(black_background): "Logo (font noir)"
|
||||
favicon: "Favicon"
|
||||
change_the_favicon: "Changer la favicon"
|
||||
main_colour: "Couleur principale :"
|
||||
primary: "Primaire"
|
||||
secondary_colour: "Couleur secondaire :"
|
||||
secondary: "Secondaire"
|
||||
background_picture_of_the_profile_banner: "Image de fond du bandeau de profil"
|
||||
change_the_profile_banner: "Changer le bandeau de profil"
|
||||
home_page: "Page d'accueil"
|
||||
news_of_the_home_page: "Brève de la page d'accueil :"
|
||||
type_your_news_here: "Saisir votre brève ici"
|
||||
leave_it_empty_to_not_bring_up_any_news_on_the_home_page: "Laisser vide pour ne pas faire apparaître de brève sur la page d'accueil"
|
||||
twitter_stream: "Flux Twitter :"
|
||||
name_of_the_twitter_account: "Nom du compte Twitter"
|
||||
title_of_the_about_page: "Titre page A propos"
|
||||
shift_enter_to_force_carriage_return: "MAJ. + ENTRÉE pour forcer le retour à la ligne"
|
||||
input_the_main_content: "Saisir le contenu principal"
|
||||
input_the_fablab_contacts: "Saisir les Contacts du FabLab"
|
||||
reservations: "Réservations"
|
||||
reservations_parameters: "Paramètres des réservations"
|
||||
confine_the_booking_agenda: "Borner l'agenda de réservation"
|
||||
opening_time: "Heure d'ouverture"
|
||||
closing_time: "Heure de fermeture"
|
||||
ability_for_the_users_to_move_their_reservations: "Possibilité pour l'utilisateur de déplacer ses réservations"
|
||||
reservations_shifting: "Déplacement des réservations"
|
||||
prior_period_(hours): "Délai préalable (en heures)"
|
||||
enabled: "Activé"
|
||||
disabled: "Désactivé"
|
||||
ability_for_the_users_to_cancel_their_reservations: "Possibilité pour l'utilisateur d'annuler ses réservations"
|
||||
reservations_cancelling: "Annulation des réservations"
|
||||
reservations_reminders: "Rappel des réservations"
|
||||
notification_sending_before_the_reservation_occurs: "Envoi de notification avant l'avènement de la réservation"
|
||||
customization_of_SETTING_successfully_saved: "La personnalisation de {{SETTING}} a bien été enregistrée." # angular interpolation
|
||||
file_successfully_updated: "Le fichier a bien été mis à jour."
|
||||
name_genre: "l'accord du nom"
|
||||
machine_explications_alert: "l'explication sur la page de réservation d'une machine"
|
||||
training_explications_alert: "l'explication sur la page de réservation d'une formation"
|
||||
training_information_message: "l'information sur la page de réservation d'une formation"
|
||||
subscription_explications_alert: "l'explication sur la page de souscription à un abonnement"
|
||||
main_color: "la couleur principale"
|
||||
secondary_color: "la couleur secondaire"
|
||||
home_blogpost: "la brève de la page d'accueil"
|
||||
twitter_name: "nom du flux Twitter"
|
||||
about_title: "titre de la page \"À propos\""
|
||||
about_body: "corps de la page \"À propos\""
|
||||
about_contacts: "contacts sur la page \"À propos\""
|
||||
booking_window_start: "l'heure d'ouverture"
|
||||
booking_window_end: "l'heure de fermeture"
|
||||
booking_move_enable: "l'activation du déplacement de réservation"
|
||||
booking_move_delay: "délai préventif de déplacement"
|
||||
booking_cancel_enable: "l'activation de l'annulation de réservation"
|
||||
booking_cancel_delay: "délai préventif d'annulation"
|
||||
reminder_enable: "l'activation du rappel de réservation"
|
||||
reminder_delay: "délai avant envoi de la notification de rappel"
|
||||
default_value_is_24_hours: "Si aucune valeur n'est renseignée : 24 heures."
|
||||
settings:
|
||||
title: "Titre"
|
||||
customize_the_application: "Personnalisation de l'application"
|
||||
general: "Général"
|
||||
fablab_title: "Titre du FabLab"
|
||||
fablab_name: "Nom du FabLab"
|
||||
title_concordance: "Accord du titre"
|
||||
male: "Masculin."
|
||||
female: "Féminin."
|
||||
eg: "ex :"
|
||||
about: "A propos"
|
||||
male_preposition: "du"
|
||||
female_preposition: "de la"
|
||||
customize_information_messages: "Personnaliser les messages d'informations"
|
||||
message_of_the_machine_booking_page: "Message sur la page de réservation d'une machine :"
|
||||
type_the_message_content: "Saisir le contenu du message"
|
||||
warning_message_of_the_training_booking_page: "Message d'avertissement sur la page de réservation d'une formation :"
|
||||
information_message_of_the_training_reservation_page: "Message d'information sur la page de réservation d'une formation :"
|
||||
message_of_the_subscriptions_page: "Message sur la page des abonnements :"
|
||||
message_of_the_events_page: "Message sur la page des évènements :"
|
||||
message_of_the_spaces_page: "Message sur la page des espaces :"
|
||||
legal_documents: "Documents légaux"
|
||||
if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user: "Si ces documents ne sont pas renseignés, aucun consentement à leur sujet ne sera demandé à l'utilisateur."
|
||||
general_terms_and_conditions_(T&C): "Conditions générales de vente (CGV)"
|
||||
terms_of_service_(TOS): "Conditions générales d'utilisation (CGU)"
|
||||
customize_the_graphics: "Personnaliser la charte graphique"
|
||||
for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height: "Pour un rendu optimal, l'image du logo doit être au format PNG avec un fond transparent et d'un aspect environ 3,5 fois plus long que haut."
|
||||
concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "La favicon devrait quant à elle être au format ICO et d'une taille de 16x16 pixels."
|
||||
remember_to_refresh_the_page_for_the_changes_to_take_effect: "Pensez à rafraîchir la page pour que les modifications prennent effet."
|
||||
logo_(white_background): "Logo (fond blanc)"
|
||||
change_the_logo: "Changer le logo"
|
||||
logo_(black_background): "Logo (font noir)"
|
||||
favicon: "Favicon"
|
||||
change_the_favicon: "Changer la favicon"
|
||||
main_colour: "Couleur principale :"
|
||||
primary: "Primaire"
|
||||
secondary_colour: "Couleur secondaire :"
|
||||
secondary: "Secondaire"
|
||||
background_picture_of_the_profile_banner: "Image de fond du bandeau de profil"
|
||||
change_the_profile_banner: "Changer le bandeau de profil"
|
||||
home_page: "Page d'accueil"
|
||||
news_of_the_home_page: "Brève de la page d'accueil :"
|
||||
type_your_news_here: "Saisir votre brève ici"
|
||||
leave_it_empty_to_not_bring_up_any_news_on_the_home_page: "Laisser vide pour ne pas faire apparaître de brève sur la page d'accueil"
|
||||
twitter_stream: "Flux Twitter :"
|
||||
name_of_the_twitter_account: "Nom du compte Twitter"
|
||||
title_of_the_about_page: "Titre page A propos"
|
||||
shift_enter_to_force_carriage_return: "MAJ. + ENTRÉE pour forcer le retour à la ligne"
|
||||
input_the_main_content: "Saisir le contenu principal"
|
||||
input_the_fablab_contacts: "Saisir les Contacts du FabLab"
|
||||
reservations: "Réservations"
|
||||
reservations_parameters: "Paramètres des réservations"
|
||||
confine_the_booking_agenda: "Borner l'agenda de réservation"
|
||||
opening_time: "Heure d'ouverture"
|
||||
closing_time: "Heure de fermeture"
|
||||
ability_for_the_users_to_move_their_reservations: "Possibilité pour l'utilisateur de déplacer ses réservations"
|
||||
reservations_shifting: "Déplacement des réservations"
|
||||
prior_period_(hours): "Délai préalable (en heures)"
|
||||
enabled: "Activé"
|
||||
disabled: "Désactivé"
|
||||
ability_for_the_users_to_cancel_their_reservations: "Possibilité pour l'utilisateur d'annuler ses réservations"
|
||||
reservations_cancelling: "Annulation des réservations"
|
||||
reservations_reminders: "Rappel des réservations"
|
||||
notification_sending_before_the_reservation_occurs: "Envoi de notification avant l'avènement de la réservation"
|
||||
customization_of_SETTING_successfully_saved: "La personnalisation de {{SETTING}} a bien été enregistrée." # angular interpolation
|
||||
file_successfully_updated: "Le fichier a bien été mis à jour."
|
||||
name_genre: "l'accord du nom"
|
||||
machine_explications_alert: "l'explication sur la page de réservation d'une machine"
|
||||
training_explications_alert: "l'explication sur la page de réservation d'une formation"
|
||||
training_information_message: "l'information sur la page de réservation d'une formation"
|
||||
subscription_explications_alert: "l'explication sur la page de souscription à un abonnement"
|
||||
event_explications_alert: "l'explication sur la page de réservation d'un évènement"
|
||||
space_explications_alert: "l'explication sur la page de réservation d'un espace"
|
||||
main_color: "la couleur principale"
|
||||
secondary_color: "la couleur secondaire"
|
||||
home_blogpost: "la brève de la page d'accueil"
|
||||
twitter_name: "nom du flux Twitter"
|
||||
about_title: "titre de la page \"À propos\""
|
||||
about_body: "corps de la page \"À propos\""
|
||||
about_contacts: "contacts sur la page \"À propos\""
|
||||
booking_window_start: "l'heure d'ouverture"
|
||||
booking_window_end: "l'heure de fermeture"
|
||||
booking_move_enable: "l'activation du déplacement de réservation"
|
||||
booking_move_delay: "délai préventif de déplacement"
|
||||
booking_cancel_enable: "l'activation de l'annulation de réservation"
|
||||
booking_cancel_delay: "délai préventif d'annulation"
|
||||
reminder_enable: "l'activation du rappel de réservation"
|
||||
reminder_delay: "délai avant envoi de la notification de rappel"
|
||||
default_value_is_24_hours: "Si aucune valeur n'est renseignée : 24 heures."
|
||||
|
||||
open_api_clients:
|
||||
add_new_client: "Créer un compte client"
|
||||
@ -575,3 +618,17 @@ fr:
|
||||
client_successfully_updated: "Les modifications ont été enregistrées."
|
||||
client_successfully_deleted: "Le compte client a bien été supprimé."
|
||||
access_successfully_revoked: "L'accès a bien été revoqué."
|
||||
|
||||
space_new:
|
||||
# créer un nouvel espace
|
||||
space_new:
|
||||
add_a_new_space: "Ajouter un nouvel espace"
|
||||
watch_out_when_creating_a_new_space_its_prices_are_initialized_at_0_for_all_subscriptions: "Attention, lors de la création d'un espace, ses tarifs de réservation sont initialisés à zero pour tous les abonnements."
|
||||
consider_changing_its_prices_before_creating_any_reservation_slot: "Pensez à modifier ces prix avant de créer des créneaux pour cet espace."
|
||||
add_this_space: "Ajouter cet espace"
|
||||
|
||||
space_edit:
|
||||
# modifier un espace existant
|
||||
space_edit:
|
||||
edit_the_space_NAME: "Modifier l'espace : {{NAME}}" # angular interpolation
|
||||
validate_the_changes: "Valider les modifications"
|
||||
|
@ -98,67 +98,27 @@ en:
|
||||
machines_reserve:
|
||||
# book a machine
|
||||
machine_planning: "Machine planning"
|
||||
select_one_or_more_slots_in_the_calendar: "Select one or more slots in the calendar"
|
||||
you_ve_just_selected_the_slot: "You've just selected the slot:"
|
||||
datetime_to_time: "{{START_DATETIME}} to {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM
|
||||
cost_of_a_machine_hour: "Cost of a machine hour"
|
||||
offer_this_slot: "Offer this slot"
|
||||
confirm_this_slot: "Confirm this slot"
|
||||
remove_this_slot: "Remove this slot"
|
||||
to_benefit_from_attractive_prices: "To benefit from attractive prices"
|
||||
view_our_subscriptions: "View our subscriptions"
|
||||
cost_of_the_subscription: "Cost of the subscription"
|
||||
you_have_settled_the_following_machine_hours: "You have settled the following machine hours:"
|
||||
you_have_settled_a_: "You have settled a"
|
||||
i_want_to_change_the_following_reservation: "I want to change the following reservation:"
|
||||
cancel_my_modification: "Cancel my modification"
|
||||
select_a_new_slot_in_the_calendar: "Select a new slot in the calendar"
|
||||
cancel_my_selection: "Cancel my selection"
|
||||
tags_of_the_original_slot: "Tags of the original slot:"
|
||||
tags_of_the_destination_slot: "Tags of the destination slot:"
|
||||
confirm_my_modification: "Confirm my modification"
|
||||
your_booking_slot_was_successfully_moved_from_: "Your booking slot was successfully moved from"
|
||||
i_ve_reserved: "J'ai réservé"
|
||||
i_ve_reserved: "I've reserved"
|
||||
not_available: "Not available"
|
||||
unable_to_change_the_reservation: "Unable to change the reservation"
|
||||
i_reserve: "I reserve"
|
||||
i_shift: "I shift"
|
||||
i_change: "I change"
|
||||
do_you_really_want_to_cancel_this_reservation: "So you really want to cancel this reservation?"
|
||||
reservation_was_cancelled_successfully: "Reservation was cancelled successfully."
|
||||
cancellation_failed: "Cancellation failed."
|
||||
a_problem_occured_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later."
|
||||
|
||||
trainings_reserve:
|
||||
# book a training
|
||||
trainings_planning: "Trainings planning"
|
||||
planning_of: "Planning of" # followed by the training name (eg. "Planning of 3d printer training")
|
||||
all_trainings: "All trainings"
|
||||
select_a_slot_in_the_calendar: "Select a slot in the calendar"
|
||||
you_ve_just_selected_the_slot: "You've just selected the slot:"
|
||||
datetime_to_time: "{{START_DATETIME}} to {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM
|
||||
offer_this_training: "Offer this training"
|
||||
confirm_this_slot: "Confirm this slot"
|
||||
remove_this_slot: "Remove this slot"
|
||||
to_benefit_from_attractive_prices_and_a_free_training: "To benefit from attractives prices and a free training"
|
||||
view_our_subscriptions: "View our subscriptions"
|
||||
subscription_cost: "Subscription cost"
|
||||
you_have_settled_the_training: "You have settled the training"
|
||||
training_cost_: "Training cost:"
|
||||
you_have_settled_a_: "You have settled a"
|
||||
i_want_to_change_the_following_reservation: "I want to change the following reservation:"
|
||||
cancel_my_modification: "Cancel my modification"
|
||||
select_a_new_slot_in_the_calendar: "Select a new slot in the calendar"
|
||||
cancel_my_selection: "Cancel my selection"
|
||||
confirm_my_modification: "Confirm my modification"
|
||||
your_booking_slot_was_successfully_moved_from_: "Your booking slot was successfully moved from"
|
||||
i_ve_reserved: "I've reserved"
|
||||
an_error_occured_preventing_the_booked_slot_from_being_modified: "An error occurred preventing the booked slot from being modified."
|
||||
i_shift: "I shift"
|
||||
i_change: "I change"
|
||||
do_you_really_want_to_cancel_this_reservation: "Do you really want to cancel this reservation?"
|
||||
cancellation_failed: "Cancellation failed."
|
||||
a_problem_occured_during_the_payment_process_please_try_again_later: "A problem occured during the payment process. Please try again later."
|
||||
|
||||
space_reserve:
|
||||
# book a space
|
||||
space_reserve:
|
||||
planning_of_space_NAME: "Planning of the {{NAME}} space" # angular interpolation
|
||||
i_ve_reserved: "I've reserved"
|
||||
i_shift: "I shift"
|
||||
i_change: "I change"
|
||||
|
||||
notifications:
|
||||
notifications_center: "Notifications center"
|
||||
|
@ -98,67 +98,27 @@ fr:
|
||||
machines_reserve:
|
||||
# réserver une machine
|
||||
machine_planning: "Planning machine"
|
||||
select_one_or_more_slots_in_the_calendar: "Sélectionnez un ou plusieurs créneaux dans le calendrier"
|
||||
you_ve_just_selected_the_slot: "Vous venez de sélectionner le créneau :"
|
||||
datetime_to_time: "{{START_DATETIME}} à {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM
|
||||
cost_of_a_machine_hour: "Coût de l'heure machine"
|
||||
offer_this_slot: "Offrir ce créneau"
|
||||
confirm_this_slot: "Valider ce créneau"
|
||||
remove_this_slot: "Supprimer ce créneau"
|
||||
to_benefit_from_attractive_prices: "Pour bénéficier de prix avantageux"
|
||||
view_our_subscriptions: "Consultez nos abonnements"
|
||||
cost_of_the_subscription: "Coût de l'abonnement"
|
||||
you_have_settled_the_following_machine_hours: "Vous avez réglé les heures machines suivantes :"
|
||||
you_have_settled_a_: "Vous avez réglé un"
|
||||
i_want_to_change_the_following_reservation: "Je souhaite modifier ma réservation suivante :"
|
||||
cancel_my_modification: "Annuler ma modification"
|
||||
select_a_new_slot_in_the_calendar: "Sélectionnez un nouveau créneau dans le calendrier"
|
||||
cancel_my_selection: "Annuler ma sélection"
|
||||
tags_of_the_original_slot: "Étiquettes du créneau d'origine :"
|
||||
tags_of_the_destination_slot: "Étiquettes du créneau de destination :"
|
||||
confirm_my_modification: "Valider ma modification"
|
||||
your_booking_slot_was_successfully_moved_from_: "Votre créneau de réservation a bien été déplacé du"
|
||||
i_ve_reserved: "J'ai réservé"
|
||||
not_available: "Non disponible"
|
||||
unable_to_change_the_reservation: "Impossible de modifier la réservation"
|
||||
i_reserve: "Je réserve"
|
||||
i_shift: "Je déplace"
|
||||
i_change: "Je change"
|
||||
do_you_really_want_to_cancel_this_reservation: "Êtes-vous sur de vouloir annuler cette réservation ?"
|
||||
reservation_was_cancelled_successfully: "La réservation a bien été annulée."
|
||||
cancellation_failed: "L'annulation a échoué."
|
||||
a_problem_occured_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."
|
||||
|
||||
trainings_reserve:
|
||||
# réserver une formation
|
||||
trainings_planning: "Planning formations"
|
||||
planning_of: "Planning de la" # suivi du nom de la formation (eg. "Planning de la formation imprimante 3d")
|
||||
all_trainings: "Toutes les formations"
|
||||
select_a_slot_in_the_calendar: "Sélectionnez un créneau dans le calendrier"
|
||||
you_ve_just_selected_the_slot: "Vous venez de sélectionner le créneau :"
|
||||
datetime_to_time: "{{START_DATETIME}} à {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM
|
||||
offer_this_training: "Offrir cette formation"
|
||||
confirm_this_slot: "Valider ce créneau"
|
||||
remove_this_slot: "Supprimer ce créneau"
|
||||
to_benefit_from_attractive_prices_and_a_free_training: "Pour bénéficier de prix avantageux et d'une formation offerte"
|
||||
view_our_subscriptions: "Consultez nos abonnements"
|
||||
subscription_cost: "Coût de l'abonnement"
|
||||
you_have_settled_the_training: "Vous avez réglé la formation"
|
||||
training_cost_: "Coût de la formation :"
|
||||
you_have_settled_a_: "Vous avez réglé un"
|
||||
i_want_to_change_the_following_reservation: "Je souhaite modifier ma réservation suivante :"
|
||||
cancel_my_modification: "Annuler ma modification"
|
||||
select_a_new_slot_in_the_calendar: "Sélectionnez un nouveau créneau dans le calendrier"
|
||||
cancel_my_selection: "Annuler ma sélection"
|
||||
confirm_my_modification: "Valider ma modification"
|
||||
your_booking_slot_was_successfully_moved_from_: "Votre créneau de réservation a bien été déplacé du"
|
||||
i_ve_reserved: "J'ai réservé"
|
||||
an_error_occured_preventing_the_booked_slot_from_being_modified: "Une erreur est survenue, empêchant la modification du créneau réservé."
|
||||
i_shift: "Je déplace"
|
||||
i_change: "Je change"
|
||||
do_you_really_want_to_cancel_this_reservation: "Êtes-vous sur de vouloir annuler cette réservation ?"
|
||||
cancellation_failed: "L'annulation a échoué."
|
||||
a_problem_occured_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."
|
||||
|
||||
space_reserve:
|
||||
# réserver un espace
|
||||
space_reserve:
|
||||
planning_of_space_NAME: "Planning de l'espace {{NAME}}" # angular interpolation
|
||||
i_ve_reserved: "J'ai réservé"
|
||||
i_shift: "Je déplace"
|
||||
i_change: "Je change"
|
||||
|
||||
notifications:
|
||||
notifications_center: "Centre de notifications"
|
||||
|
@ -31,6 +31,7 @@ en:
|
||||
reserve_a_machine: "Reserve a Machine"
|
||||
trainings_registrations: "Trainings registrations"
|
||||
events_registrations: "Events registrations"
|
||||
reserve_a_space: "Reserve a Space"
|
||||
projects_gallery: "Projects gallery"
|
||||
subscriptions: "Subscriptions"
|
||||
public_calendar: "Calendar"
|
||||
@ -44,6 +45,7 @@ en:
|
||||
subscriptions_and_prices: "Subscriptions and Prices"
|
||||
manage_the_events: "Manage the events"
|
||||
manage_the_machines: "Manage the Machines"
|
||||
manage_the_spaces: "Manage the Spaces"
|
||||
manage_the_projects_elements: "Manage the Projects Elements"
|
||||
statistics: "Statistics"
|
||||
customization: "Customization"
|
||||
@ -246,6 +248,29 @@ en:
|
||||
you_can_shift_this_reservation_on_the_following_slots: "You can shift this reservation on the following slots:"
|
||||
|
||||
calendar:
|
||||
calendar: "Calendar"
|
||||
show_no_disponible: "Show the slots no disponibles"
|
||||
filter-calendar: "Filter calendar"
|
||||
# public calendar
|
||||
calendar:
|
||||
calendar: "Calendar"
|
||||
show_unavailables: "Show unavailable slots"
|
||||
filter_calendar: "Filter calendar"
|
||||
trainings: "Trainings"
|
||||
machines: "Machines"
|
||||
spaces: "Spaces"
|
||||
events: "Events"
|
||||
|
||||
spaces_list:
|
||||
# list of spaces
|
||||
the_spaces: "The spaces"
|
||||
add_a_space: "Add a space"
|
||||
|
||||
space_show:
|
||||
# display the details of a space
|
||||
space_show:
|
||||
book_this_space: "Book this space"
|
||||
unauthorized_operation: "Unauthorized operation"
|
||||
confirmation_required: "Confirmation required"
|
||||
do_you_really_want_to_delete_this_space: "Do you really want to delete this space?"
|
||||
the_space_cant_be_deleted_because_it_is_already_reserved_by_some_users: "Unable to delete this space, because it is already reserved by some users."
|
||||
characteristics: "Characteristics"
|
||||
files_to_download: "Files to download"
|
||||
projects_using_the_space: "Projects using the space"
|
@ -31,6 +31,7 @@ fr:
|
||||
reserve_a_machine: "Réserver une machine"
|
||||
trainings_registrations: "Inscriptions formations"
|
||||
events_registrations: "Inscriptions aux évènements"
|
||||
reserve_a_space: "Réserver un espace"
|
||||
projects_gallery: "Galerie de projets"
|
||||
subscriptions: "Abonnements"
|
||||
public_calendar: "Calendrier"
|
||||
@ -44,6 +45,7 @@ fr:
|
||||
subscriptions_and_prices: "Abonnements & Tarifs"
|
||||
manage_the_events: "Gérer les évènements"
|
||||
manage_the_machines: "Gérer les machines"
|
||||
manage_the_spaces: "Gérer les espaces"
|
||||
manage_the_projects_elements: "Gérer les éléments projets"
|
||||
statistics: "Statistiques"
|
||||
customization: "Personnalisation"
|
||||
@ -248,6 +250,29 @@ fr:
|
||||
you_can_shift_this_reservation_on_the_following_slots: "Vous pouvez déplacer cette réservation sur les créneaux suivants :"
|
||||
|
||||
calendar:
|
||||
calendar: "Calendrier"
|
||||
show_no_disponible: "Afficher les crénaux non disponibles"
|
||||
filter-calendar: "Filtrer le calendrier"
|
||||
# calendrier publique
|
||||
calendar:
|
||||
calendar: "Calendrier"
|
||||
show_unavailables: "Afficher les créneaux non disponibles"
|
||||
filter_calendar: "Filtrer le calendrier"
|
||||
trainings: "Formations"
|
||||
machines: "Machines"
|
||||
spaces: "Espaces"
|
||||
events: "Évènements"
|
||||
|
||||
spaces_list:
|
||||
# liste des espaces
|
||||
the_spaces: "Les espaces"
|
||||
add_a_space: "Ajouter un espace"
|
||||
|
||||
space_show:
|
||||
# affichage des détails d'un espace
|
||||
space_show:
|
||||
book_this_space: "Réserver cet espace"
|
||||
unauthorized_operation: "Opération non autorisée"
|
||||
confirmation_required: "Confirmation requise"
|
||||
do_you_really_want_to_delete_this_space: "Voulez-vous vraiment supprimer cet espace ?"
|
||||
the_space_cant_be_deleted_because_it_is_already_reserved_by_some_users: "L'espace ne peut pas être supprimé car il a déjà été réservé par des utilisateurs."
|
||||
characteristics: "Caractéristiques"
|
||||
files_to_download: "Fichiers à télécharger"
|
||||
projects_using_the_space: "Projets utilisant l'espace"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user