1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-17 06:52:27 +01:00

Merge branch 'dev' into l10n_dev

This commit is contained in:
Sylvain 2020-04-29 17:12:51 +02:00 committed by GitHub
commit ad52f825fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 1664 additions and 1021 deletions

View File

@ -2,13 +2,22 @@
- Interface to manage partners
- Ability to define, per availability, a custom duration for the reservation slots
- Corrected the documentation about BOOK_SLOT_AT_SAME_TIME
- Fix a bug: unable to change group if the previous was deactivated
- Fix a bug: unable to create events or trainings that are not multiples of SLOT_DURATION
- Fix a bug: unable to delete an unreserved event
- Fix a bug: "Free entry" label for events without reservation
- Fix a bug: updating a setting without any changes triggers an error
- Fix a bug: plan edition does not show the associated group
- Fix a bug: subscription page shows the groups without any active plans
- Fix a bug: cart price inconsistently updated after a subscription
## v4.3.4 2020 April 14
- Improved version check
- Improved setup script for installations without nginx
- Changed some default values for new installations
- Compatible database with Fab-manager v1, to allow upgrades
- Database is now compatible with Fab-manager v1, to allow upgrades
- Updated documentation
- Changed In-Context pseudo-language to Zulu instead of Acholi
- Allow removing contacts from the about page
@ -28,7 +37,7 @@
## v4.3.3 2020 April 1st
- Docker build will no longer embed development dependencies
- Updated instructions to setup a development environment
- Updated instructions to set up a development environment
- Updated translations
- Removed `MESSAGEFORMAT_LOCALE` as it is now handled by make-plural
- Updated rails framework to v5.2
@ -64,7 +73,7 @@
## v4.3.1 2020 March 04
- Updated user's manual for v4.3 (fr)
- Display user's manual when help is asked, if no tour is available
- Display user's manual when asking for help, if no tour is available
- Change style and pluralize the text of the slot division alert in new availability assistant
- Fix a bug: in feature tours, next and previous arrows may be broken on some systems
- Fix a bug: in the user's menu, two links to the personal wallet

View File

@ -64,8 +64,6 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
$translateProvider.useMessageFormatInterpolation();
// Set the language of the instance (from ruby configuration)
$translateProvider.preferredLanguage(Fablab.locale);
// In any cases, fallback to english
$translateProvider.fallbackLanguage('en');
// End the tour when the user clicks the forward or back buttons of the browser
TourConfigProvider.enableNavigationInterceptors();
}]).run(['$rootScope', '$log', 'AuthService', 'Auth', 'amMoment', '$state', 'editableOptions', 'Analytics',

View File

@ -18,8 +18,8 @@
* Controller used in the calendar management page
*/
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'AuthService', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, moment, AuthService, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
/* PRIVATE STATIC CONSTANTS */
// The calendar is divided in slots of 30 minutes
@ -313,14 +313,16 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
placement: 'right',
popupClass: 'width-350'
});
uitour.createStep({
selector: '.admin-calendar .export-xls-button',
stepId: 'export',
order: 2,
title: _t('app.admin.tour.calendar.export.title'),
content: _t('app.admin.tour.calendar.export.content'),
placement: 'left'
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: '.admin-calendar .export-xls-button',
stepId: 'export',
order: 2,
title: _t('app.admin.tour.calendar.export.title'),
content: _t('app.admin.tour.calendar.export.content'),
placement: 'left'
});
}
uitour.createStep({
selector: '.heading .import-ics-button',
stepId: 'import',

View File

@ -153,8 +153,8 @@ class EventsController {
/**
* Controller used in the events listing page (admin view)
*/
Application.Controllers.controller('AdminEventsController', ['$scope', '$state', 'dialogs', '$uibModal', 'growl', 'Event', 'Category', 'EventTheme', 'AgeRange', 'PriceCategory', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t', 'Member', 'uiTourService',
function ($scope, $state, dialogs, $uibModal, growl, Event, Category, EventTheme, AgeRange, PriceCategory, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t, Member, uiTourService) {
Application.Controllers.controller('AdminEventsController', ['$scope', '$state', 'dialogs', '$uibModal', 'growl', 'AuthService', 'Event', 'Category', 'EventTheme', 'AgeRange', 'PriceCategory', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t', 'Member', 'uiTourService',
function ($scope, $state, dialogs, $uibModal, growl, AuthService, Event, Category, EventTheme, AgeRange, PriceCategory, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t, Member, uiTourService) {
/* PUBLIC SCOPE */
// By default, the pagination mode is activated to limit the page size
@ -407,38 +407,40 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
content: _t('app.admin.tour.events.filter.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.events-management .events-categories',
stepId: 'categories',
order: 3,
title: _t('app.admin.tour.events.categories.title'),
content: _t('app.admin.tour.events.categories.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.events-management .events-themes',
stepId: 'themes',
order: 4,
title: _t('app.admin.tour.events.themes.title'),
content: _t('app.admin.tour.events.themes.content'),
placement: 'top'
});
uitour.createStep({
selector: '.events-management .events-age-ranges',
stepId: 'ages',
order: 5,
title: _t('app.admin.tour.events.ages.title'),
content: _t('app.admin.tour.events.ages.content'),
placement: 'top'
});
uitour.createStep({
selector: '.events-management .prices-tab',
stepId: 'prices',
order: 6,
title: _t('app.admin.tour.events.prices.title'),
content: _t('app.admin.tour.events.prices.content'),
placement: 'bottom'
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: '.events-management .events-categories',
stepId: 'categories',
order: 3,
title: _t('app.admin.tour.events.categories.title'),
content: _t('app.admin.tour.events.categories.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.events-management .events-themes',
stepId: 'themes',
order: 4,
title: _t('app.admin.tour.events.themes.title'),
content: _t('app.admin.tour.events.themes.content'),
placement: 'top'
});
uitour.createStep({
selector: '.events-management .events-age-ranges',
stepId: 'ages',
order: 5,
title: _t('app.admin.tour.events.ages.title'),
content: _t('app.admin.tour.events.ages.content'),
placement: 'top'
});
uitour.createStep({
selector: '.events-management .prices-tab',
stepId: 'prices',
order: 6,
title: _t('app.admin.tour.events.prices.title'),
content: _t('app.admin.tour.events.prices.content'),
placement: 'bottom'
});
}
uitour.createStep({
selector: 'body',
stepId: 'conclusion',

View File

@ -17,8 +17,8 @@
/**
* Controller used in the admin invoices listing page
*/
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t', 'Member', 'uiTourService',
function ($scope, $state, Invoice, AccountingPeriod, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t, Member, uiTourService) {
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t', 'Member', 'uiTourService',
function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t, Member, uiTourService) {
/* PRIVATE STATIC CONSTANTS */
// number of invoices loaded each time we click on 'load more...'
@ -291,8 +291,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
growl.success(_t('app.admin.invoices.invoice_reference_successfully_saved'));
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_invoice_reference'));
console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_invoice_reference'));
console.error(error);
});
});
};
@ -330,8 +332,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
}
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_invoicing_code'));
return console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_invoicing_code'));
console.error(error);
});
return Setting.update({ name: 'invoice_code-active' }, { value: result.active ? 'true' : 'false' }, function (data) {
@ -343,8 +347,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
}
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_activating_the_invoicing_code'));
return console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_activating_the_invoicing_code'));
console.error(error);
});
});
};
@ -375,8 +381,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
return growl.success(_t('app.admin.invoices.order_number_successfully_saved'));
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_order_number'));
return console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_order_number'));
console.error(error);
});
});
};
@ -434,8 +442,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
}
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_VAT_rate'));
return console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_VAT_rate'));
console.error(error);
});
return Setting.update({ name: 'invoice_VAT-active' }, { value: result.active ? 'true' : 'false' }, function (data) {
@ -447,8 +457,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
}
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_activating_the_VAT'));
return console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_activating_the_VAT'));
console.error(error);
});
});
};
@ -463,8 +475,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
return growl.success(_t('app.admin.invoices.text_successfully_saved'));
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_text'));
return console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_text'));
console.error(error);
});
};
@ -478,8 +492,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
return growl.success(_t('app.admin.invoices.address_and_legal_information_successfully_saved'));
}
, function (error) {
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_address_and_the_legal_information'));
return console.error(error);
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_address_and_the_legal_information'));
console.error(error);
});
};
@ -566,15 +582,27 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
$scope.setupInvoicesTour = function () {
// get the tour defined by the ui-tour directive
const uitour = uiTourService.getTourByName('invoices');
uitour.createStep({
selector: 'body',
stepId: 'welcome',
order: 0,
title: _t('app.admin.tour.invoices.welcome.title'),
content: _t('app.admin.tour.invoices.welcome.content'),
placement: 'bottom',
orphan: true
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: 'body',
stepId: 'welcome',
order: 0,
title: _t('app.admin.tour.invoices.welcome.title'),
content: _t('app.admin.tour.invoices.welcome.content'),
placement: 'bottom',
orphan: true
});
} else {
uitour.createStep({
selector: 'body',
stepId: 'welcome_manager',
order: 0,
title: _t('app.admin.tour.invoices.welcome_manager.title'),
content: _t('app.admin.tour.invoices.welcome_manager.content'),
placement: 'bottom',
orphan: true
});
}
if (!Fablab.withoutInvoices && $scope.invoices.length > 0) {
uitour.createStep({
selector: '.invoices-management .invoices-list',
@ -609,39 +637,41 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
placement: 'left'
});
}
uitour.createStep({
selector: '.invoices-management .invoices-settings',
stepId: 'settings',
order: 5,
title: _t('app.admin.tour.invoices.settings.title'),
content: _t('app.admin.tour.invoices.settings.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.invoices-management .accounting-codes-tab',
stepId: 'codes',
order: 6,
title: _t('app.admin.tour.invoices.codes.title'),
content: _t('app.admin.tour.invoices.codes.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.heading .export-accounting-button',
stepId: 'export',
order: 7,
title: _t('app.admin.tour.invoices.export.title'),
content: _t('app.admin.tour.invoices.export.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.heading .close-accounting-periods-button',
stepId: 'periods',
order: 8,
title: _t('app.admin.tour.invoices.periods.title'),
content: _t('app.admin.tour.invoices.periods.content'),
placement: 'bottom',
popupClass: 'shift-left-50'
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: '.invoices-management .invoices-settings',
stepId: 'settings',
order: 5,
title: _t('app.admin.tour.invoices.settings.title'),
content: _t('app.admin.tour.invoices.settings.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.invoices-management .accounting-codes-tab',
stepId: 'codes',
order: 6,
title: _t('app.admin.tour.invoices.codes.title'),
content: _t('app.admin.tour.invoices.codes.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.heading .export-accounting-button',
stepId: 'export',
order: 7,
title: _t('app.admin.tour.invoices.export.title'),
content: _t('app.admin.tour.invoices.export.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.heading .close-accounting-periods-button',
stepId: 'periods',
order: 8,
title: _t('app.admin.tour.invoices.periods.title'),
content: _t('app.admin.tour.invoices.periods.content'),
placement: 'bottom',
popupClass: 'shift-left-50'
});
}
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
@ -710,8 +740,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
{ value: $scope.invoice.logo.base64 },
function (data) { growl.success(_t('app.admin.invoices.logo_successfully_saved')); },
function (error) {
if (error.status === 304) return;
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_logo'));
return console.error(error);
console.error(error);
}
);
}

View File

@ -126,8 +126,8 @@ class MembersController {
/**
* Controller used in the members/groups management page
*/
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', '$uibModal', 'membersPromise', 'adminsPromise', 'partnersPromise', 'managersPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export', 'User', 'uiTourService',
function ($scope, $sce, $uibModal, membersPromise, adminsPromise, partnersPromise, managersPromise, growl, Admin, dialogs, _t, Member, Export, User, uiTourService) {
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', '$uibModal', 'membersPromise', 'adminsPromise', 'partnersPromise', 'managersPromise', 'growl', 'Admin', 'AuthService', 'dialogs', '_t', 'Member', 'Export', 'User', 'uiTourService',
function ($scope, $sce, $uibModal, membersPromise, adminsPromise, partnersPromise, managersPromise, growl, Admin, AuthService, dialogs, _t, Member, Export, User, uiTourService) {
/* PRIVATE STATIC CONSTANTS */
// number of users loaded each time we click on 'load more...'
@ -217,6 +217,18 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
}
};
/**
* Change the managers ordering criterion to the one provided
* @param orderManager {string} ordering criterion
*/
$scope.setOrderManager = function (orderManager) {
if ($scope.orderManager === orderManager) {
return $scope.orderManager = `-${orderManager}`;
} else {
return $scope.orderManager = orderManager;
}
};
/**
* Open a modal dialog allowing the admin to create a new partner user
@ -343,6 +355,36 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
);
}
/**
* Ask for confirmation then delete the specified manager
* @param managers {Array} full list of managers
* @param manager {Object} manager to delete
*/
$scope.destroyManager = function (managers, manager) {
dialogs.confirm(
{
resolve: {
object () {
return {
title: _t('app.admin.members.confirmation_required'),
msg: $sce.trustAsHtml(_t('app.admin.members.delete_this_manager') + '<br/><br/>' + _t('app.admin.members.this_may_take_a_while_please_wait'))
};
}
}
},
function () { // cancel confirmed
User.delete(
{ id: manager.id },
function () {
managers.splice(findItemIdxById(managers, manager.id), 1);
return growl.success(_t('app.admin.members.manager_successfully_deleted'));
},
function (error) { growl.error(_t('app.admin.members.unable_to_delete_the_manager')); }
);
}
);
}
/**
* Callback for the 'load more' button.
* Will load the next results of the current search, if any
@ -434,22 +476,24 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
placement: 'left'
});
}
uitour.createStep({
selector: '.members-management .exports-buttons',
stepId: 'exports',
order: 5,
title: _t('app.admin.tour.members.exports.title'),
content: _t('app.admin.tour.members.exports.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.heading .import-members',
stepId: 'import',
order: 6,
title: _t('app.admin.tour.members.import.title'),
content: _t('app.admin.tour.members.import.content'),
placement: 'left'
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: '.members-management .exports-buttons',
stepId: 'exports',
order: 5,
title: _t('app.admin.tour.members.exports.title'),
content: _t('app.admin.tour.members.exports.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.heading .import-members',
stepId: 'import',
order: 6,
title: _t('app.admin.tour.members.import.title'),
content: _t('app.admin.tour.members.import.content'),
placement: 'left'
});
}
uitour.createStep({
selector: '.members-management .admins-tab',
stepId: 'admins',
@ -458,31 +502,33 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
content: _t('app.admin.tour.members.admins.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.members-management .groups-tab',
stepId: 'groups',
order: 8,
title: _t('app.admin.tour.members.groups.title'),
content: _t('app.admin.tour.members.groups.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.members-management .labels-tab',
stepId: 'labels',
order: 9,
title: _t('app.admin.tour.members.labels.title'),
content: _t('app.admin.tour.members.labels.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.members-management .sso-tab',
stepId: 'sso',
order: 10,
title: _t('app.admin.tour.members.sso.title'),
content: _t('app.admin.tour.members.sso.content'),
placement: 'bottom',
popupClass: 'shift-left-50'
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: '.members-management .groups-tab',
stepId: 'groups',
order: 8,
title: _t('app.admin.tour.members.groups.title'),
content: _t('app.admin.tour.members.groups.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.members-management .labels-tab',
stepId: 'labels',
order: 9,
title: _t('app.admin.tour.members.labels.title'),
content: _t('app.admin.tour.members.labels.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.members-management .sso-tab',
stepId: 'sso',
order: 10,
title: _t('app.admin.tour.members.sso.title'),
content: _t('app.admin.tour.members.sso.content'),
placement: 'bottom',
popupClass: 'shift-left-50'
});
}
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
@ -1029,3 +1075,72 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
}
]);
/**
* Controller used in the manager's creation page (admin view)
*/
Application.Controllers.controller('NewManagerController', ['$state', '$scope', 'User', 'groupsPromise', 'tagsPromise', 'growl', '_t',
function ($state, $scope, User, groupsPromise, tagsPromise, growl, _t) {
// default admin profile
$scope.manager = {
statistic_profile_attributes: {
gender: true
},
profile_attributes: {},
invoicing_profile_attributes: {}
};
// Default parameters for AngularUI-Bootstrap datepicker
$scope.datePicker = {
format: Fablab.uibDateFormat,
opened: false,
options: {
startingDay: Fablab.weekStartingDay
}
};
// list of all groups
$scope.groups = groupsPromise.filter(function (g) { return (g.slug !== 'admins') && !g.disabled; });
// list of all tags
$scope.tags = tagsPromise;
/**
* Shows the birth day datepicker
* @param $event {Object} jQuery event object
*/
$scope.openDatePicker = function ($event) { $scope.datePicker.opened = true; };
/**
* Send the new manager, currently stored in $scope.manager, to the server for database saving
*/
$scope.saveManager = function () {
User.save(
{},
{ manager: $scope.manager },
function () {
growl.success(_t('app.admin.manager_new.manager_successfully_created', { GENDER: getGender($scope.manager) }));
return $state.go('app.admin.members');
}
, function (error) {
growl.error(_t('app.admin.admins_new.failed_to_create_manager') + JSON.stringify(error.data ? error.data : error));
console.error(error);
}
);
};
/* PRIVATE SCOPE */
/**
* Return an enumerable meaningful string for the gender of the provider user
* @param user {Object} Database user record
* @return {string} 'male' or 'female'
*/
const getGender = function (user) {
if (user.statistic_profile_attributes) {
if (user.statistic_profile_attributes.gender) { return 'male'; } else { return 'female'; }
} else { return 'other'; }
};
}
]);

View File

@ -209,7 +209,12 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
{ name: setting.name },
{ value },
function () { growl.success(_t('app.admin.settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`app.admin.settings.${setting.name}`) })); },
function (error) { console.log(error); }
function (error) {
if (error.status === 304) return;
growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting'));
console.log(error);
}
);
};

View File

@ -126,8 +126,8 @@ Application.Controllers.controller('EventsController', ['$scope', '$state', 'Eve
}
]);
Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$stateParams', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'Slot', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise',
function ($scope, $state, $stateParams, $rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, Slot, eventPromise, growl, _t, Wallet, helpers, dialogs, priceCategoriesPromise, settingsPromise) {
Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$stateParams', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'Slot', 'eventPromise', 'growl', '_t', 'Wallet', 'AuthService', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise',
function ($scope, $state, $stateParams, $rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, Slot, eventPromise, growl, _t, Wallet, AuthService, helpers, dialogs, priceCategoriesPromise, settingsPromise) {
/* PUBLIC SCOPE */
// reservations for the currently shown event
@ -245,32 +245,32 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
if ($scope.event.nb_total_places > 0) {
$scope.reserveSuccess = false;
if (!$scope.isAuthenticated()) {
return $scope.login(null, function (user) {
if (user.role !== 'admin') {
return $scope.ctrl.member = user;
$scope.login(null, function (user) {
if (user.role !== 'admin' || user.role !== 'manager') {
$scope.ctrl.member = user;
}
const sameTimeReservations = findReservationsAtSameTime();
if (sameTimeReservations.length > 0) {
showReserveSlotSameTimeModal(sameTimeReservations, function(res) {
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
$scope.reserve.toReserve = !$scope.reserve.toReserve;
});
} else {
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
$scope.reserve.toReserve = !$scope.reserve.toReserve;
}
});
} else {
if ($scope.currentUser.role === 'admin') {
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
if (AuthService.isAuthorized(['admin', 'manager'])) {
$scope.reserve.toReserve = !$scope.reserve.toReserve;
} else {
Member.get({ id: $scope.currentUser.id }, function (member) {
$scope.ctrl.member = member;
const sameTimeReservations = findReservationsAtSameTime();
if (sameTimeReservations.length > 0) {
showReserveSlotSameTimeModal(sameTimeReservations, function(res) {
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
$scope.reserve.toReserve = !$scope.reserve.toReserve;
});
} else {
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
$scope.reserve.toReserve = !$scope.reserve.toReserve;
}
});
}
@ -286,9 +286,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
resetEventReserve();
$scope.reserveSuccess = false;
if ($scope.ctrl.member) {
return Member.get({ id: $scope.ctrl.member.id }, function (member) {
Member.get({ id: $scope.ctrl.member.id }, function (member) {
$scope.ctrl.member = member;
return getReservations($scope.event.id, 'Event', $scope.ctrl.member.id);
getReservations($scope.event.id, 'Event', $scope.ctrl.member.id);
});
}
};
@ -303,14 +303,17 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
return Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }, function (wallet) {
const amountToPay = helpers.getAmountToPay($scope.reserve.amountTotal, wallet.amount);
if (($scope.currentUser.role !== 'admin') && (amountToPay > 0)) {
if ((AuthService.isAuthorized(['member']) && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
if ($rootScope.fablabWithoutOnlinePayment) {
growl.error(_t('app.public.events_show.online_payment_disabled'));
} else {
return payByStripe(reservation);
}
} else {
if (($scope.currentUser.role === 'admin') || (amountToPay === 0)) {
if (AuthService.isAuthorized('admin')
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id !== $rootScope.currentUser.id)
|| amountToPay === 0) {
return payOnSite(reservation);
}
}
@ -564,7 +567,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
}
// watch when a coupon is applied to re-compute the total price
return $scope.$watch('coupon.applied', function (newValue, oldValue) {
$scope.$watch('coupon.applied', function (newValue, oldValue) {
if ((newValue !== null) || (oldValue !== null)) {
return $scope.computeEventAmount();
}
@ -577,7 +580,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* @param reservable_type {string} 'Event'
* @param user_id {number} the user's id (current or managed)
*/
var getReservations = function (reservable_id, reservable_type, user_id) {
const getReservations = function (reservable_id, reservable_type, user_id) {
Reservation.query({
reservable_id,
reservable_type,
@ -592,7 +595,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* @param event {Object} Current event
* @return {{user_id:number, reservable_id:number, reservable_type:string, slots_attributes:Array<Object>, nb_reserve_places:number}}
*/
var mkReservation = function (member, reserve, event) {
const mkReservation = function (member, reserve, event) {
const reservation = {
user_id: member.id,
reservable_id: event.id,
@ -628,7 +631,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* @param coupon {Object} Coupon as returned from the API
* @return {{reservation:Object, coupon_code:string}}
*/
var mkRequestParams = function (reservation, coupon) {
const mkRequestParams = function (reservation, coupon) {
const params = {
reservation,
coupon_code: ((coupon ? coupon.code : undefined))
@ -640,7 +643,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
/**
* Set the current reservation to the default values. This implies the reservation form to be hidden.
*/
var resetEventReserve = function () {
const resetEventReserve = function () {
if ($scope.event) {
$scope.reserve = {
nbPlaces: {
@ -666,7 +669,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* Open a modal window which trigger the stripe payment process
* @param reservation {Object} to book
*/
var payByStripe = function (reservation) {
const payByStripe = function (reservation) {
$uibModal.open({
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>',
size: 'md',
@ -730,7 +733,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* Open a modal window which trigger the local payment process
* @param reservation {Object} to book
*/
var payOnSite = function (reservation) {
const payOnSite = function (reservation) {
$uibModal.open({
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>',
size: 'sm',
@ -808,7 +811,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* What to do after the payment was successful
* @param reservation {Object} booked reservation
*/
var afterPayment = function (reservation) {
const afterPayment = function (reservation) {
$scope.event.nb_free_places = $scope.event.nb_free_places - reservation.total_booked_seats;
resetEventReserve();
$scope.reserveSuccess = true;
@ -822,7 +825,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
/**
* Find user's reservations, the same date at the same time, with event
*/
var findReservationsAtSameTime = function () {
const findReservationsAtSameTime = function () {
let sameTimeReservations = [
'training_reservations',
'machine_reservations',
@ -848,7 +851,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* @param sameTimeReservations {Array} reservations the same date at the same time
* @param callback {function} callback will invoke when user confirm
*/
var showReserveSlotSameTimeModal = function(sameTimeReservations, callback) {
const showReserveSlotSameTimeModal = function(sameTimeReservations, callback) {
const modalInstance = $uibModal.open({
animation: true,
templateUrl: '<%= asset_path "shared/_reserve_slot_same_time.html" %>',

View File

@ -1,7 +1,7 @@
'use strict';
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', 'settingsPromise', 'Member', 'uiTourService', '_t', 'Help',
function ($scope, $stateParams, settingsPromise, Member, uiTourService, _t, Help) {
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', '$translatePartialLoader', 'AuthService', 'settingsPromise', 'Member', 'uiTourService', '_t', 'Help',
function ($scope, $stateParams, $translatePartialLoader, AuthService, settingsPromise, Member, uiTourService, _t, Help) {
/* PUBLIC SCOPE */
// Home page HTML content
@ -21,8 +21,12 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
* This is intended as a contextual help (when pressing F1)
*/
$scope.setupHomeTour = function () {
if ($scope.currentUser && $scope.currentUser.role === 'admin') {
setupWelcomeTour();
if (AuthService.isAuthorized(['admin', 'manager'])) {
// Workaround for the following bug: sometimes, when the feature tour is shown, the translations keys are not
// interpreted. This is an ugly hack, but we can't do better for now because angular-ui-tour does not support
// removing steps (this would allow us to recreate the steps when the translations are loaded), and we can't use
// promises with _t's translations (this would be a very big refactoring)
setTimeout(setupWelcomeTour, 1000);
}
};
@ -182,7 +186,7 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
selector: '.nav-primary .admin-section',
stepId: 'admin',
order: 9,
title: _t('app.public.tour.welcome.admin.title'),
title: _t('app.public.tour.welcome.admin.title', { ROLE: _t(`app.public.common.${$scope.currentUser.role}`) }),
content: _t('app.public.tour.welcome.admin.content'),
placement: 'right'
});
@ -271,14 +275,16 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
placement: 'bottom',
orphan: 'true'
});
uitour.createStep({
selector: '.app-generator .app-version',
stepId: 'version',
order: 19,
title: _t('app.public.tour.welcome.version.title'),
content: _t('app.public.tour.welcome.version.content'),
placement: 'top'
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: '.app-generator .app-version',
stepId: 'version',
order: 19,
title: _t('app.public.tour.welcome.version.title'),
content: _t('app.public.tour.welcome.version.content'),
placement: 'top'
});
}
uitour.createStep({
selector: 'body',
stepId: 'conclusion',

View File

@ -180,8 +180,8 @@ const _reserveMachine = function (machine, e) {
/**
* Controller used in the public listing page, allowing everyone to see the list of machines
*/
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'Machine', '$uibModal', 'machinesPromise', 'Member', 'uiTourService',
function ($scope, $state, _t, Machine, $uibModal, machinesPromise, Member, uiTourService) {
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'AuthService', 'Machine', '$uibModal', 'machinesPromise', 'Member', 'uiTourService',
function ($scope, $state, _t, AuthService, Machine, $uibModal, machinesPromise, Member, uiTourService) {
/* PUBLIC SCOPE */
// Retrieve the list of machines
@ -219,32 +219,54 @@ Application.Controllers.controller('MachinesController', ['$scope', '$state', '_
*/
$scope.setupMachinesTour = function () {
// setup the tour for admins only
if ($scope.currentUser && $scope.currentUser.role === 'admin') {
if (AuthService.isAuthorized(['admin', 'manager'])) {
// get the tour defined by the ui-tour directive
const uitour = uiTourService.getTourByName('machines');
uitour.createStep({
selector: 'body',
stepId: 'welcome',
order: 0,
title: _t('app.public.tour.machines.welcome.title'),
content: _t('app.public.tour.machines.welcome.content'),
placement: 'bottom',
orphan: true
});
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: 'body',
stepId: 'welcome',
order: 0,
title: _t('app.public.tour.machines.welcome.title'),
content: _t('app.public.tour.machines.welcome.content'),
placement: 'bottom',
orphan: true
});
if ($scope.machines.length > 0) {
uitour.createStep({
selector: '.machines-list .show-button',
stepId: 'view',
order: 1,
title: _t('app.public.tour.machines.view.title'),
content: _t('app.public.tour.machines.view.content'),
placement: 'top'
});
}
} else {
uitour.createStep({
selector: 'body',
stepId: 'welcome_manager',
order: 0,
title: _t('app.public.tour.machines.welcome_manager.title'),
content: _t('app.public.tour.machines.welcome_manager.content'),
placement: 'bottom',
orphan: true
});
}
if ($scope.machines.length > 0) {
uitour.createStep({
selector: '.machines-list .show-button',
stepId: 'view',
order: 1,
title: _t('app.public.tour.machines.view.title'),
content: _t('app.public.tour.machines.view.content'),
selector: '.machines-list .reserve-button',
stepId: 'reserve',
order: 2,
title: _t('app.public.tour.machines.reserve.title'),
content: _t('app.public.tour.machines.reserve.content'),
placement: 'top'
});
}
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
order: 2,
order: 3,
title: _t('app.public.tour.conclusion.title'),
content: _t('app.public.tour.conclusion.content'),
placement: 'bottom',
@ -524,12 +546,13 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
* When modifying an already booked reservation, callback when the modification was successfully done.
*/
$scope.modifyMachineSlot = function () {
$scope.events.placable.title = $scope.currentUser.role !== 'admin' ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available');
$scope.events.placable.title = $scope.currentUser.id === $scope.events.modifiable.user.id ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.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.events.placable.user = angular.copy($scope.events.modifiable.user);
$scope.events.modifiable.backgroundColor = 'white';
$scope.events.modifiable.title = '';
@ -549,7 +572,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
$scope.events.placable.backgroundColor = 'white';
$scope.events.placable.title = '';
}
$scope.events.modifiable.title = $scope.currentUser.role !== 'admin' ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available');
$scope.events.modifiable.title = $scope.currentUser.id === $scope.events.modifiable.user.id ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available');
$scope.events.modifiable.backgroundColor = 'white';
return updateCalendar();
@ -604,16 +627,18 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
angular.forEach($scope.events.reserved, function (machineSlot, key) {
machineSlot.is_reserved = true;
machineSlot.can_modify = true;
if ($scope.currentUser.role !== 'admin') {
machineSlot.title = _t('app.logged.machines_reserve.i_ve_reserved');
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR;
updateMachineSlot(machineSlot, reservation, $scope.currentUser);
} else {
if ($scope.currentUser.role === 'admin' || ($scope.currentUser.role === 'manager' && reservation.user_id !== $scope.currentUser.id)) {
// an admin or a manager booked for someone else
machineSlot.title = _t('app.logged.machines_reserve.not_available');
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR;
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member);
} else {
// booked for "myself"
machineSlot.title = _t('app.logged.machines_reserve.i_ve_reserved');
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR;
updateMachineSlot(machineSlot, reservation, $scope.currentUser);
}
return machineSlot.backgroundColor = 'white';
machineSlot.backgroundColor = 'white';
});
if ($scope.selectedPlan) {

View File

@ -81,59 +81,73 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
{
state: 'app.admin.calendar',
linkText: 'app.public.common.manage_the_calendar',
linkIcon: 'calendar'
linkIcon: 'calendar',
authorizedRoles: ['admin', 'manager']
},
{
state: 'app.public.machines_list',
linkText: 'app.public.common.manage_the_machines',
linkIcon: 'cogs'
linkIcon: 'cogs',
authorizedRoles: ['admin', 'manager']
},
{
state: 'app.admin.trainings',
linkText: 'app.public.common.trainings_monitoring',
linkIcon: 'graduation-cap'
linkIcon: 'graduation-cap',
authorizedRoles: ['admin', 'manager']
},
{
state: 'app.admin.events',
linkText: 'app.public.common.manage_the_events',
linkIcon: 'tags'
linkIcon: 'tags',
authorizedRoles: ['admin', 'manager']
},
{ class: 'menu-spacer' },
{
state: 'app.admin.members',
linkText: 'app.public.common.manage_the_users',
linkIcon: 'users'
linkIcon: 'users',
authorizedRoles: ['admin', 'manager']
},
{
state: 'app.admin.pricing',
linkText: 'app.public.common.subscriptions_and_prices',
linkIcon: 'money'
linkIcon: 'money',
authorizedRoles: ['admin']
},
{
state: 'app.admin.invoices',
linkText: 'app.public.common.manage_the_invoices',
linkIcon: 'file-pdf-o'
linkIcon: 'file-pdf-o',
authorizedRoles: ['admin', 'manager']
},
{
state: 'app.admin.statistics',
linkText: 'app.public.common.statistics',
linkIcon: 'bar-chart-o'
linkIcon: 'bar-chart-o',
authorizedRoles: ['admin']
},
{
class: 'menu-spacer',
authorizedRoles: ['admin']
},
{ class: 'menu-spacer' },
{
state: 'app.admin.settings',
linkText: 'app.public.common.customization',
linkIcon: 'gear'
linkIcon: 'gear',
authorizedRoles: ['admin']
},
{
state: 'app.admin.project_elements',
linkText: 'app.public.common.manage_the_projects_elements',
linkIcon: 'tasks'
linkIcon: 'tasks',
authorizedRoles: ['admin']
},
{
state: 'app.admin.open_api_clients',
linkText: 'app.public.common.open_api_clients',
linkIcon: 'cloud'
linkIcon: 'cloud',
authorizedRoles: ['admin']
}
].concat(Fablab.adminNavLinks);

View File

@ -12,8 +12,8 @@
*/
'use strict';
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers',
function ($scope, $rootScope, $state, $uibModal, Auth, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers) {
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers',
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers) {
/* PUBLIC SCOPE */
// list of groups
@ -28,13 +28,6 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
// list of plans, classified by group
$scope.plansClassifiedByGroup = [];
for (var group of Array.from($scope.groups)) {
const groupObj = { id: group.id, name: group.name, plans: [] };
for (let plan of Array.from(plansPromise)) {
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
}
$scope.plansClassifiedByGroup.push(groupObj);
}
// user to deal with
$scope.ctrl = {
@ -62,15 +55,15 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
/**
* Callback to deal with the subscription of the user selected in the dropdown list instead of the current user's
* subscription. (admins only)
* subscription. (admins and managers only)
*/
$scope.updateMember = function () {
$scope.selectedPlan = null;
$scope.paid.plan = null;
$scope.group.change = false;
return Member.get({ id: $scope.ctrl.member.id }, function (member) {
Member.get({ id: $scope.ctrl.member.id }, function (member) {
$scope.ctrl.member = member;
return $scope.group.id = $scope.ctrl.member.group_id;
$scope.group.id = $scope.ctrl.member.group_id;
});
};
@ -97,14 +90,17 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
$scope.openSubscribePlanModal = function () {
Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }, function (wallet) {
const amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount);
if (($scope.currentUser.role !== 'admin') && (amountToPay > 0)) {
if ((AuthService.isAuthorized('member') && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
if ($rootScope.fablabWithoutOnlinePayment) {
growl.error(_t('app.public.plans.online_payment_disabled'));
} else {
return payByStripe();
}
} else {
if (($scope.currentUser.role === 'admin') || (amountToPay === 0)) {
if (AuthService.isAuthorized('admin')
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id !== $rootScope.currentUser.id)
|| amountToPay === 0) {
return payOnSite();
}
}
@ -115,7 +111,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
* Return the group object, identified by the ID set in $scope.group.id
*/
$scope.getUserGroup = function () {
for (group of Array.from($scope.groups)) {
for (const group of Array.from($scope.groups)) {
if (group.id === $scope.group.id) {
return group;
}
@ -130,7 +126,8 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
$scope.ctrl.member = user;
$scope.group.change = false;
$scope.selectedPlan = null;
if ($scope.currentUser.role !== 'admin') {
if (AuthService.isAuthorized('member') ||
(AuthService.isAuthorized('manager') && $scope.currentUser.id !== $scope.ctrl.member.id)) {
$rootScope.currentUser = user;
Auth._currentUser.group_id = user.group_id;
growl.success(_t('app.public.plans.your_group_was_successfully_changed'));
@ -139,7 +136,8 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
}
}
, function (err) {
if ($scope.currentUser.role !== 'admin') {
if (AuthService.isAuthorized('member') ||
(AuthService.isAuthorized('manager') && $scope.currentUser.id !== $scope.ctrl.member.id)) {
growl.error(_t('app.public.plans.an_error_prevented_your_group_from_being_changed'));
} else {
growl.error(_t('app.public.plans.an_error_prevented_to_change_the_user_s_group'));
@ -179,8 +177,20 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
* Kind of constructor: these actions will be realized first when the controller is loaded
*/
const initialize = function () {
// group all plans by Group
for (const group of $scope.groups) {
const groupObj = { id: group.id, name: group.name, plans: [], actives: 0 };
for (let plan of plansPromise) {
if (plan.group_id === group.id) {
groupObj.plans.push(plan);
if (!plan.disabled) { groupObj.actives++; }
}
}
$scope.plansClassifiedByGroup.push(groupObj);
}
if ($scope.currentUser) {
if ($scope.currentUser.role !== 'admin') {
if (!AuthService.isAuthorized('admin')) {
$scope.ctrl.member = $scope.currentUser;
$scope.paid.plan = $scope.currentUser.subscribed_plan;
$scope.group.id = $scope.currentUser.group_id;
@ -201,9 +211,9 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
* Compute the total amount for the current reservation according to the previously set parameters
* and assign the result in $scope.reserve.amountTotal
*/
var updateCartPrice = function () {
// first we check that a user was selected
if (Object.keys($scope.ctrl.member).length > 0) {
const updateCartPrice = function () {
// first we check the selection of a user
if (Object.keys($scope.ctrl.member).length > 0 && $scope.selectedPlan) {
$scope.cart.total = $scope.selectedPlan.amount;
// apply the coupon if any
if ($scope.coupon.applied) {
@ -223,7 +233,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
/**
* Open a modal window which trigger the stripe payment process
*/
var payByStripe = function () {
const payByStripe = function () {
$uibModal.open({
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>',
size: 'md',
@ -262,7 +272,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
CustomAsset.get({ name: 'cgv-file' }, function (cgv) { $scope.cgv = cgv.custom_asset; });
/**
* Callback for click on the 'proceed' button.
* Callback for a click on the 'proceed' button.
* Handle the stripe's card tokenization process response and save the subscription to the API with the
* card token just created.
*/
@ -283,7 +293,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
/**
* Open a modal window which trigger the local payment process
*/
var payOnSite = function () {
const payOnSite = function () {
$uibModal.open({
templateUrl: '<%= asset_path "plans/payment_modal.html" %>',
size: 'sm',
@ -301,7 +311,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
// user wallet amount
$scope.walletAmount = wallet.amount;
// subcription price, coupon subtracted if any
// subscription price, coupon subtracted if any
$scope.price = price;
// price to pay

View File

@ -98,8 +98,8 @@ class SpacesController {
/**
* Controller used in the public listing page, allowing everyone to see the list of spaces
*/
Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spacesPromise', '_t', 'Member', 'uiTourService',
function ($scope, $state, spacesPromise, _t, Member, uiTourService) {
Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spacesPromise', 'AuthService', '_t', 'Member', 'uiTourService',
function ($scope, $state, spacesPromise, AuthService, _t, Member, uiTourService) {
/* PUBLIC SCOPE */
// Retrieve the list of spaces
@ -131,9 +131,10 @@ Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spa
*/
$scope.setupSpacesTour = function () {
// setup the tour for admins only
if ($scope.currentUser && $scope.currentUser.role === 'admin') {
if (AuthService.isAuthorized(['admin', 'manager'])) {
// get the tour defined by the ui-tour directive
const uitour = uiTourService.getTourByName('spaces');
if (AuthService.isAuthorized('admin')) {
uitour.createStep({
selector: 'body',
stepId: 'welcome',
@ -153,10 +154,31 @@ Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spa
placement: 'top'
});
}
} else {
uitour.createStep({
selector: 'body',
stepId: 'welcome_manager',
order: 0,
title: _t('app.public.tour.spaces.welcome_manager.title'),
content: _t('app.public.tour.spaces.welcome_manager.content'),
placement: 'bottom',
orphan: true
});
}
if ($scope.spaces.length > 0) {
uitour.createStep({
selector: '.spaces-list .reserve-button',
stepId: 'reserve',
order: 2,
title: _t('app.public.tour.spaces.reserve.title'),
content: _t('app.public.tour.spaces.reserve.content'),
placement: 'top'
});
}
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
order: 2,
order: 3,
title: _t('app.public.tour.conclusion.title'),
content: _t('app.public.tour.conclusion.content'),
placement: 'bottom',

View File

@ -10,8 +10,8 @@
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'helpers', '_t', '$uibModal',
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, helpers, _t, $uibModal) {
Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'AuthService', 'helpers', '_t',
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, AuthService, helpers, _t) {
return ({
restrict: 'E',
scope: {
@ -167,7 +167,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
// first, we check that a user was selected
if (Object.keys($scope.user).length > 0) {
// check user was selected a plan if slot is restricted for subscriptions
// check selected user has a subscription, if any slot is restricted for subscriptions
const slotValidations = [];
let slotNotValid;
let slotNotValidError;
@ -195,7 +195,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
});
const hasPlanForSlot = slotValidations.every(function (a) { return a; });
if (!hasPlanForSlot) {
if (!$scope.isAdmin()) {
if (!AuthService.isAuthorized(['admin', 'manager'])) {
return growl.error(_t('app.shared.cart.slot_restrict_subscriptions_must_select_plan'));
} else {
const modalInstance = $uibModal.open({
@ -216,7 +216,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
return paySlots();
}
} else {
// otherwise we alert, this error musn't occur when the current user is not admin
// otherwise we alert, this error musn't occur when the current user is not admin or manager
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
}
};
@ -285,12 +285,6 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
return false;
};
/**
* Check if the currently logged user has teh 'admin' role?
* @returns {boolean}
*/
$scope.isAdmin = function () { return $rootScope.currentUser && ($rootScope.currentUser.role === 'admin'); };
/* PRIVATE SCOPE */
/**
@ -325,11 +319,13 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Callback triggered when the selected slot changed
*/
var slotSelectionChanged = function () {
const slotSelectionChanged = function () {
if ($scope.slot) {
// build a list of plans if this slot is restricted for subscriptions
// if this slot is restricted for subscribers...
if ($scope.slot.plan_ids.length > 0) {
// ... we select all the plans matching these restrictions...
const _plans = _.filter($scope.plans, function (p) { return _.include($scope.slot.plan_ids, p.id) });
// ... and we group these plans, by Group...
$scope.slot.plansGrouped = [];
$scope.slot.group_ids = [];
for (let group of Array.from($scope.groups)) {
@ -338,7 +334,9 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
}
if (groupObj.plans.length > 0) {
if ($scope.isAdmin()) {
// ... Finally, we only keep the plans matching the group of the current user
// OR all plans if the current user is admin or manager
if (AuthService.isAuthorized(['admin', 'manager'])) {
$scope.slot.plansGrouped.push(groupObj);
} else if ($scope.user.group_id === groupObj.id) {
$scope.slot.plansGrouped.push(groupObj);
@ -398,7 +396,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
}
}
, function (type) {
// the user has choosen an action, so we proceed
// the user has chosen an action, so we proceed
if (type === 'move') {
if (typeof $scope.onSlotStartToModify === 'function') { $scope.onSlotStartToModify(); }
return $scope.events.modifiable = $scope.slot;
@ -433,7 +431,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Reset the parameters that may lead to a wrong price but leave the content (events added to cart)
*/
var resetCartState = function () {
const resetCartState = function () {
$scope.selectedPlan = null;
$scope.coupon.applied = null;
$scope.events.moved = null;
@ -446,8 +444,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
* Determines if the provided booked slot is able to be modified by the user.
* @param slot {Object} fullCalendar event object
*/
var slotCanBeModified = function (slot) {
if ($scope.isAdmin()) { return true; }
const slotCanBeModified = function (slot) {
if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
const slotStart = moment(slot.start);
const now = moment();
return (slot.can_modify && $scope.enableBookingMove && (slotStart.diff(now, 'hours') >= $scope.moveBookingDelay));
@ -457,8 +455,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
* Determines if the provided booked slot is able to be canceled by the user.
* @param slot {Object} fullCalendar event object
*/
var slotCanBeCanceled = function (slot) {
if ($scope.isAdmin()) { return true; }
const slotCanBeCanceled = function (slot) {
if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
const slotStart = moment(slot.start);
const now = moment();
return (slot.can_modify && $scope.enableBookingCancel && (slotStart.diff(now, 'hours') >= $scope.cancelBookingDelay));
@ -467,7 +465,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Callback triggered when the selected slot changed
*/
var planSelectionChanged = function () {
const planSelectionChanged = function () {
if (Auth.isAuthenticated()) {
if ($scope.selectedPlan !== $scope.plan) {
$scope.selectedPlan = $scope.plan;
@ -486,7 +484,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Update the total price of the current selection/reservation
*/
var updateCartPrice = function () {
const updateCartPrice = function () {
if (Object.keys($scope.user).length > 0) {
const r = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
return Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) {
@ -501,7 +499,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
}
};
var setSlotsDetails = function (details) {
const setSlotsDetails = function (details) {
angular.forEach($scope.events.reserved, function (slot) {
angular.forEach(details.slots, function (s) {
if (moment(s.start_at).isSame(slot.start)) {
@ -518,7 +516,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
* @param coupon {Object} Coupon as returned from the API
* @return {{reservation:Object, coupon_code:string}}
*/
var mkRequestParams = function (reservation, coupon) {
const mkRequestParams = function (reservation, coupon) {
return {
reservation,
coupon_code: ((coupon ? coupon.code : undefined))
@ -532,7 +530,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
* @param [plan] {Object} Plan as retrieved 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}}
*/
var mkReservation = function (member, slots, plan) {
const mkReservation = function (member, slots, plan) {
const reservation = {
user_id: member.id,
reservable_id: $scope.reservableId,
@ -555,7 +553,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Open a modal window that allows the user to process a credit card payment for his current shopping cart.
*/
var payByStripe = function (reservation) {
const payByStripe = function (reservation) {
$uibModal.open({
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>',
size: 'md',
@ -612,7 +610,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
*/
var payOnSite = function (reservation) {
const payOnSite = function (reservation) {
$uibModal.open({
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>',
size: 'sm',
@ -681,7 +679,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Actions to run after the payment was successful
*/
var afterPayment = function (reservation) {
const afterPayment = function (reservation) {
// we set the cart content as 'paid' to display a summary of the transaction
$scope.events.paid = $scope.events.reserved;
$scope.amountPaid = $scope.amountTotal;
@ -697,19 +695,22 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Actions to pay slots
*/
var paySlots = function() {
const paySlots = function() {
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
if (!$scope.isAdmin() && (amountToPay > 0)) {
if ((AuthService.isAuthorized(['member']) && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) {
if ($rootScope.fablabWithoutOnlinePayment) {
growl.error(_t('app.shared.cart.online_payment_disabled'));
} else {
return payByStripe(reservation);
}
} else {
if ($scope.isAdmin() || (amountToPay === 0)) {
if (AuthService.isAuthorized(['admin'])
|| (AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id)
|| amountToPay === 0) {
return payOnSite(reservation);
}
}
@ -726,10 +727,11 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/**
* Controller of modal for show reservations the same date at the same time
*/
Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'sameTimeReservations', 'growl', '_t',
function ($scope, $uibModalInstance, sameTimeReservations, growl, _t) {
Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'AuthService', 'sameTimeReservations', 'growl', '_t',
function ($scope, $uibModalInstance, AuthService, sameTimeReservations, growl, _t) {
$scope.sameTimeReservations = sameTimeReservations;
$scope.bookSlotAtSameTime = Fablab.bookSlotAtSameTime;
$scope.isAuthorized = AuthService.isAuthorized;
/**
* Confirmation callback
*/

View File

@ -55,7 +55,7 @@ angular.module('application.router', ['ui.router'])
.state('app.logged', {
abstract: true,
data: {
authorizedRoles: ['member', 'admin']
authorizedRoles: ['member', 'admin', 'manager']
},
resolve: {
currentUser: ['Auth', function (Auth) { return Auth.currentUser(); }],
@ -68,7 +68,7 @@ angular.module('application.router', ['ui.router'])
.state('app.admin', {
abstract: true,
data: {
authorizedRoles: ['admin']
authorizedRoles: ['admin', 'manager']
},
resolve: {
currentUser: ['Auth', function (Auth) { return Auth.currentUser(); }],
@ -931,8 +931,21 @@ angular.module('application.router', ['ui.router'])
}
}
})
.state('app.admin.managers_new', {
url: '/admin/managers/new',
views: {
'main@': {
templateUrl: '<%= asset_path "admin/managers/new.html" %>',
controller: 'NewManagerController'
}
},
resolve: {
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }]
}
})
// authentification providers
// authentication providers
.state('app.admin.authentication_new', {
url: '/admin/authentications/new',
views: {

View File

@ -1,9 +1,8 @@
'use strict';
Application.Services.factory('_t', ['$filter', function ($filter) {
return function (key, interpolation, options) {
if (interpolation == null) { interpolation = undefined; }
if (options == null) { options = undefined; }
return $filter('translate')(key, interpolation, options);
Application.Services.factory('_t', ['$translate', function ($translate) {
return function (key, interpolations) {
if (interpolations == null) { interpolations = undefined; }
return $translate.instant(key, interpolations);
};
}]);

View File

@ -1,6 +1,7 @@
'use strict';
Application.Services.factory('Help', ['$rootScope', '$uibModal', '$state', function ($rootScope, $uibModal, $state) {
Application.Services.factory('Help', ['$rootScope', '$uibModal', '$state', 'AuthService',
function ($rootScope, $uibModal, $state, AuthService) {
const TOURS = {
'app.public.home': 'welcome',
'app.public.machines_list': 'machines',
@ -19,7 +20,7 @@ Application.Services.factory('Help', ['$rootScope', '$uibModal', '$state', funct
return function (e) {
if (!$rootScope.currentUser || $rootScope.currentUser.role !== 'admin') return;
if (!AuthService.isAuthorized(['admin', 'manager'])) return;
if (e.key === 'F1') {
e.preventDefault();

View File

@ -537,7 +537,6 @@
}
li.level-2-tab > a {
background: #eee;
line-height: 14px;
font-size: 12px;
}

View File

@ -45,7 +45,7 @@
</div>
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="m text-center">
<div class="m text-center" ng-show="isAuthorized('admin')">
<a class="btn btn-default export-xls-button"
ng-href="api/availabilities/export_index.xlsx"
target="export-frame"
@ -71,7 +71,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-show="reservations.length == 0" translate>{{ 'app.admin.calendar.no_reservations' }}</div>
<div ng-show="reservations.length == 0" translate>{{ 'app.admin.calendar.without_reservation' }}</div>
<div class="m-t" ng-show="availability.lock"><i class="fa fa-ban"></i> <span class="m-l-xs" translate>{{ 'app.admin.calendar.reservations_locked' }}</span></div>
</div>
</div>

View File

@ -11,7 +11,7 @@
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin'])">
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin', 'manager'])">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.admin.events_new" role="button" translate>{{ 'app.admin.events.add_an_event' }}</a>
</section>
@ -26,7 +26,7 @@
ui-tour-scroll-parent-id="content-main"
post-render="setupEventsTour">
<div class="row">
<div class="col-md-12">
<div class="col-md-12" ng-if="isAuthorized('admin')">
<uib-tabset justified="true" active="tabs.active">
<uib-tab heading="{{ 'app.admin.events.events_monitoring' | translate }}" index="0">
<ng-include src="'<%= asset_path "admin/events/monitoring.html" %>'"></ng-include>
@ -41,6 +41,9 @@
</uib-tab>
</uib-tabset>
</div>
<div class="col-md-12" ng-if="isAuthorized('manager')">
<ng-include src="'<%= asset_path "admin/events/monitoring.html" %>'"></ng-include>
</div>
</div>
</section>
</section>

View File

@ -51,7 +51,7 @@
<td style="vertical-align:middle">
<span class="ng-binding" ng-if="event.nb_total_places > 0">{{ event.nb_total_places - event.nb_free_places }} / {{ event.nb_total_places }}</span>
<span class="badge font-sbold cancelled" ng-if="event.nb_total_places == -1" translate>{{ 'app.admin.events.cancelled' }}</span>
<span class="badge font-sbold" ng-if="!event.nb_total_places" translate>{{ 'app.admin.events.free_entry' }}</span>
<span class="badge font-sbold" ng-if="!event.nb_total_places" translate>{{ 'app.admin.events.without_reservation' }}</span>
</td>
<td style="vertical-align:middle">

View File

@ -0,0 +1,111 @@
<div class="panel panel-default m-t-md accounting-codes">
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<label for="journalCode" translate>{{ 'app.admin.invoices.accounting_journal_code' }}</label>
<input type="text" id="journalCode" ng-model="settings.journalCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_journal_code' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="cardClientCode" translate>{{ 'app.admin.invoices.accounting_card_client_code' }}</label>
<input type="text" id="cardClientCode" ng-model="settings.cardClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.card_client_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="cardClientLabel" translate>{{ 'app.admin.invoices.accounting_card_client_label' }}</label>
<input type="text" id="cardClientLabel" ng-model="settings.cardClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.card_client_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="walletClientCode" translate>{{ 'app.admin.invoices.accounting_wallet_client_code' }}</label>
<input type="text" id="walletClientCode" ng-model="settings.walletClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.wallet_client_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="walletClientLabel" translate>{{ 'app.admin.invoices.accounting_wallet_client_label' }}</label>
<input type="text" id="walletClientLabel" ng-model="settings.walletClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.wallet_client_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="otherClientCode" translate>{{ 'app.admin.invoices.accounting_other_client_code' }}</label>
<input type="text" id="otherClientCode" ng-model="settings.otherClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.other_client_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="otherClientLabel" translate>{{ 'app.admin.invoices.accounting_other_client_label' }}</label>
<input type="text" id="otherClientLabel" ng-model="settings.otherClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.other_client_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="walletCode" translate>{{ 'app.admin.invoices.accounting_wallet_code' }}</label>
<input type="text" id="walletCode" ng-model="settings.walletCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_wallet_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="walletLabel" translate>{{ 'app.admin.invoices.accounting_wallet_label' }}</label>
<input type="text" id="walletLabel" ng-model="settings.walletLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_wallet_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="vatCode" translate>{{ 'app.admin.invoices.accounting_vat_code' }}</label>
<input type="text" id="vatCode" ng-model="settings.vatCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_vat_code' | translate }}"/>
</div>
<div class="col-md-6">
<label for="vatLabel" translate>{{ 'app.admin.invoices.accounting_vat_label' }}</label>
<input type="text" id="vatLabel" ng-model="settings.vatLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_vat_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="subscriptionCode" translate>{{ 'app.admin.invoices.accounting_subscription_code' }}</label>
<input type="text" id="subscriptionCode" ng-model="settings.subscriptionCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_subscription_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="subscriptionLabel" translate>{{ 'app.admin.invoices.accounting_subscription_label' }}</label>
<input type="text" id="subscriptionLabel" ng-model="settings.subscriptionLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_subscription_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="machineCode" translate>{{ 'app.admin.invoices.accounting_Machine_code' }}</label>
<input type="text" id="machineCode" ng-model="settings.machineCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_machine_code' | translate }}"/>
</div>
<div class="col-md-6">
<label for="machineLabel" translate>{{ 'app.admin.invoices.accounting_Machine_label' }}</label>
<input type="text" id="machineLabel" ng-model="settings.machineLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_machine_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="trainingCode" translate>{{ 'app.admin.invoices.accounting_Training_code' }}</label>
<input type="text" id="trainingCode" ng-model="settings.trainingCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_training_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="trainingLabel" translate>{{ 'app.admin.invoices.accounting_Training_label' }}</label>
<input type="text" id="trainingLabel" ng-model="settings.trainingLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_training_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="eventCode" translate>{{ 'app.admin.invoices.accounting_Event_code' }}</label>
<input type="text" id="eventCode" ng-model="settings.eventCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_event_code' | translate }}"/>
</div>
<div class="col-md-6">
<label for="eventLabel" translate>{{ 'app.admin.invoices.accounting_Event_label' }}</label>
<input type="text" id="eventLabel" ng-model="settings.eventLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_event_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="spaceCode" translate>{{ 'app.admin.invoices.accounting_Space_code' }}</label>
<input type="text" id="spaceCode" ng-model="settings.spaceCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_space_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="spaceLabel" translate>{{ 'app.admin.invoices.accounting_Space_label' }}</label>
<input type="text" id="spaceLabel" ng-model="settings.spaceLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_space_label' | translate }}"/>
</div>
</div>
<button name="button" class="btn btn-warning m-t-lg" ng-click="save()" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
</div>

View File

@ -11,7 +11,7 @@
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3">
<section class="heading-actions wrapper">
<section class="heading-actions wrapper" ng-show="isAuthorized('admin')">
<a class="btn btn-default rounded m-t-sm export-accounting-button" ng-click="toggleExportModal()"><i class="fa fa-book"></i></a>
<iframe name="export-frame" height="0" width="0" class="none" id="accounting-export-frame"></iframe>
<a class="btn btn-lg btn-default rounded m-t-sm text-sm close-accounting-periods-button" ng-click="closeAnAccountingPeriod()"><i class="fa fa-calendar-check-o"></i> {{ 'app.admin.invoices.accounting_periods' | translate }}</a>
@ -28,530 +28,25 @@
ui-tour-scroll-parent-id="content-main"
post-render="setupInvoicesTour">
<div class="row">
<div class="col-md-12">
<div class="col-md-12" ng-if="isAuthorized('admin')">
<uib-tabset justified="true" active="tabs.active">
<uib-tab heading="{{ 'app.admin.invoices.invoices_list' | translate }}" ng-hide="fablabWithoutInvoices" index="0">
<h3 class="m-t-xs"><i class="fa fa-filter"></i> {{ 'app.admin.invoices.filter_invoices' | translate }}</h3>
<ng-include src="'<%= asset_path "admin/invoices/list.html" %>'"></ng-include>
</uib-tab>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" translate>{{ 'app.admin.invoices.invoice_num_' }}</span>
<input type="text" ng-model="searchInvoice.reference" class="form-control" placeholder="" ng-change="handleFilterChange()">
</div>
</div>
</div>
<uib-tab heading="{{ 'app.admin.invoices.invoicing_settings' | translate }}" index="1" class="invoices-settings">
<ng-include src="'<%= asset_path "admin/invoices/settings.html" %>'"></ng-include>
</uib-tab>
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" translate>{{ 'app.admin.invoices.customer_' }}</span>
<input type="text" ng-model="searchInvoice.name" class="form-control" placeholder="" ng-change="handleFilterChange()">
</div>
</div>
</div>
<uib-tab heading="{{ 'app.admin.invoices.accounting_codes' | translate }}" index="2" class="accounting-codes-tab">
<ng-include src="'<%= asset_path "admin/invoices/codes.html" %>'"></ng-include>
</uib-tab>
</uib-tabset>
</div>
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">{{ "app.admin.invoices.date_" | translate }}</span>
<input type="date" ng-model="searchInvoice.date" class="form-control" ng-change="handleFilterChange()">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table class="table invoices-list" ng-if="invoices.length > 0">
<thead>
<tr>
<th style="width:5%"></th>
<th style="width:15%"><a href="" ng-click="setOrderInvoice('reference')">{{ 'app.admin.invoices.invoice_num' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='reference', 'fa fa-sort-numeric-desc': orderInvoice=='-reference', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderInvoice('date')">{{ 'app.admin.invoices.date' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='date', 'fa fa-sort-numeric-desc': orderInvoice=='-date', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderInvoice('total')"> {{ 'app.admin.invoices.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderInvoice('name')">{{ 'app.admin.invoices.customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:30%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="invoice in invoices">
<td class="chained-indicator">
<i class="fa fa-link chained" ng-show="invoice.chained_footprint"/>
<i class="fa fa-chain-broken broken" ng-hide="invoice.chained_footprint"/>
</td>
<td>{{ invoice.reference }}</td>
<td ng-if="!invoice.is_avoir">{{ invoice.date | amDateFormat:'L LTS' }}</td>
<td ng-if="invoice.is_avoir">{{ invoice.date | amDateFormat:'L' }}</td>
<td>{{ invoice.total | currency}}</td>
<td>
<a href="" ui-sref="app.admin.members_edit({id: invoice.user_id})" ng-show="invoice.user_id">{{ invoice.name }}</a>
<span ng-hide="invoice.user_id">{{ invoice.name }}</span>
<td>
<div class="buttons">
<a class="btn btn-default download-button" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="!invoice.is_avoir">
<i class="fa fa-file-pdf-o"></i> {{ 'app.admin.invoices.download_the_invoice' | translate }}
</a>
<a class="btn btn-default" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="invoice.is_avoir">
<i class="fa fa-file-pdf-o"></i> {{ 'app.admin.invoices.download_the_credit_note' | translate }}
</a>
<a class="btn btn-default refund-button" ng-click="generateAvoirForInvoice(invoice)" ng-if="(!invoice.has_avoir || invoice.has_avoir == 'partial') && !invoice.is_avoir && !invoice.prevent_refund && !isDateClosed(invoice.created_at)">
<i class="fa fa-reply"></i> {{ 'app.admin.invoices.credit_note' | translate }}
</a>
</div>
</td>
</tr>
</tbody>
</table>
<div class="text-center">
<button class="btn btn-warning" ng-click="showNextInvoices()" ng-hide="noMoreResults"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'app.admin.invoices.display_more_invoices' | translate }}</button>
</div>
<p ng-if="invoices.length == 0" translate>{{ 'app.admin.invoices.no_invoices_for_now' }}</p>
</div>
</div>
</uib-tab>
<uib-tab heading="{{ 'app.admin.invoices.invoicing_settings' | translate }}" index="1" class="invoices-settings">
<div class="alert alert-warning p-md m-t" role="alert" ng-show="fablabWithoutInvoices">
<i class="fa fa-warning m-r"></i>
<span translate>{{ 'app.admin.invoices.warning_invoices_disabled' }}</span>
</div>
<form class="invoice-placeholder">
<div class="invoice-logo">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!invoice.logo" class="img-responsive">
<img base-sixty-four-image="invoice.logo" ng-if="invoice.logo && invoice.logo.base64">
<div class="tools-box">
<div class="btn-group">
<div class="btn btn-default btn-file">
<i class="fa fa-edit"></i> {{ 'app.admin.invoices.change_logo' | translate }}
<input type="file" accept="image/png,image/jpeg,image/x-png,image/pjpeg" name="invoice[logo][attachment]" ng-model="invoice.logo" base-sixty-four-input>
</div>
</div>
</div>
</div>
<div class="invoice-buyer-infos">
<strong translate>{{ 'app.admin.invoices.john_smith' }}</strong>
<div translate>{{ 'app.admin.invoices.john_smith_at_example_com' }}</div>
</div>
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'app.admin.invoices.invoice_reference_' | translate }} {{mkReference()}}</div>
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'app.admin.invoices.code_' | translate }} {{invoice.code.model}}</div>
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'app.admin.invoices.code_disabled' }}</div>
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'app.admin.invoices.order_num' | translate }} {{mkNumber()}}</div>
<div class="invoice-date">{{ 'app.admin.invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
<div class="invoice-object">
{{ 'app.admin.invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
</div>
<div class="invoice-data">
{{ 'app.admin.invoices.order_summary' | translate }}
<table>
<thead>
<tr>
<th translate>{{ 'app.admin.invoices.details' }}</th>
<th class="right" translate>{{ 'app.admin.invoices.amount' }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ 'app.admin.invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
<td class="right">{{30.0 | currency}}</td>
</tr>
<tr class="invoice-total" ng-class="{'bold vat-line':invoice.VAT.active}">
<td ng-show="!invoice.VAT.active" translate>{{ 'app.admin.invoices.total_amount' }}</td>
<td ng-show="invoice.VAT.active" translate>{{ 'app.admin.invoices.total_including_all_taxes' }}</td>
<td class="right">{{30.0 | currency}}</td>
</tr>
<tr class="invoice-vat invoice-activable" ng-click="openEditVAT()" ng-show="!invoice.VAT.active">
<td translate>{{ 'app.admin.invoices.VAT_disabled' }}</td>
<td></td>
</tr>
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
<td>{{ 'app.admin.invoices.including_VAT' | translate }} {{invoice.VAT.rate}} %</td>
<td>{{30-(30/(invoice.VAT.rate/100+1)) | currency}}</td>
</tr>
<tr class="invoice-ht vat-line italic" ng-show="invoice.VAT.active">
<td translate>{{ 'app.admin.invoices.including_total_excluding_taxes' }}</td>
<td>{{30/(invoice.VAT.rate/100+1) | currency}}</td>
</tr>
<tr class="invoice-payed vat-line bold" ng-show="invoice.VAT.active">
<td translate>{{ 'app.admin.invoices.including_amount_payed_on_ordering' }}</td>
<td>{{30.0 | currency}}</td>
</tr>
</tbody>
</table>
<p class="invoice-payment" translate translate-values="{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT'), AMOUNT:(30.0 | currency)}">
{{ 'app.admin.invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
</p>
</div>
<div medium-editor class="invoice-text invoice-editable" ng-model="invoice.text.content"
options='{
"placeholder": "{{ "app.admin.invoices.important_notes" | translate }}",
"buttons": ["underline"]
}'
ng-blur="textEditEnd($event)">
</div>
<div medium-editor class="invoice-legals invoice-editable" ng-model="invoice.legals.content"
options='{
"placeholder": "{{ "app.admin.invoices.address_and_legal_information" | translate }}",
"buttons": ["bold", "underline"]
}'
ng-blur="legalsEditEnd($event)">
</div>
</form>
</uib-tab>
<uib-tab heading="{{ 'app.admin.invoices.accounting_codes' | translate }}" index="2" class="accounting-codes-tab">
<div class="panel panel-default m-t-md accounting-codes">
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<label for="journalCode" translate>{{ 'app.admin.invoices.accounting_journal_code' }}</label>
<input type="text" id="journalCode" ng-model="settings.journalCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_journal_code' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="cardClientCode" translate>{{ 'app.admin.invoices.accounting_card_client_code' }}</label>
<input type="text" id="cardClientCode" ng-model="settings.cardClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.card_client_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="cardClientLabel" translate>{{ 'app.admin.invoices.accounting_card_client_label' }}</label>
<input type="text" id="cardClientLabel" ng-model="settings.cardClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.card_client_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="walletClientCode" translate>{{ 'app.admin.invoices.accounting_wallet_client_code' }}</label>
<input type="text" id="walletClientCode" ng-model="settings.walletClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.wallet_client_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="walletClientLabel" translate>{{ 'app.admin.invoices.accounting_wallet_client_label' }}</label>
<input type="text" id="walletClientLabel" ng-model="settings.walletClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.wallet_client_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="otherClientCode" translate>{{ 'app.admin.invoices.accounting_other_client_code' }}</label>
<input type="text" id="otherClientCode" ng-model="settings.otherClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.other_client_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="otherClientLabel" translate>{{ 'app.admin.invoices.accounting_other_client_label' }}</label>
<input type="text" id="otherClientLabel" ng-model="settings.otherClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.other_client_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="walletCode" translate>{{ 'app.admin.invoices.accounting_wallet_code' }}</label>
<input type="text" id="walletCode" ng-model="settings.walletCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_wallet_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="walletLabel" translate>{{ 'app.admin.invoices.accounting_wallet_label' }}</label>
<input type="text" id="walletLabel" ng-model="settings.walletLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_wallet_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="vatCode" translate>{{ 'app.admin.invoices.accounting_vat_code' }}</label>
<input type="text" id="vatCode" ng-model="settings.vatCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_vat_code' | translate }}"/>
</div>
<div class="col-md-6">
<label for="vatLabel" translate>{{ 'app.admin.invoices.accounting_vat_label' }}</label>
<input type="text" id="vatLabel" ng-model="settings.vatLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_vat_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="subscriptionCode" translate>{{ 'app.admin.invoices.accounting_subscription_code' }}</label>
<input type="text" id="subscriptionCode" ng-model="settings.subscriptionCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_subscription_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="subscriptionLabel" translate>{{ 'app.admin.invoices.accounting_subscription_label' }}</label>
<input type="text" id="subscriptionLabel" ng-model="settings.subscriptionLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_subscription_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="machineCode" translate>{{ 'app.admin.invoices.accounting_Machine_code' }}</label>
<input type="text" id="machineCode" ng-model="settings.machineCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_machine_code' | translate }}"/>
</div>
<div class="col-md-6">
<label for="machineLabel" translate>{{ 'app.admin.invoices.accounting_Machine_label' }}</label>
<input type="text" id="machineLabel" ng-model="settings.machineLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_machine_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="trainingCode" translate>{{ 'app.admin.invoices.accounting_Training_code' }}</label>
<input type="text" id="trainingCode" ng-model="settings.trainingCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_training_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="trainingLabel" translate>{{ 'app.admin.invoices.accounting_Training_label' }}</label>
<input type="text" id="trainingLabel" ng-model="settings.trainingLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_training_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="eventCode" translate>{{ 'app.admin.invoices.accounting_Event_code' }}</label>
<input type="text" id="eventCode" ng-model="settings.eventCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_event_code' | translate }}"/>
</div>
<div class="col-md-6">
<label for="eventLabel" translate>{{ 'app.admin.invoices.accounting_Event_label' }}</label>
<input type="text" id="eventLabel" ng-model="settings.eventLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_event_label' | translate }}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="spaceCode" translate>{{ 'app.admin.invoices.accounting_Space_code' }}</label>
<input type="text" id="spaceCode" ng-model="settings.spaceCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_space_code' | translate }}" />
</div>
<div class="col-md-6">
<label for="spaceLabel" translate>{{ 'app.admin.invoices.accounting_Space_label' }}</label>
<input type="text" id="spaceLabel" ng-model="settings.spaceLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_space_label' | translate }}"/>
</div>
</div>
<button name="button" class="btn btn-warning m-t-lg" ng-click="save()" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>
<div class="col-md-12" ng-if="isAuthorized('manager')">
<ng-include src="'<%= asset_path "admin/invoices/list.html" %>'"></ng-include>
</div>
</div>
</section>
<script type="text/ng-template" id="editReference.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.invoice_reference' }}</h3>
</div>
<div class="modal-body row">
<div class="elements col-md-4">
<h4>Éléments</h4>
<ul>
<li ng-click="invoice.reference.help = 'addYear.html'">{{ 'app.admin.invoices.year' | translate }}</li>
<li ng-click="invoice.reference.help = 'addMonth.html'">{{ 'app.admin.invoices.month' | translate }}</li>
<li ng-click="invoice.reference.help = 'addDay.html'">{{ 'app.admin.invoices.day' | translate }}</li>
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ 'app.admin.invoices.num_of_invoice' | translate }}</li>
<li ng-click="invoice.reference.help = 'addOnlineInfo.html'">{{ 'app.admin.invoices.online_sales' | translate }}</li>
<%# <li ng-click="invoice.reference.help = 'addWalletInfo.html'">{{ 'app.admin.invoices.wallet' | translate }}</li> %>
<li ng-click="invoice.reference.help = 'addRefundInfo.html'">{{ 'app.admin.invoices.refund' | translate }}</li>
</ul>
</div>
<div class="col-md-8">
<div class="model">
<h4 translate>{{ 'app.admin.invoices.model' }}</h4>
<input type="text" class="form-control" ng-model="model">
</div>
<div class="help">
<h4 translate>{{ 'app.admin.invoices.documentation' }}</h4>
<ng-include src="invoice.reference.help" autoscroll="true">
</ng-include>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>
<script type="text/ng-template" id="addYear.html">
<table class="invoice-element-legend">
<tr><td><strong>YY</strong></td><td translate>{{ 'app.admin.invoices.2_digits_year' }}</td></tr>
<tr><td><strong>YYYY</strong></td><td translate>{{ 'app.admin.invoices.4_digits_year' }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addMonth.html">
<table class="invoice-element-legend">
<tr><td><strong>M</strong></td><td translate>{{ 'app.admin.invoices.month_number' }}</td></tr>
<tr><td><strong>MM</strong></td><td translate>{{ 'app.admin.invoices.2_digits_month_number' }}</td></tr>
<tr><td><strong>MMM</strong></td><td translate>{{ 'app.admin.invoices.3_characters_month_name' }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addDay.html">
<table class="invoice-element-legend">
<tr><td><strong>D</strong></td><td translate>{{ 'app.admin.invoices.day_in_the_month' }}</td></tr>
<tr><td><strong>DD</strong></td><td translate>{{ 'app.admin.invoices.2_digits_day_in_the_month' }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addInvoiceNumber.html">
<table class="invoice-element-legend">
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'app.admin.invoices.n_digits_daily_count_of_invoices' }}</td></tr>
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'app.admin.invoices.n_digits_monthly_count_of_invoices' }}</td></tr>
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'app.admin.invoices.n_digits_annual_amount_of_invoices' }}</td></tr>
</table>
<span class="bottom-notes" translate>{{ 'app.admin.invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
</script>
<script type="text/ng-template" id="addOrderNumber.html">
<table class="invoice-element-legend">
<tr><td><strong>nn...nn</strong></td><td translate>{{ 'app.admin.invoices.n_digits_count_of_orders' }}</td></tr>
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'app.admin.invoices.n_digits_daily_count_of_orders' }}</td></tr>
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'app.admin.invoices.n_digits_monthly_count_of_orders' }}</td></tr>
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'app.admin.invoices.n_digits_annual_amount_of_orders' }}</td></tr>
</table>
<span class="bottom-notes" translate>{{ 'app.admin.invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
</script>
<script type="text/ng-template" id="addOnlineInfo.html">
<table class="invoice-element-legend">
<tr><td><strong>X[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe' | translate }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addWalletInfo.html">
<table class="invoice-element-legend">
<tr><td><strong>W[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_WPM_will_add_PM_to_the_invoices_settled_with_wallet' | translate }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addRefundInfo.html">
<table class="invoice-element-legend">
<tr><td><strong>R[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_RA_will_add_A_to_the_refund_invoices' | translate }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="editCode.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.code' }}</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="enableCode" class="control-label" translate>{{ 'app.admin.invoices.enable_the_code' }}</label>
<input bs-switch
ng-model="isSelected"
id="enableCode"
type="checkbox"
class="form-control m-l-sm"
switch-on-text="{{ 'app.admin.invoices.enabled' | translate }}"
switch-off-text="{{ 'app.admin.invoices.disabled' | translate }}"
switch-animate="true"/>
</div>
<div class="form-group" ng-show="isSelected">
<label for="codeModel" class="control-label" translate>{{ 'app.admin.invoices.code' }}</label>
<input id="codeModel" type="text" ng-model="codeModel" class="form-control"/>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>
<script type="text/ng-template" id="editNumber.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.order_number' }}</h3>
</div>
<div class="modal-body row">
<div class="elements col-md-4">
<h4 translate>{{ 'app.admin.invoices.elements' }}</h4>
<ul>
<li ng-click="invoice.number.help = 'addYear.html'">{{ 'app.admin.invoices.year' | translate }}</li>
<li ng-click="invoice.number.help = 'addMonth.html'">{{ 'app.admin.invoices.month' | translate }}</li>
<li ng-click="invoice.number.help = 'addDay.html'">{{ 'app.admin.invoices.day' | translate }}</li>
<li ng-click="invoice.number.help = 'addOrderNumber.html'">{{ 'app.admin.invoices.order_num' | translate }}</li>
</ul>
</div>
<div class="col-md-8">
<div class="model">
<h4 translate>{{ 'app.admin.invoices.model' }}</h4>
<input type="text" class="form-control" ng-model="model">
</div>
<div class="help">
<h4 translate>{{ 'app.admin.invoices.documentation' }}</h4>
<ng-include src="invoice.number.help" autoscroll="true">
</ng-include>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>
<script type="text/ng-template" id="editVAT.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.VAT' }}</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="enableVAT" class="control-label" translate>{{ 'app.admin.invoices.enable_VAT' }}</label>
<input bs-switch
ng-model="isSelected"
id="enableVAT"
type="checkbox"
class="form-control m-l-sm"
switch-on-text="{{ 'app.admin.invoices.enabled' | translate }}"
switch-off-text="{{ 'app.admin.invoices.disabled' | translate }}"
switch-animate="true"/>
</div>
<div class="form-group" ng-show="isSelected">
<label for="vatRate" class="control-label" translate>{{ 'app.admin.invoices.VAT_rate' }}</label>
<div class="input-group">
<span class="input-group-addon">% </span>
<input id="vatRate" type="number" ng-model="rate" class="form-control" min="0" max="100"/>
</div>
</div>
<div class="m-t-lg">
<h4 translate>{{ 'app.admin.invoices.VAT_history' }}</h4>
<table class="table scrollable-3-cols">
<thead>
<tr>
<th translate>{{ 'app.admin.invoices.VAT_rate' }}</th>
<th translate>{{ 'app.admin.invoices.changed_at' }}</th>
<th translate>{{ 'app.admin.invoices.changed_by' }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="value in history | orderBy:'-date'">
<td>
<span class="no-user-label" ng-show="value.enabled === false" translate>{{'app.admin.invoices.VAT_disabled'}}</span>
<span class="no-user-label" ng-show="value.enabled === true" translate>{{'app.admin.invoices.VAT_enabled'}}</span>
<span ng-show="value.rate">{{value.rate}}</span>
</td>
<td>{{value.date | amDateFormat:'L LT'}}</td>
<td>{{value.user.name}}<span class="no-user-label" ng-hide="value.user" translate>{{ 'app.admin.invoices.deleted_user' }}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>

View File

@ -0,0 +1,86 @@
<h3 class="m-t-xs"><i class="fa fa-filter"></i> {{ 'app.admin.invoices.filter_invoices' | translate }}</h3>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" translate>{{ 'app.admin.invoices.invoice_num_' }}</span>
<input type="text" ng-model="searchInvoice.reference" class="form-control" placeholder="" ng-change="handleFilterChange()">
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" translate>{{ 'app.admin.invoices.customer_' }}</span>
<input type="text" ng-model="searchInvoice.name" class="form-control" placeholder="" ng-change="handleFilterChange()">
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">{{ "app.admin.invoices.date_" | translate }}</span>
<input type="date" ng-model="searchInvoice.date" class="form-control" ng-change="handleFilterChange()">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table class="table invoices-list" ng-if="invoices.length > 0">
<thead>
<tr>
<th style="width:5%"></th>
<th style="width:15%"><a href="" ng-click="setOrderInvoice('reference')">{{ 'app.admin.invoices.invoice_num' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='reference', 'fa fa-sort-numeric-desc': orderInvoice=='-reference', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderInvoice('date')">{{ 'app.admin.invoices.date' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='date', 'fa fa-sort-numeric-desc': orderInvoice=='-date', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderInvoice('total')"> {{ 'app.admin.invoices.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderInvoice('name')">{{ 'app.admin.invoices.customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:30%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="invoice in invoices">
<td class="chained-indicator">
<i class="fa fa-link chained" ng-show="invoice.chained_footprint"/>
<i class="fa fa-chain-broken broken" ng-hide="invoice.chained_footprint"/>
</td>
<td>{{ invoice.reference }}</td>
<td ng-if="!invoice.is_avoir">{{ invoice.date | amDateFormat:'L LTS' }}</td>
<td ng-if="invoice.is_avoir">{{ invoice.date | amDateFormat:'L' }}</td>
<td>{{ invoice.total | currency}}</td>
<td>
<a href="" ui-sref="app.admin.members_edit({id: invoice.user_id})" ng-show="invoice.user_id">{{ invoice.name }}</a>
<span ng-hide="invoice.user_id">{{ invoice.name }}</span>
<td>
<div class="buttons">
<a class="btn btn-default download-button" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="!invoice.is_avoir">
<i class="fa fa-file-pdf-o"></i> {{ 'app.admin.invoices.download_the_invoice' | translate }}
</a>
<a class="btn btn-default" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="invoice.is_avoir">
<i class="fa fa-file-pdf-o"></i> {{ 'app.admin.invoices.download_the_credit_note' | translate }}
</a>
<a class="btn btn-default refund-button" ng-click="generateAvoirForInvoice(invoice)" ng-if="(!invoice.has_avoir || invoice.has_avoir == 'partial') && !invoice.is_avoir && !invoice.prevent_refund && !isDateClosed(invoice.created_at)">
<i class="fa fa-reply"></i> {{ 'app.admin.invoices.credit_note' | translate }}
</a>
</div>
</td>
</tr>
</tbody>
</table>
<div class="text-center">
<button class="btn btn-warning" ng-click="showNextInvoices()" ng-hide="noMoreResults"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'app.admin.invoices.display_more_invoices' | translate }}</button>
</div>
<p ng-if="invoices.length == 0" translate>{{ 'app.admin.invoices.no_invoices_for_now' }}</p>
</div>
</div>

View File

@ -0,0 +1,310 @@
<div class="alert alert-warning p-md m-t" role="alert" ng-show="fablabWithoutInvoices">
<i class="fa fa-warning m-r"></i>
<span translate>{{ 'app.admin.invoices.warning_invoices_disabled' }}</span>
</div>
<form class="invoice-placeholder">
<div class="invoice-logo">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!invoice.logo" class="img-responsive">
<img base-sixty-four-image="invoice.logo" ng-if="invoice.logo && invoice.logo.base64">
<div class="tools-box">
<div class="btn-group">
<div class="btn btn-default btn-file">
<i class="fa fa-edit"></i> {{ 'app.admin.invoices.change_logo' | translate }}
<input type="file" accept="image/png,image/jpeg,image/x-png,image/pjpeg" name="invoice[logo][attachment]" ng-model="invoice.logo" base-sixty-four-input>
</div>
</div>
</div>
</div>
<div class="invoice-buyer-infos">
<strong translate>{{ 'app.admin.invoices.john_smith' }}</strong>
<div translate>{{ 'app.admin.invoices.john_smith_at_example_com' }}</div>
</div>
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'app.admin.invoices.invoice_reference_' | translate }} {{mkReference()}}</div>
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'app.admin.invoices.code_' | translate }} {{invoice.code.model}}</div>
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'app.admin.invoices.code_disabled' }}</div>
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'app.admin.invoices.order_num' | translate }} {{mkNumber()}}</div>
<div class="invoice-date">{{ 'app.admin.invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
<div class="invoice-object">
{{ 'app.admin.invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
</div>
<div class="invoice-data">
{{ 'app.admin.invoices.order_summary' | translate }}
<table>
<thead>
<tr>
<th translate>{{ 'app.admin.invoices.details' }}</th>
<th class="right" translate>{{ 'app.admin.invoices.amount' }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ 'app.admin.invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
<td class="right">{{30.0 | currency}}</td>
</tr>
<tr class="invoice-total" ng-class="{'bold vat-line':invoice.VAT.active}">
<td ng-show="!invoice.VAT.active" translate>{{ 'app.admin.invoices.total_amount' }}</td>
<td ng-show="invoice.VAT.active" translate>{{ 'app.admin.invoices.total_including_all_taxes' }}</td>
<td class="right">{{30.0 | currency}}</td>
</tr>
<tr class="invoice-vat invoice-activable" ng-click="openEditVAT()" ng-show="!invoice.VAT.active">
<td translate>{{ 'app.admin.invoices.VAT_disabled' }}</td>
<td></td>
</tr>
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
<td>{{ 'app.admin.invoices.including_VAT' | translate }} {{invoice.VAT.rate}} %</td>
<td>{{30-(30/(invoice.VAT.rate/100+1)) | currency}}</td>
</tr>
<tr class="invoice-ht vat-line italic" ng-show="invoice.VAT.active">
<td translate>{{ 'app.admin.invoices.including_total_excluding_taxes' }}</td>
<td>{{30/(invoice.VAT.rate/100+1) | currency}}</td>
</tr>
<tr class="invoice-payed vat-line bold" ng-show="invoice.VAT.active">
<td translate>{{ 'app.admin.invoices.including_amount_payed_on_ordering' }}</td>
<td>{{30.0 | currency}}</td>
</tr>
</tbody>
</table>
<p class="invoice-payment" translate translate-values="{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT'), AMOUNT:(30.0 | currency)}">
{{ 'app.admin.invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
</p>
</div>
<div medium-editor class="invoice-text invoice-editable" ng-model="invoice.text.content"
options='{
"placeholder": "{{ "app.admin.invoices.important_notes" | translate }}",
"buttons": ["underline"]
}'
ng-blur="textEditEnd($event)">
</div>
<div medium-editor class="invoice-legals invoice-editable" ng-model="invoice.legals.content"
options='{
"placeholder": "{{ "app.admin.invoices.address_and_legal_information" | translate }}",
"buttons": ["bold", "underline"]
}'
ng-blur="legalsEditEnd($event)">
</div>
</form>
<script type="text/ng-template" id="editReference.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.invoice_reference' }}</h3>
</div>
<div class="modal-body row">
<div class="elements col-md-4">
<h4>Éléments</h4>
<ul>
<li ng-click="invoice.reference.help = 'addYear.html'">{{ 'app.admin.invoices.year' | translate }}</li>
<li ng-click="invoice.reference.help = 'addMonth.html'">{{ 'app.admin.invoices.month' | translate }}</li>
<li ng-click="invoice.reference.help = 'addDay.html'">{{ 'app.admin.invoices.day' | translate }}</li>
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ 'app.admin.invoices.num_of_invoice' | translate }}</li>
<li ng-click="invoice.reference.help = 'addOnlineInfo.html'">{{ 'app.admin.invoices.online_sales' | translate }}</li>
<%# <li ng-click="invoice.reference.help = 'addWalletInfo.html'">{{ 'app.admin.invoices.wallet' | translate }}</li> %>
<li ng-click="invoice.reference.help = 'addRefundInfo.html'">{{ 'app.admin.invoices.refund' | translate }}</li>
</ul>
</div>
<div class="col-md-8">
<div class="model">
<h4 translate>{{ 'app.admin.invoices.model' }}</h4>
<input type="text" class="form-control" ng-model="model">
</div>
<div class="help">
<h4 translate>{{ 'app.admin.invoices.documentation' }}</h4>
<ng-include src="invoice.reference.help" autoscroll="true">
</ng-include>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>
<script type="text/ng-template" id="addYear.html">
<table class="invoice-element-legend">
<tr><td><strong>YY</strong></td><td translate>{{ 'app.admin.invoices.2_digits_year' }}</td></tr>
<tr><td><strong>YYYY</strong></td><td translate>{{ 'app.admin.invoices.4_digits_year' }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addMonth.html">
<table class="invoice-element-legend">
<tr><td><strong>M</strong></td><td translate>{{ 'app.admin.invoices.month_number' }}</td></tr>
<tr><td><strong>MM</strong></td><td translate>{{ 'app.admin.invoices.2_digits_month_number' }}</td></tr>
<tr><td><strong>MMM</strong></td><td translate>{{ 'app.admin.invoices.3_characters_month_name' }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addDay.html">
<table class="invoice-element-legend">
<tr><td><strong>D</strong></td><td translate>{{ 'app.admin.invoices.day_in_the_month' }}</td></tr>
<tr><td><strong>DD</strong></td><td translate>{{ 'app.admin.invoices.2_digits_day_in_the_month' }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addInvoiceNumber.html">
<table class="invoice-element-legend">
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'app.admin.invoices.n_digits_daily_count_of_invoices' }}</td></tr>
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'app.admin.invoices.n_digits_monthly_count_of_invoices' }}</td></tr>
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'app.admin.invoices.n_digits_annual_amount_of_invoices' }}</td></tr>
</table>
<span class="bottom-notes" translate>{{ 'app.admin.invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
</script>
<script type="text/ng-template" id="addOrderNumber.html">
<table class="invoice-element-legend">
<tr><td><strong>nn...nn</strong></td><td translate>{{ 'app.admin.invoices.n_digits_count_of_orders' }}</td></tr>
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'app.admin.invoices.n_digits_daily_count_of_orders' }}</td></tr>
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'app.admin.invoices.n_digits_monthly_count_of_orders' }}</td></tr>
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'app.admin.invoices.n_digits_annual_amount_of_orders' }}</td></tr>
</table>
<span class="bottom-notes" translate>{{ 'app.admin.invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
</script>
<script type="text/ng-template" id="addOnlineInfo.html">
<table class="invoice-element-legend">
<tr><td><strong>X[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe' | translate }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addWalletInfo.html">
<table class="invoice-element-legend">
<tr><td><strong>W[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_WPM_will_add_PM_to_the_invoices_settled_with_wallet' | translate }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="addRefundInfo.html">
<table class="invoice-element-legend">
<tr><td><strong>R[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_RA_will_add_A_to_the_refund_invoices' | translate }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="editCode.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.code' }}</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="enableCode" class="control-label" translate>{{ 'app.admin.invoices.enable_the_code' }}</label>
<input bs-switch
ng-model="isSelected"
id="enableCode"
type="checkbox"
class="form-control m-l-sm"
switch-on-text="{{ 'app.admin.invoices.enabled' | translate }}"
switch-off-text="{{ 'app.admin.invoices.disabled' | translate }}"
switch-animate="true"/>
</div>
<div class="form-group" ng-show="isSelected">
<label for="codeModel" class="control-label" translate>{{ 'app.admin.invoices.code' }}</label>
<input id="codeModel" type="text" ng-model="codeModel" class="form-control"/>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>
<script type="text/ng-template" id="editNumber.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.order_number' }}</h3>
</div>
<div class="modal-body row">
<div class="elements col-md-4">
<h4 translate>{{ 'app.admin.invoices.elements' }}</h4>
<ul>
<li ng-click="invoice.number.help = 'addYear.html'">{{ 'app.admin.invoices.year' | translate }}</li>
<li ng-click="invoice.number.help = 'addMonth.html'">{{ 'app.admin.invoices.month' | translate }}</li>
<li ng-click="invoice.number.help = 'addDay.html'">{{ 'app.admin.invoices.day' | translate }}</li>
<li ng-click="invoice.number.help = 'addOrderNumber.html'">{{ 'app.admin.invoices.order_num' | translate }}</li>
</ul>
</div>
<div class="col-md-8">
<div class="model">
<h4 translate>{{ 'app.admin.invoices.model' }}</h4>
<input type="text" class="form-control" ng-model="model">
</div>
<div class="help">
<h4 translate>{{ 'app.admin.invoices.documentation' }}</h4>
<ng-include src="invoice.number.help" autoscroll="true">
</ng-include>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>
<script type="text/ng-template" id="editVAT.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.VAT' }}</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="enableVAT" class="control-label" translate>{{ 'app.admin.invoices.enable_VAT' }}</label>
<input bs-switch
ng-model="isSelected"
id="enableVAT"
type="checkbox"
class="form-control m-l-sm"
switch-on-text="{{ 'app.admin.invoices.enabled' | translate }}"
switch-off-text="{{ 'app.admin.invoices.disabled' | translate }}"
switch-animate="true"/>
</div>
<div class="form-group" ng-show="isSelected">
<label for="vatRate" class="control-label" translate>{{ 'app.admin.invoices.VAT_rate' }}</label>
<div class="input-group">
<span class="input-group-addon">% </span>
<input id="vatRate" type="number" ng-model="rate" class="form-control" min="0" max="100"/>
</div>
</div>
<div class="m-t-lg">
<h4 translate>{{ 'app.admin.invoices.VAT_history' }}</h4>
<table class="table scrollable-3-cols">
<thead>
<tr>
<th translate>{{ 'app.admin.invoices.VAT_rate' }}</th>
<th translate>{{ 'app.admin.invoices.changed_at' }}</th>
<th translate>{{ 'app.admin.invoices.changed_by' }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="value in history | orderBy:'-date'">
<td>
<span class="no-user-label" ng-show="value.enabled === false" translate>{{'app.admin.invoices.VAT_disabled'}}</span>
<span class="no-user-label" ng-show="value.enabled === true" translate>{{'app.admin.invoices.VAT_enabled'}}</span>
<span ng-show="value.rate">{{value.rate}}</span>
</td>
<td>{{value.date | amDateFormat:'L LT'}}</td>
<td>{{value.user.name}}<span class="no-user-label" ng-hide="value.user" translate>{{ 'app.admin.invoices.deleted_user' }}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>

View File

@ -0,0 +1,34 @@
<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">
<section class="heading-title">
<h1 translate>{{ 'app.admin.manager_new.add_a_manager' }}</h1>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class=" col-sm-12 col-md-9 b-r nopadding">
<form role="form" name="managerForm" class="form-horizontal" novalidate>
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
<ng-include src="'<%= asset_path "shared/_manager_form.html" %>'"></ng-include>
</div>
<div class="panel-footer no-padder">
<input type="submit" value="{{ 'app.shared.buttons.save' | translate}}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-click="saveManager()" ng-disabled="managerForm.$invalid"/>
</div>
</section>
</form>
</div>
</div>

View File

@ -7,7 +7,9 @@
</div>
</div>
<div class="col-md-12">
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" translate>{{ 'app.admin.members.add_a_new_administrator' }}</button>
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" ng-show="isAuthorized('admin')" translate>
{{ 'app.admin.members.add_a_new_administrator' }}
</button>
<table class="table">
<thead>
@ -29,7 +31,7 @@
<td>{{ admin.email }}</td>
<td>{{ admin.profile_attributes.phone }}</td>
<td>
<button class="btn btn-danger" ng-if="admin.id != currentUser.id" ng-click="destroyAdmin(admins, admin)">
<button class="btn btn-danger" ng-if="isAuthorized('admin') && admin.id != currentUser.id" ng-click="destroyAdmin(admins, admin)">
<i class="fa fa-trash-o"></i>
</button>
</td>

View File

@ -76,8 +76,13 @@
<p>
{{ 'app.admin.members_edit.price_' | translate }} {{ subscription.plan.amount | currency}}
</p>
<button class="btn btn-default" ng-click="updateSubscriptionModal(subscription, true)" translate>{{ 'app.admin.members_edit.offer_free_days' }}</button>
<button class="btn btn-default" ng-click="updateSubscriptionModal(subscription, false)" translate>{{ 'app.admin.members_edit.extend_subscription' }}</button>
<div ng-hide="user.id === currentUser.id">
<button class="btn btn-default" ng-click="updateSubscriptionModal(subscription, true)" translate>{{ 'app.admin.members_edit.offer_free_days' }}</button>
<button class="btn btn-default" ng-click="updateSubscriptionModal(subscription, false)" translate>{{ 'app.admin.members_edit.extend_subscription' }}</button>
</div>
<p class="alert alert-info" ng-show="user.id === currentUser.id" translate>
{{ 'app.admin.members_edit.cannot_extend_own_subscription' }}
</p>
</div>
@ -229,9 +234,12 @@
<ng-include src="'<%= asset_path "wallet/show.html" %>'"></ng-include>
<div class="clearfix"></div>
<div class="col-sm-4 text-center">
<div class="col-sm-4 text-center" ng-hide="user.id === currentUser.id">
<button type="button" class="btn btn-warning m-t m-b" ng-click="createWalletCreditModal(user, wallet)" translate>{{ 'app.admin.members_edit.to_credit' }}</button>
</div>
<p class="col-sm-4 alert alert-info" ng-show="user.id === currentUser.id" translate>
{{ 'app.admin.members_edit.cannot_credit_own_wallet' }}
</p>
</div>

View File

@ -11,7 +11,7 @@
</section>
</div>
<div class="col-xs-1 col-xs-offset-1 col-md-offset-2 b-l">
<section class="heading-actions wrapper">
<section class="heading-actions wrapper" ng-show="isAuthorized('admin')">
<a role="button" class="btn btn-default b-2x rounded m-t-sm import-members" ui-sref="app.admin.members_import">
<i class="fa fa-cloud-upload"></i>
</a>
@ -30,7 +30,7 @@
post-render="setupMembersTour">
<div class="row">
<div class="col-md-12">
<uib-tabset justified="true" active="tabs.active">
<uib-tabset justified="true" active="tabs.active" ng-if="isAuthorized('admin')">
<uib-tab heading="{{ 'app.admin.members.users' | translate }}" index="0">
<ng-include src="'<%= asset_path "admin/members/users.html" %>'"></ng-include>
@ -48,6 +48,10 @@
<div ui-view="authentification"></div>
</uib-tab>
</uib-tabset>
<div ng-if="isAuthorized('manager')">
<ng-include src="'<%= asset_path "admin/members/users.html" %>'"></ng-include>
</div>
</div>
</div>
</section>

View File

@ -1,35 +1,44 @@
<p class="alert alert-info m-t-lg" translate>
{{ 'app.admin.members.managers_info' }}
</p>
<div class="col-md-5 m-t-lg">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'app.admin.members.search_for_an_administrator' | translate }}">
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'app.admin.members.search_for_a_manager' | translate }}">
</div>
</div>
</div>
<div class="col-md-12">
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" translate>{{ 'app.admin.members.add_a_new_administrator' }}</button>
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.managers_new" ng-show="isAuthorized('admin')" translate>
{{ 'app.admin.members.add_a_new_manager' }}
</button>
<table class="table">
<thead>
<tr>
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderManager('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderManager('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderAdmin('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderManager('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderManager('phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
<th style="width:10%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="admin in admins | filter:searchFilter | orderBy: orderAdmin">
<td class="text-c">{{ admin.profile_attributes.last_name }}</td>
<td class="text-c">{{ admin.profile_attributes.first_name }}</td>
<td>{{ admin.email }}</td>
<td>{{ admin.profile_attributes.phone }}</td>
<tr ng-repeat="manager in managers | filter:searchFilter | orderBy: orderManager">
<td class="text-c">{{ manager.last_name }}</td>
<td class="text-c">{{ manager.first_name }}</td>
<td>{{ manager.email }}</td>
<td>{{ manager.phone }}</td>
<td>
<button class="btn btn-danger" ng-if="admin.id != currentUser.id" ng-click="destroyAdmin(admins, admin)">
<button class="btn btn-default edit-member" ui-sref="app.admin.members_edit({id: manager.id})">
<i class="fa fa-edit"></i>
</button>
<button class="btn btn-danger" ng-if="isAuthorized('admin')" ng-click="destroyManager(managers, manager)">
<i class="fa fa-trash-o"></i>
</button>
</td>

View File

@ -18,8 +18,10 @@
</div>
</div>
<div class="col-md-12">
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.members_new" translate>{{ 'app.admin.members.add_a_new_member' }}</button>
<div class="pull-right exports-buttons">
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.members_new" translate>
{{ 'app.admin.members.add_a_new_member' }}
</button>
<div class="pull-right exports-buttons" ng-show="isAuthorized('admin')">
<a class="btn btn-default" ng-href="api/members/export_members.xlsx" target="export-frame" ng-click="alertExport('members')">
<i class="fa fa-file-excel-o"></i> {{ 'app.admin.members.members' | translate }}
</a>
@ -57,7 +59,7 @@
<button class="btn btn-default edit-member" ui-sref="app.admin.members_edit({id: m.id})">
<i class="fa fa-edit"></i>
</button>
<button class="btn btn-danger delete-member" ng-click="deleteMember(m.id)">
<button class="btn btn-danger delete-member" ng-click="deleteMember(m.id)" ng-show="isAuthorized('admin')">
<i class="fa fa-trash"></i>
</button>
<span class="label label-danger text-white" ng-show="m.need_completion" translate>{{ 'app.shared.user_admin.incomplete_profile' }}</span>

View File

@ -33,7 +33,7 @@
<td>{{ partner.email }}</td>
<td><a ui-sref="app.admin.plans.edit({id:partner.resource.id})">{{ partner.resource ? partner.resource.base_name : '' }}</a></td>
<td>
<button class="btn btn-danger" ng-if="partner.id != currentUser.id" ng-click="destroyPartner(partners, partner)">
<button class="btn btn-danger" ng-if="!partner.resource" ng-click="destroyPartner(partners, partner)">
<i class="fa fa-trash-o"></i>
</button>
</td>

View File

@ -1,18 +1,18 @@
<uib-tabset justified="true" active="tabs.sub" class="m-t">
<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.members' | translate }}" index="0">
<uib-tab classes="{{isAuthorized('admin') ? 'level-2-tab' : ''}}" heading="{{ 'app.admin.members.members' | translate }}" index="0">
<ng-include src="'<%= asset_path "admin/members/members.html" %>'"></ng-include>
</uib-tab>
<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.administrators' | translate }}" class="admins-tab" index="1">
<uib-tab classes="{{isAuthorized('admin') ? 'level-2-tab' : ''}}" heading="{{ 'app.admin.members.administrators' | translate }}" class="admins-tab" index="1">
<ng-include src="'<%= asset_path "admin/members/administrators.html" %>'"></ng-include>
</uib-tab>
<!--<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.managers' | translate }}" class="admins-tab" index="2">
<uib-tab classes="{{isAuthorized('admin') ? 'level-2-tab' : ''}}" heading="{{ 'app.admin.members.managers' | translate }}" class="admins-tab" index="2">
<ng-include src="'<%= asset_path "admin/members/managers.html" %>'"></ng-include>
</uib-tab>-->
</uib-tab>
<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.partners' | translate }}" class="admins-tab" index="3">
<uib-tab classes="{{isAuthorized('admin') ? 'level-2-tab' : ''}}" heading="{{ 'app.admin.members.partners' | translate }}" class="admins-tab" index="3" ng-show="isAuthorized('admin')">
<ng-include src="'<%= asset_path "admin/members/partners.html" %>'"></ng-include>
</uib-tab>

View File

@ -35,7 +35,7 @@
ng-disabled="method == 'PATCH'">
<option value="all" translate>{{ 'app.shared.plan.transversal_all_groups' }}</option>
<optgroup label="Groupes">
<option ng-repeat="group in groups" value="{{group.id}}" ng-selected="plan.group_id == group.id">{{group.name}}</option>
<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>{{ 'app.shared.plan.group_is_required' }}</span>

View File

@ -17,8 +17,6 @@
</section>
</div>
</div>
</section>

View File

@ -35,7 +35,7 @@
<uib-tabset justified="true" active="tabs.active">
<uib-tab heading="{{ 'app.admin.trainings.trainings' | translate }}" index="0" class="manage-trainings">
<div class="m-t m-b">
<button type="button" class="btn btn-warning" ui-sref="app.admin.trainings_new">
<button type="button" class="btn btn-warning" ui-sref="app.admin.trainings_new" ng-show="isAuthorized('admin')">
<i class="fa fa-plus m-r"></i>
<span translate>{{ 'app.admin.trainings.add_a_new_training' }}</span>
</button>
@ -64,7 +64,7 @@
<td>{{ showMachines(training) }}</td>
<td>{{ training.nb_total_places }}</td>
<td>
<div class="buttons">
<div class="buttons" ng-show="isAuthorized('admin')">
<button class="btn btn-default" ui-sref="app.admin.trainings_edit({id:training.id})">
<i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}
</button>

View File

@ -1,5 +1,4 @@
<div>
<section class="heading">
<div class="row no-gutter">
<ng-include src="'<%= asset_path "dashboard/nav.html" %>'"></ng-include>
@ -7,7 +6,6 @@
</section>
<div class="row no-gutter">
<div class="col-md-12 m m-t-lg">
<ng-include src="'<%= asset_path "wallet/show.html" %>'"></ng-include>
@ -17,5 +15,4 @@
<ng-include src="'<%= asset_path "wallet/transactions.html" %>'"></ng-include>
</div>
</div>
</div>
</div>

View File

@ -67,7 +67,7 @@
<span class="text-black-light text-xs" ng-if="event.nb_free_places > 0">{{event.nb_free_places}} {{ 'app.public.events_list.still_available' | translate }}</span>
<span class="text-black-light text-xs" ng-if="event.nb_total_places > 0 && event.nb_free_places <= 0" translate>{{ 'app.public.events_list.sold_out' }}</span>
<span class="text-black-light text-xs" ng-if="event.nb_total_places == -1" translate>{{ 'app.public.events_list.cancelled' }}</span>
<span class="text-black-light text-xs" ng-if="!event.nb_total_places" translate>{{ 'app.public.events_list.free_entry' }}</span>
<span class="text-black-light text-xs" ng-if="!event.nb_total_places" translate>{{ 'app.public.events_list.without_reservation' }}</span>
</div>
</div>

View File

@ -14,11 +14,11 @@
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized('admin')">
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin', 'manager'])">
<section class="heading-actions wrapper">
<a ui-sref="app.admin.events_edit({id: event.id})" ng-if="isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}</a>
<a ng-click="deleteEvent(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>
<a ui-sref="app.admin.events_edit({id: event.id})" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}</a>
<a ng-click="deleteEvent(event)" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
</section>
</div>
@ -62,7 +62,7 @@
</ul>
</section>
<div ng-if="currentUser.role === 'admin'">
<div ng-if="isAuthorized(['admin', 'manager'])">
<select-member></select-member>
</div>
@ -102,7 +102,7 @@
<div ng-if="event.nb_total_places == -1"><span class="badge font-sbold" translate>{{ 'app.public.events_show.cancelled' }}</span></div>
</div>
<div class="text-sm m-b" ng-if="!event.nb_total_places">
<div><span class="badge font-sbold" translate>{{ 'app.public.events_show.free_entry' }}</span></div>
<div><span class="badge font-sbold" translate>{{ 'app.public.events_show.without_reservation' }}</span></div>
</div>

View File

@ -42,7 +42,7 @@
<i class="fa fa-user red col-xs-3 padder-icon"></i>
<h6 class="m-n col-xs-9 ">
<span ng-if="event.nb_free_places > 0">{{ 'app.public.home.still_available' | translate }} {{event.nb_free_places}}</span>
<span ng-if="!event.nb_total_places" translate>{{ 'app.public.home.free_entry' }}</span>
<span ng-if="!event.nb_total_places" translate>{{ 'app.public.home.without_reservation' }}</span>
<span ng-if="event.nb_total_places > 0 && event.nb_free_places <= 0" translate>{{ 'app.public.home.event_full' }}</span>
</h6>
</div>

View File

@ -37,7 +37,7 @@
</select>
</div>
</div>
<div class="col-md-3 col-md-offset-6 m-t-n row-centered" ng-if="isAuthorized('admin')">
<div class="col-md-3 col-md-offset-6 m-t-n row-centered" ng-if="isAuthorized(['admin', 'manager'])">
<a role="button" ui-sref="app.admin.calendar" class="btn btn-lg btn-default rounded m-t-sm text-sm">
<i class="fa fa-calendar-check-o m-r" aria-hidden="true"></i><span translate>{{ 'app.public.machines_list.new_availability' }}</span>
</a>
@ -59,7 +59,7 @@
<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="reserveMachine(machine, $event)" ng-hide="machine.disabled">
<div class="btn btn-default btn-block no-b padder-v red reserve-button" ng-click="reserveMachine(machine, $event)" ng-hide="machine.disabled">
<i class="fa fa-bookmark m-r-xs"></i>
<span class="hidden-sm" translate>{{ 'app.public.machines_list.book' }}</span>
</div>

View File

@ -25,7 +25,7 @@
<div class="col-sm-12 col-md-12 col-lg-3">
<div ng-if="currentUser.role === 'admin'">
<div ng-if="isAuthorized(['admin', 'manager'])">
<select-member></select-member>
</div>

View File

@ -17,7 +17,7 @@
<div class="row no-gutter">
<div class="col-sm-12 col-md-9 b-r">
<div class="row m-t m-b padder" ng-repeat="plansGroup in plansClassifiedByGroup | groupFilter:ctrl.member">
<div class="row m-t m-b padder" ng-repeat="plansGroup in plansClassifiedByGroup | groupFilter:ctrl.member" ng-show="plansGroup.actives > 0">
<div class="col-md-12 text-center">
<h2 class="text-u-c">{{plansGroup.name}}</h2>
@ -36,25 +36,24 @@
<div class="wrap">
<div class="price">
<div class="amount" data-fittext>{{plan.amount | currency}}</div>
<span class="period">{{ plan.interval | planIntervalFilter: plan.interval_count }}</span>
<span class="period">{{ plan.interval | planIntervalFilter: plan.interval_count }}</span>
</div>
</div>
</div>
<div class="cta-button" ng-if="!currentUser || currentUser.role == 'member'">
<button class="btn btn-default rounded" ng-click="selectPlan(plan)" ng-if="currentUser.subscribed_plan.id != plan.id" ng-disabled="currentUser.subscribed_plan" ng-class="{ 'bg-yellow': selectedPlan==plan }">
<span ng-if="currentUser" translate>{{ 'app.public.plans.i_choose_that_plan' }}</span>
<span ng-if="!currentUser" translate>{{ 'app.public.plans.i_subscribe_online' }}</span>
<div class="cta-button" ng-if="!ctrl.member || ctrl.member.role == 'member' || (ctrl.member.role == 'manager' && ctrl.member.id === currentUser.id)">
<button class="btn btn-default rounded" ng-click="selectPlan(plan)" ng-if="ctrl.member.subscribed_plan.id != plan.id" ng-disabled="ctrl.member.subscribed_plan" ng-class="{ 'bg-yellow': selectedPlan==plan }">
<span ng-if="ctrl.member" translate>{{ 'app.public.plans.i_choose_that_plan' }}</span>
<span ng-if="!ctrl.member" translate>{{ 'app.public.plans.i_subscribe_online' }}</span>
</button>
<button class="btn btn-warning bg-yellow rounded" ng-if="currentUser.subscribed_plan.id == plan.id" ng-disabled="currentUser.subscribed_plan.id == plan.id" translate>{{ 'app.public.plans.i_already_subscribed' }}</button>
<button class="btn btn-warning bg-yellow rounded" ng-if="ctrl.member.subscribed_plan.id == plan.id" ng-disabled="ctrl.member.subscribed_plan.id == plan.id" translate>
{{ 'app.public.plans.i_already_subscribed' }}
</button>
</div>
<div class="cta-button" ng-if="currentUser.role == 'admin'">
<div class="cta-button" ng-if="isAuthorized('admin') || (ctrl.member.role == 'manager' && ctrl.member.id != currentUser.id)">
<button class="btn btn-default rounded" ng-click="selectPlan(plan)" ng-class="{ 'bg-yellow': selectedPlan==plan }" ng-disabled="!ctrl.member">
<span translate>{{ 'app.public.plans.i_choose_that_plan' }}</span>
</button>
@ -67,9 +66,9 @@
</div>
<div class="col-xs-12 col-md-12 col-lg-10 col-centered no-gutter" ng-if="currentUser.subscription && isInPast(currentUser.subscription.expired_at)">
<div class="col-xs-12 col-md-12 col-lg-10 col-centered no-gutter" ng-if="ctrl.member.subscription && isInPast(ctrl.member.subscription.expired_at)">
<uib-alert type="info">
{{ 'app.public.plans.your_subscription_expires_on_the_DATE' | translate:{DATE:(currentUser.subscription.expired_at | amDateFormat:'L' )} }}
{{ 'app.public.plans.your_subscription_expires_on_the_DATE' | translate:{DATE:(ctrl.member.subscription.expired_at | amDateFormat:'L' )} }}
</uib-alert>
</div>
@ -81,14 +80,14 @@
<div class="col-sm-12 col-md-12 col-lg-3">
<div ng-if="currentUser.role === 'admin'">
<div ng-if="isAuthorized(['admin', 'manager'])">
<select-member subscription="false"></select-member>
</div>
<section class="widget panel b-a m m-t-lg" ng-show="ctrl.member">
<div class="panel-heading b-b">
<h3 ng-show="currentUser.role != 'admin'" translate>{{ 'app.public.plans.my_group' }}</h3>
<h3 ng-show="currentUser.role === 'admin'" translate translate-values="{GENDER:getGender(currentUser)}">{{ 'app.public.plans.his_group' }}</h3>
<h3 ng-show="isAuthorized('member')" translate>{{ 'app.public.plans.my_group' }}</h3>
<h3 ng-show="isAuthorized(['admin', 'manager'])" translate translate-values="{GENDER:getGender(ctrl.member)}">{{ 'app.public.plans.his_group' }}</h3>
</div>
<div class="widget-content no-bg auto wrapper">
<div ng-show="!group.change">
@ -99,14 +98,14 @@
ng-click="group.change = !group.change"
ng-show="(!selectedPlan && ctrl.member && !ctrl.member.subscribed_plan && ctrl.member.subscription) || (!paid.plan)"
translate
translate-values="{ROLE:currentUser.role}">{{ 'app.public.plans.he_wants_to_change_group' }}</button>
translate-values="{ROLE:ctrl.member.role}">{{ 'app.public.plans.he_wants_to_change_group' }}</button>
</div>
<div ng-show="group.change">
<select class="form-control" ng-options="g.id as g.name for g in groups" ng-model="group.id"></select>
<button class="btn btn-success m-t"
ng-click="selectGroup()"
translate
translate-values="{ROLE:currentUser.role, GENDER:getGender(currentUser)}">{{ 'app.public.plans.change_my_group' }}</button>
translate-values="{ROLE:ctrl.member.role, GENDER:getGender(ctrl.member)}">{{ 'app.public.plans.change_my_group' }}</button>
</div>
</div>
</section>

View File

@ -16,7 +16,7 @@
<div class="panel-body">
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(slot.start | amDateFormat:'LLLL'), END_TIME:(slot.end | amDateFormat:'LT') } }}</div>
<div class="text-base">{{ 'app.shared.cart.cost_of_TYPE' | translate:{TYPE:reservableType} }} <span ng-class="{'text-blue': !slot.promo, 'red': slot.promo}">{{slot.price | currency}}</span></div>
<div ng-show="isAdmin()" class="m-t">
<div ng-show="isAuthorized(['admin', 'manager'])" class="m-t">
<label for="offerSlot" class="control-label m-r" translate>{{ 'app.shared.cart.offer_this_slot' }}</label>
<input bs-switch
ng-model="slot.offered"

View File

@ -0,0 +1,157 @@
<div class="row m-t">
<div class="col-sm-offset-3 col-sm-6">
<div class="form-group" ng-class="{'has-error': managerForm['manager[statistic_profile_attributes][gender]'].$dirty && managerForm['manager[statistic_profile_attributes][gender]'].$invalid}">
<label class="checkbox-inline btn btn-default">
<input type="radio"
name="manager[statistic_profile_attributes][gender]"
ng-model="manager.statistic_profile_attributes.gender"
ng-value="true"
required/>
<i class="fa fa-male m-l-sm"></i> {{ 'app.admin.manager_new.man' | translate }}
</label>
<label class="checkbox-inline btn btn-default">
<input type="radio"
name="manager[statistic_profile_attributes][gender]"
ng-model="manager.statistic_profile_attributes.gender"
ng-value="false"/>
<i class="fa fa-female m-l-sm"></i> {{ 'app.admin.manager_new.woman' | translate }}
</label>
<span class="exponent m-l-xs help-cursor"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
</div>
<div class="form-group" ng-class="{'has-error': managerForm['manager[username]'].$dirty && managerForm['manager[username]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
<input ng-model="manager.username"
type="text" name="manager[username]"
class="form-control"
id="user_username"
placeholder="{{ 'app.admin.manager_new.pseudonym' | translate }}"
required>
</div>
<span class="help-block" ng-show="managerForm['manager[username]'].$dirty && managerForm['manager[username]'].$error.required" translate>{{ 'app.admin.manager_new.pseudonym_is_required' }}</span>
</div>
<div class="form-group" ng-class="{'has-error': managerForm['manager[profile_attributes][last_name]'].$dirty && managerForm['manager[profile_attributes][last_name]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
<input ng-model="manager.profile_attributes.last_name"
type="text"
name="manager[profile_attributes][last_name]"
class="form-control"
id="user_last_name"
placeholder="{{ 'app.admin.manager_new.surname' | translate }}"
required>
</div>
<span class="help-block" ng-show="managerForm['manager[profile_attributes][last_name]'].$dirty && managerForm['manager[profile_attributes][last_name]'].$error.required" translate>{{ 'app.admin.manager_new.surname_is_required' }}</span>
</div>
<div class="form-group" ng-class="{'has-error': managerForm['manager[profile_attributes][first_name]'].$dirty && managerForm['manager[profile_attributes][first_name]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
<input ng-model="manager.profile_attributes.first_name"
type="text"
name="manager[profile_attributes][first_name]"
class="form-control"
id="user_first_name"
placeholder="{{ 'app.admin.manager_new.first_name' | translate }}"
required>
</div>
<span class="help-block" ng-show="managerForm['manager[profile_attributes][first_name]'].$dirty && managerForm['manager[profile_attributes][first_name]'].$error.required" translate>{{ 'app.admin.manager_new.first_name_is_required' }}</span>
</div>
<div class="form-group" ng-class="{'has-error': managerForm['manager[email]'].$dirty && managerForm['manager[email]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-envelope"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
<input ng-model="manager.email"
type="email"
name="manager[email]"
class="form-control"
id="user_email"
placeholder="{{ 'app.admin.manager_new.email_address' | translate }}"
required>
</div>
<span class="help-block" ng-show="managerForm['manager[email]'].$dirty && managerForm['manager[email]'].$error.required" translate>{{ 'app.admin.manager_new.email_is_required' }}</span>
</div>
<div class="form-group" ng-class="{'has-error': managerForm['manager[statistic_profile_attributes][birthday]'].$dirty && managerForm['manager[statistic_profile_attributes][birthday]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-calendar-o"></i> </span>
<input type="text"
id="user_birthday"
class="form-control"
ng-model="manager.statistic_profile_attributes.birthday"
uib-datepicker-popup="{{datePicker.format}}"
datepicker-options="datePicker.options"
is-open="datePicker.opened"
placeholder="{{ 'app.admin.manager_new.birth_date' | translate }}"
ng-click="openDatePicker($event)"
/>
<input type="hidden"
name="manager[statistic_profile_attributes][birthday]"
value="{{manager.statistic_profile_attributes.birthday | toIsoDate}}" />
</div>
</div>
<div class="form-group">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-map-marker"></i> </span>
<input type="hidden"
name="manager[invoicing_profile_attributes][address_attributes][id]"
ng-value="manager.invoicing_profile_attributes.address.id" />
<input ng-model="manager.invoicing_profile_attributes.address_attributes.address"
type="text"
name="manager[invoicing_profile_attributes][address_attributes][address]"
class="form-control"
id="user_address"
placeholder="{{ 'app.admin.manager_new.address' | translate }}">
</div>
</div>
<div class="form-group" ng-class="{'has-error': managerForm['manager[profile_attributes][phone]'].$dirty && managerForm['manager[profile_attributes][phone]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-phone"></i> </span>
<input ng-model="manager.profile_attributes.phone"
type="text"
name="manager[profile_attributes][phone]"
class="form-control" id="user_phone"
placeholder="{{ 'app.admin.manager_new.phone_number' | translate }}">
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group" ng-class="{'has-error': managerForm['manager[group_id]'].$dirty && managerForm['manager[group_id]'].$invalid}">
<label for="manager_group_id" class="col-sm-3 control-label">
<span translate>{{ 'app.shared.user_admin.group' }}</span>
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
</label>
<div class="col-sm-6">
<select ng-model="manager.group_id" class="form-control" name="manager[group_id]" id="manager_group_id"
ng-options="g.id as g.name for g in groups" required>
</select>
<input type="hidden" name="manager[group_id]" ng-value="manager.group_id" />
<span class="help-block" ng-show="managerForm['manager[group_id]'].$dirty && managerForm['manager[group_id]'].$error.required" translate>{{ 'app.shared.user_admin.group_is_required' }}</span>
</div>
</div>
</div>
<div>
<div class="form-group">
<label class="col-sm-3 control-label" translate>{{ 'app.shared.user_admin.tags' }}</label>
<div class="col-sm-6 nopadding">
<input type="hidden" name="manager[tag_ids][]" value="" />
<ui-select multiple ng-model="manager.tag_ids" name="user[tag_ids][]" class="form-control">
<ui-select-match>
<span ng-bind="$item.name"></span>
<input type="hidden" name="manager[tag_ids][]" value="{{$item.id}}" />
</ui-select-match>
<ui-select-choices repeat="t.id as t in (tags | filter: $select.search)">
<span ng-bind-html="t.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>
</div>

View File

@ -3,16 +3,16 @@
<h1 translate>{{ 'app.shared.cart.slot_at_same_time' }}</h1>
</div>
<div class="modal-body">
<p ng-if="bookSlotAtSameTime || currentUser.role === 'admin'" translate>{{ 'app.shared.cart.do_you_really_want_to_book_slot_at_same_time' }}</p>
<p ng-if="!bookSlotAtSameTime && currentUser.role !== 'admin'" translate>{{ 'app.shared.cart.unable_to_book_slot_because_really_have_reservation_at_same_time' }}</p>
<p ng-if="bookSlotAtSameTime || isAuthorized(['admin', 'manager'])" translate>{{ 'app.shared.cart.do_you_really_want_to_book_slot_at_same_time' }}</p>
<p ng-if="!bookSlotAtSameTime && !isAuthorized(['admin', 'manager'])" translate>{{ 'app.shared.cart.unable_to_book_slot_because_really_have_reservation_at_same_time' }}</p>
<ul>
<li ng-repeat="r in sameTimeReservations">
<span>{{::r.reservable.name}}{{::r.reservable.title}}</span>
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(r.start_at | amDateFormat:'LLLL'), END_TIME:(r.end_at | amDateFormat:'LT') } }}</div>
</li>
</div>
</ul>
</div>
<div class="modal-footer">
<button ng-if="bookSlotAtSameTime || currentUser.role === 'admin'" class="btn btn-info" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button ng-if="bookSlotAtSameTime || isAuthorized(['admin', 'manager'])" class="btn btn-info" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>

View File

@ -3,8 +3,8 @@
<h1 translate>{{ 'app.shared.confirm_modify_slot_modal.change_the_slot' }}</h1>
</div>
<div class="modal-body">
<p ng-show="currentUser.role != 'admin'" translate>{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_your_booking_slot_initially_planned_at' }} </p>
<p ng-show="currentUser.role == 'admin'" translate translate-values="{NAME: object.user.name}">{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_NAME_s_booking_slot_initially_planned_at' }}</p>
<p ng-show="currentUser.id === object.user.id" translate>{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_your_booking_slot_initially_planned_at' }} </p>
<p ng-show="currentUser.id !== object.user.id" translate translate-values="{NAME: object.user.name}">{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_NAME_s_booking_slot_initially_planned_at' }}</p>
<p><strong>{{object.start | amDateFormat: 'LL'}} : {{object.start | amDateFormat:'LT'}} - {{object.end | amDateFormat:'LT'}}</strong></p>
</div>
<div class="modal-footer">

View File

@ -40,8 +40,8 @@
<li><a ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
<li><a ui-sref="app.logged.dashboard.invoices" ng-hide="fablabWithoutInvoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li ng-hide="fablabWithoutWallet"><a ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
<li class="divider" ng-if="currentUser.role === 'admin'"></li>
<li><a class="text-black pointer" ng-click="help($event)" ng-if="currentUser.role === 'admin'"><i class="fa fa-question-circle"></i> <span translate>{{ 'app.public.common.help' }}</span> </a></li>
<li class="divider" ng-if="isAuthorized(['admin', 'manager'])"></li>
<li><a class="text-black pointer" ng-click="help($event)" ng-if="isAuthorized(['admin', 'manager'])"><i class="fa fa-question-circle"></i> <span translate>{{ 'app.public.common.help' }}</span> </a></li>
<li class="divider"></li>
<li><a class="text-black pointer" ng-click="logout($event)"><i class="fa fa-power-off"></i> {{ 'app.public.common.sign_out' | translate }}</a></li>
</ul>

View File

@ -71,10 +71,11 @@
</ul>
<!-- Admin entries -->
<div class="line-s bg-red-dark dk " ng-if="isAuthorized('admin')"></div>
<div class="line-s bg-red-dark dk " ng-if="isAuthorized(['admin', 'manager'])"></div>
<div class="text-xs font-bold text-bordeau hidden-nav-xs padder m-t-lg m-b-sm admin-section" ng-if="isAuthorized('admin')" translate>{{ 'app.public.common.admin' }}</div>
<ul class="nav" ng-if="isAuthorized('admin')">
<li class="{{navLink.class}}" ng-repeat="navLink in adminNavLinks">
<div class="text-xs font-bold text-bordeau hidden-nav-xs padder m-t-lg m-b-sm admin-section" ng-if="isAuthorized('manager')" translate>{{ 'app.public.common.manager' }}</div>
<ul class="nav" ng-if="isAuthorized(['admin', 'manager'])">
<li class="{{navLink.class}}" ng-repeat="navLink in adminNavLinks" ng-if="!navLink.authorizedRoles || isAuthorized(navLink.authorizedRoles)">
<a ng-click="toggleNavSize($event)" ga ui-sref="{{navLink.state}}" ui-sref-active="active" class="auto" data-toggle="class:nav-off-screen" data-target="#nav" ng-if="navLink.state">
<i class="fa fa-{{navLink.linkIcon}} fa-lg"></i>
<span>{{navLink.linkText | translate}}</span>

View File

@ -37,7 +37,7 @@
</select>
</div>
</div>
<div class="col-md-3 col-md-offset-6 m-t-n row-centered" ng-if="isAuthorized('admin')">
<div class="col-md-3 col-md-offset-6 m-t-n row-centered" ng-if="isAuthorized(['admin', 'manager'])">
<a role="button" ui-sref="app.admin.calendar" class="btn btn-lg btn-default rounded m-t-sm text-sm">
<i class="fa fa-calendar-check-o m-r" aria-hidden="true"></i><span translate>{{ 'app.public.spaces_list.new_availability' }}</span>
</a>
@ -60,7 +60,7 @@
<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)" ng-hide="space.disabled">
<div class="btn btn-default btn-block no-b padder-v red reserve-button" ng-click="reserveSpace(space, $event)" ng-hide="space.disabled">
<i class="fa fa-bookmark m-r-xs"></i>
<span class="hidden-sm" translate>{{ 'app.public.spaces_list.book' }}</span>
</div>

View File

@ -23,7 +23,7 @@
<div class="col-sm-12 col-md-12 col-lg-3">
<div ng-if="currentUser.role === 'admin'">
<div ng-if="isAuthorized(['admin', 'manager'])">
<select-member></select-member>
</div>

View File

@ -80,7 +80,7 @@ class API::AvailabilitiesController < API::ApiController
end
def machine
@current_user_role = current_user.admin? ? 'admin' : 'user'
@current_user_role = current_user.role
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@slots = service.machines(params[:machine_id], user)
@ -92,7 +92,7 @@ class API::AvailabilitiesController < API::ApiController
end
def spaces
@current_user_role = current_user.admin? ? 'admin' : 'user'
@current_user_role = current_user.role
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@slots = service.spaces(params[:space_id], user)

View File

@ -38,7 +38,7 @@ class API::MembersController < API::ApiController
end
def create
authorize User
authorize :user, :create_member?
@member = User.new(user_params.permit!)
members_service = Members::MembersService.new(@member)
@ -222,7 +222,7 @@ class API::MembersController < API::ApiController
],
statistic_profile_attributes: %i[id gender birthday])
elsif current_user.admin?
elsif current_user.admin? || current_user.manager?
params.require(:user).permit(:username, :email, :password, :password_confirmation, :is_allow_contact, :is_allow_newsletter, :group_id,
tag_ids: [],
profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job,

View File

@ -9,13 +9,13 @@ class API::ReservationsController < API::ApiController
def index
if params[:reservable_id] && params[:reservable_type] && params[:user_id]
params[:user_id] = current_user.id unless current_user.admin?
params[:user_id] = current_user.id unless current_user.admin? || current_user.manager?
where_clause = params.permit(:reservable_id, :reservable_type).to_h
where_clause[:statistic_profile_id] = StatisticProfile.find_by!(user_id: params[:user_id])
@reservations = Reservation.where(where_clause)
elsif params[:reservable_id] && params[:reservable_type] && current_user.admin?
elsif params[:reservable_id] && params[:reservable_type] && (current_user.admin? || current_user.manager?)
@reservations = Reservation.where(params.permit(:reservable_id, :reservable_type))
else
@reservations = []
@ -25,12 +25,13 @@ class API::ReservationsController < API::ApiController
def show; end
# Admins can create any reservations. Members can directly create reservations if total = 0,
# otherwise, they must use payments_controller#confirm_payment
# otherwise, they must use payments_controller#confirm_payment.
# Managers can create reservations for other users
def create
user_id = current_user.admin? ? params[:reservation][:user_id] : current_user.id
user_id = current_user.admin? || current_user.manager? ? params[:reservation][:user_id] : current_user.id
amount = transaction_amount(current_user.admin?, user_id)
authorize ReservationContext.new(Reservation, amount)
authorize ReservationContext.new(Reservation, amount, user_id)
@reservation = Reservation.new(reservation_params)
is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id)

View File

@ -10,12 +10,13 @@ class API::SubscriptionsController < API::ApiController
end
# Admins can create any subscriptions. Members can directly create subscriptions if total = 0,
# otherwise, they must use payments_controller#confirm_payment
# otherwise, they must use payments_controller#confirm_payment.
# Managers can create subscriptions for other users
def create
user_id = current_user.admin? ? params[:subscription][:user_id] : current_user.id
user_id = current_user.admin? || current_user.manager? ? params[:subscription][:user_id] : current_user.id
amount = transaction_amount(current_user.admin?, user_id)
authorize SubscriptionContext.new(Subscription, amount)
authorize SubscriptionContext.new(Subscription, amount, user_id)
@subscription = Subscription.new(subscription_params)
is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id)

View File

@ -41,7 +41,8 @@ class API::TrainingsController < API::ApiController
end
head :no_content
elsif @training.update(training_params)
elsif current_user.admin? && @training.update(training_params)
# only admins can fully update a training, not managers
render :show, status: :ok, location: @training
else
render json: @training.errors, status: :unprocessable_entity

View File

@ -6,7 +6,9 @@ class API::UsersController < API::ApiController
before_action :set_user, only: %i[destroy]
def index
if current_user.admin? && %w[partner manager].include?(params[:role])
authorize User
if %w[partner manager].include?(params[:role])
@users = User.with_role(params[:role].to_sym).includes(:profile)
else
head 403
@ -15,12 +17,18 @@ class API::UsersController < API::ApiController
def create
authorize User
res = UserService.create_partner(partner_params)
res = if !params[:user].empty?
UserService.create_partner(partner_params)
elsif !params[:manager].empty?
UserService.create_manager(manager_params)
else
nil
end
if res[:saved]
@user = res[:user]
render status: :created
else²
else
render json: res[:user].errors.full_messages, status: :unprocessable_entity
end
end
@ -40,4 +48,14 @@ class API::UsersController < API::ApiController
def partner_params
params.require(:user).permit(:email, :first_name, :last_name)
end
def manager_params
params.require(:manager).permit(
:username, :email, :group_id,
tag_ids: [],
profile_attributes: %i[first_name last_name phone],
invoicing_profile_attributes: [address_attributes: [:address]],
statistic_profile_attributes: %i[gender birthday]
)
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# Devise controller to handle validation of email addresses
class ConfirmationsController < Devise::ConfirmationsController
# The path used after confirmation.
def after_confirmation_path_for(resource_name, resource)

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# Devise controller used for the "forgotten password" feature
class PasswordsController < Devise::PasswordsController
# POST /resource/password
# POST /users/password.json
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
yield resource if block_given?

View File

@ -1,17 +1,20 @@
# frozen_string_literal: true
# Handle requests originated by indexer bots of social networks
class SocialBotController < ActionController::Base
def share
case request.original_fullpath
when /(=%2F|\/)projects(%2F|\/)([\-0-9a-z_]+)/
@project = Project.friendly.find("#{$3}")
render :project, status: :ok
when /(=%2F|\/)events(%2F|\/)([0-9]+)/
@event = Event.find("#{$3}".to_i)
render :event, status: :ok
when /(=%2F|\/)trainings(%2F|\/)([\-0-9a-z_]+)/
@training = Training.friendly.find("#{$3}")
when %r{(=%2F|/)projects(%2F|/)([\-0-9a-z_]+)}
@project = Project.friendly.find(Regexp.last_match(3).to_s)
render :project, status: :ok
when %r{(=%2F|/)events(%2F|/)([0-9]+)}
@event = Event.find(Regexp.last_match(3).to_s.to_i)
render :event, status: :ok
when %r{(=%2F|/)trainings(%2F|/)([\-0-9a-z_]+)}
@training = Training.friendly.find(Regexp.last_match(3).to_s)
render :training, status: :ok
else
puts "unknown bot request : #{request.original_url}"
else
puts "unknown bot request : #{request.original_url}"
end
end

View File

@ -160,6 +160,8 @@ class Availability < ApplicationRecord
private
def length_must_be_slot_multiple
return unless available_type == 'machines' || available_type == 'space'
duration = slot_duration || ApplicationHelper::SLOT_DURATION
return unless end_at < (start_at + duration.minutes)

View File

@ -23,7 +23,7 @@ class Avoir < Invoice
def notify_admins_refund_created
NotificationCenter.call type: 'notify_admin_refund_created',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self
end
end

View File

@ -15,7 +15,7 @@ class EventPriceCategory < ApplicationRecord
protected
def verify_no_associated_tickets
throw(:abort) if tickets.count.zero?
throw(:abort) unless tickets.count.zero?
end
end

View File

@ -144,7 +144,7 @@ class Project < ApplicationRecord
def notify_admin_when_project_published
NotificationCenter.call type: 'notify_admin_when_project_published',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self
end

View File

@ -306,7 +306,7 @@ class Reservation < ApplicationRecord
def notify_admin_member_create_reservation
NotificationCenter.call type: 'notify_admin_member_create_reservation',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self
end

View File

@ -40,7 +40,7 @@ class Slot < ApplicationRecord
receiver: reservation.user,
attached_object: self
NotificationCenter.call type: 'notify_admin_slot_is_modified',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self
end
@ -49,7 +49,7 @@ class Slot < ApplicationRecord
receiver: reservation.user,
attached_object: self
NotificationCenter.call type: 'notify_admin_slot_is_canceled',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self
end

View File

@ -148,7 +148,7 @@ class Subscription < ApplicationRecord
def notify_admin_subscription_canceled
NotificationCenter.call type: 'notify_admin_subscription_canceled',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self
end
@ -173,7 +173,7 @@ class Subscription < ApplicationRecord
meta_data: meta_data
NotificationCenter.call type: :notify_admin_subscription_extended,
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self,
meta_data: meta_data
end

View File

@ -97,6 +97,22 @@ class User < ApplicationRecord
User.with_role(:member)
end
def self.partners
User.with_role(:partner)
end
def self.managers
User.with_role(:manager)
end
def self.admins_and_managers
User.with_any_role(:admin, :manager)
end
def self.online_payers
User.with_any_role(:manager, :member)
end
def self.superadmin
return unless Rails.application.secrets.superadmin_email.present?
@ -104,7 +120,7 @@ class User < ApplicationRecord
end
def training_machine?(machine)
return true if admin?
return true if admin? || manager?
trainings.map(&:machines).flatten.uniq.include?(machine)
end
@ -131,6 +147,24 @@ class User < ApplicationRecord
has_role? :member
end
def manager?
has_role? :manager
end
def partner?
has_role? :partner
end
def role
if admin?
'admin'
elsif manager?
'manager'
else
'other'
end
end
def all_projects
my_projects.to_a.concat projects
end
@ -285,7 +319,7 @@ class User < ApplicationRecord
protected
# remove projets drafts that are not linked to another user
# remove projects drafts that are not linked to another user
def remove_orphan_drafts
orphans = my_projects
.joins('LEFT JOIN project_users ON projects.id = project_users.project_id')
@ -332,19 +366,19 @@ class User < ApplicationRecord
attached_object: self
else
NotificationCenter.call type: 'notify_admin_when_user_is_created',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self
end
end
def notify_group_changed
return if changes[:group_id].first.nil?
return unless changes[:group_id]&.first
ex_group = Group.find(changes[:group_id].first)
meta_data = { ex_group_name: ex_group.name }
NotificationCenter.call type: :notify_admin_user_group_changed,
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: self,
meta_data: meta_data

View File

@ -2,9 +2,13 @@
# Check the access policies for API::AccountingPeriodsController
class AccountingPeriodPolicy < ApplicationPolicy
%w[index show create last_closing_end download_archive].each do |action|
%w[index show create download_archive].each do |action|
define_method "#{action}?" do
user.admin?
end
end
def last_closing_end?
user.admin? || user.manager?
end
end

View File

@ -1,6 +1,6 @@
class AdminPolicy < ApplicationPolicy
def index?
user.admin?
user.admin? || user.manager?
end
def create?

View File

@ -1,7 +1,14 @@
# frozen_string_literal: true
# Check the access policies for API::AvailabilitiesController
class AvailabilityPolicy < ApplicationPolicy
%w(index? show? create? update? destroy? reservations? export? lock?).each do |action|
%w[index? show? create? update? destroy? reservations? lock?].each do |action|
define_method action do
user.admin?
user.admin? || user.manager?
end
end
def export?
user.admin?
end
end

View File

@ -18,7 +18,7 @@ class EventPolicy < ApplicationPolicy
end
def create?
user.admin?
user.admin? || user.manager?
end
def update?

View File

@ -3,10 +3,10 @@
# Check the access policies for API::ICalendarController
class ICalendarPolicy < ApplicationPolicy
def create?
user.admin?
user.admin? || user.manager?
end
def destroy?
user.admin?
user.admin? || user.manager?
end
end

View File

@ -1,18 +1,21 @@
# frozen_string_literal: true
# Check the access policies for API::InvoicesController
class InvoicePolicy < ApplicationPolicy
def index?
user.admin?
end
def download?
user.admin? or (record.invoicing_profile.user_id == user.id)
user.admin? || user.manager? || (record.invoicing_profile.user_id == user.id)
end
def create?
user.admin?
user.admin? || user.manager?
end
def list?
user.admin?
user.admin? || user.manager?
end
def first?

View File

@ -2,11 +2,12 @@
# Pundit Additional context to validate the price of a reservation
class ReservationContext
attr_reader :reservation, :price
attr_reader :reservation, :price, :user_id
def initialize(reservation, price)
def initialize(reservation, price, user_id)
@reservation = reservation
@price = price
@user_id = user_id
end
def policy_class

View File

@ -3,10 +3,10 @@
# Check the access policies for API::ReservationsController
class ReservationPolicy < ApplicationPolicy
def create?
user.admin? || record.price.zero?
user.admin? || (user.manager? && record.user_id != user.id) || record.price.zero?
end
def update?
user.admin? || record.user == user
user.admin? || user.manager? || record.user == user
end
end

View File

@ -1,15 +1,18 @@
# frozen_string_literal: true
# Check the access policies for API::SlotsController
class SlotPolicy < ApplicationPolicy
def update?
# check that the update is allowed and the prevention delay has not expired
delay = Setting.find_by( name: 'booking_move_delay').value.to_i
enabled = (Setting.find_by( name: 'booking_move_enable').value == 'true')
delay = Setting.find_by(name: 'booking_move_delay').value.to_i
enabled = (Setting.find_by(name: 'booking_move_enable').value == 'true')
# these condition does not apply to admins
user.admin? or
(record.reservation.user == user and enabled and ((record.start_at - DateTime.current).to_i / 3600 >= delay))
user.admin? || user.manager? ||
(record.reservation.user == user && enabled && ((record.start_at - DateTime.current).to_i / 3600 >= delay))
end
def cancel?
user.admin? or record.reservation.user == user
user.admin? || user.manager? || record.reservation.user == user
end
end

View File

@ -2,11 +2,12 @@
# Pundit Additional context to validate the price of a subscription
class SubscriptionContext
attr_reader :subscription, :price
attr_reader :subscription, :price, :user_id
def initialize(subscription, price)
def initialize(subscription, price, user_id)
@subscription = subscription
@price = price
@user_id = user_id
end
def policy_class

View File

@ -4,7 +4,7 @@
class SubscriptionPolicy < ApplicationPolicy
include FablabConfiguration
def create?
!fablab_plans_deactivated? && (user.admin? || record.price.zero?)
!fablab_plans_deactivated? && (user.admin? || (user.manager? && record.user_id != user.id) || record.price.zero?)
end
def show?
@ -12,6 +12,6 @@ class SubscriptionPolicy < ApplicationPolicy
end
def update?
user.admin?
user.admin? || (user.manager? && record.user.id != user.id)
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# Check the access policies for API::TrainingsController
class TrainingPolicy < ApplicationPolicy
class Scope < Scope
def resolve
@ -5,17 +8,19 @@ class TrainingPolicy < ApplicationPolicy
end
end
%w(create update).each do |action|
define_method "#{action}?" do
user.admin?
end
def create
user.admin?
end
def update?
user.admin? || user.manager?
end
def destroy?
user.admin? and record.destroyable?
user.admin? && record.destroyable?
end
def availabilities?
user.admin?
user.admin? || user.manager?
end
end

View File

@ -16,26 +16,30 @@ class UserPolicy < ApplicationPolicy
end
def show?
user.admin? || (record.is_allow_contact && record.member?) || (user.id == record.id)
user.admin? || user.manager? || (record.is_allow_contact && record.member?) || (user.id == record.id)
end
def update?
user.admin? || (user.id == record.id)
user.admin? || user.manager? || (user.id == record.id)
end
def destroy?
user.admin? || (user.id == record.id)
end
def merge?
user.id == record.id
%w[merge complete_tour].each do |action|
define_method "#{action}?" do
user.id == record.id
end
end
def complete_tour?
user.id == record.id
%w[list index create_member].each do |action|
define_method "#{action}?" do
user.admin? || user.manager?
end
end
%w[list create mapping].each do |action|
%w[create mapping].each do |action|
define_method "#{action}?" do
user.admin?
end

View File

@ -1,13 +1,14 @@
class WalletPolicy < ApplicationPolicy
def by_user?
user.admin? or user == record.user
end
# frozen_string_literal: true
def transactions?
user.admin? or user == record.user
# Check the access policies for API::WalletController
class WalletPolicy < ApplicationPolicy
%w[by_user transactions].each do |action|
define_method "#{action}?" do
user.admin? || user.manager? || user == record.user
end
end
def credit?
user.admin?
user.admin? || (user.manager? && user != record.user)
end
end

View File

@ -50,7 +50,7 @@ class Members::ListService
'SELECT max("created_at") ' \
'FROM "subscriptions" ' \
'WHERE "statistic_profile_id" = "statistic_profiles"."id")')
.where("users.is_active = 'true' AND roles.name = 'member'")
.where("users.is_active = 'true' AND (roles.name = 'member' OR roles.name = 'manager')")
.limit(50)
query.downcase.split(' ').each do |word|
members = members.where('lower(f_unaccent(profiles.first_name)) ~ :search OR ' \

View File

@ -50,4 +50,19 @@ class UserService
end
{ saved: saved, user: admin }
end
def self.create_manager(params)
generated_password = Devise.friendly_token.first(8)
manager = User.new(params.merge(password: generated_password))
manager.send :set_slug
saved = manager.save
if saved
manager.send_confirmation_instructions
manager.add_role(:manager)
manager.remove_role(:member)
UsersMailer.delay.notify_user_account_created(manager, generated_password)
end
{ saved: saved, user: manager }
end
end

View File

@ -22,7 +22,7 @@ class WalletService
receiver: @wallet.user,
attached_object: transaction
NotificationCenter.call type: 'notify_admin_user_wallet_is_credited',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: transaction
return transaction
end

View File

@ -15,13 +15,13 @@ json.array!(@slots) do |slot|
json.id slot.machine.id
json.name slot.machine.name
end
# the user who booked the slot ...
if (@current_user_role == 'admin') && slot.reservation
# the user who booked the slot, if the slot was reserved
if (%w[admin manager].include? @current_user_role) && slot.reservation
json.user do
json.id slot.reservation.user.id
json.name slot.reservation.user.profile.full_name
end
end # ... if the slot was reserved
end
json.tag_ids slot.availability.tag_ids
json.tags slot.availability.tags do |t|
json.id t.id

View File

@ -16,13 +16,13 @@ json.array!(@slots) do |slot|
json.id slot.space.id
json.name slot.space.name
end
# the user who booked the slot ...
if (@current_user_role == 'admin') && slot.reservation
# the user who booked the slot, if the slot was reserved
if (%w[admin manager].include? @current_user_role) && slot.reservation
json.user do
json.id slot.reservation.user.id
json.name slot.reservation.user.profile.full_name
end
end # ... if the slot was reserved
end
json.tag_ids slot.availability.tag_ids
json.tags slot.availability.tags do |t|
json.id t.id

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
json.extract! @machine, :id, :name, :description, :spec, :disabled, :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|
@ -7,9 +9,11 @@ json.machine_files_attributes @machine.machine_files do |f|
end
json.trainings @machine.trainings.each, :id, :name, :disabled
json.current_user_is_training current_user.training_machine?(@machine) if current_user
json.current_user_training_reservation do
json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(@machine)
end if current_user and !current_user.training_machine?(@machine) and current_user.training_reservation_by_machine(@machine)
if current_user && !current_user.training_machine?(@machine) && current_user.training_reservation_by_machine(@machine)
json.current_user_training_reservation do
json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(@machine)
end
end
json.machine_projects @machine.projects.published.last(10) do |p|
json.id p.id

View File

@ -2,6 +2,7 @@
json.users @users do |user|
json.extract! user, :id, :email, :first_name, :last_name
json.phone user.profile.phone
json.name user.profile.full_name
json.resource user.roles.last.resource
end

View File

@ -9,14 +9,14 @@ class SubscriptionExpireWorker
receiver: s.user,
attached_object: s
NotificationCenter.call type: 'notify_admin_subscription_will_expire_in_7_days',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: s
else
NotificationCenter.call type: 'notify_member_subscription_is_expired',
receiver: s.user,
attached_object: s
NotificationCenter.call type: 'notify_admin_subscription_is_expired',
receiver: User.admins,
receiver: User.admins_and_managers,
attached_object: s
end
end

View File

@ -19,7 +19,7 @@ en:
availabilities: "Availabilities"
availabilities_notice: "Export to an Excel workbook every slots available for reservation, and their occupancy rate."
ongoing_reservations: "Ongoing reservations"
no_reservations: "No reservations"
without_reservation: "Without reservation"
confirmation_required: "Confirmation required"
do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Do you really {GENDER, select, other {want}} to cancel the {USER}'s reservation, the {DATE} at {TIME}, concerning {RESERVATION}?"
reservation_was_successfully_cancelled: "Reservation was successfully cancelled."
@ -193,7 +193,7 @@ en:
booking: "Booking"
sold_out: "Sold out"
cancelled: "Cancelled"
free_entry: "Free entry"
without_reservation: "Without reservation"
free_admission: "Free admission"
view_reservations: "View reservations"
load_the_next_events: "Load the next events..."
@ -604,15 +604,21 @@ en:
administrators: "Administrators"
search_for_an_administrator: "Search for an administrator"
add_a_new_administrator: "Add a new administrator"
managers: "Managers"
managers_info: "A manager is a restricted administrator that cannot modify the settings of the application. However, he will be able to take reservations for any members and for all managers, including himself, and to process payments and refunds."
search_for_a_manager: "Search for a manager"
add_a_new_manager: "Add a new manager"
delete_this_manager: "Do you really want to delete this manager? This cannot be undone."
manager_successfully_deleted: "Manager successfully deleted."
unable_to_delete_the_manager: "Unable to delete the manager."
partners: "Partners"
partners_info: "A partner is a special user that can be associated with the «Partner» plans. These users will only receive notifications about subscriptions to these plans."
partners_info: "A partner is a special user that can be associated with the «Partner» plans. These users won't be able to connect and will just receive notifications about subscriptions to their associated plan."
search_for_a_partner: "Search for a partner"
add_a_new_partner: "Add a new partner"
delete_this_partner: "Do you really want to delete this partner? This cannot be undone."
partner_successfully_deleted: "Partner successfully deleted."
unable_to_delete_the_partner: "Unable to delete the partner."
associated_plan: "Associated plan"
managers: "Managers"
groups: "Groups"
tags: "Tags"
authentication: "Authentication"
@ -757,6 +763,8 @@ en:
a_problem_occurred_while_taking_the_subscription: "A problem occurred while taking the subscription"
wallet: "Wallet"
to_credit: 'Credit'
cannot_credit_own_wallet: "You cannot credit your own wallet. Please ask another manager or an administrator to credit your wallet."
cannot_extend_own_subscription: "You cannot extend your own subscription. Please ask another manager or an administrator to extend your subscription."
#add a new administrator to the platform
admins_new:
add_an_administrator: "Add an administrator"
@ -775,6 +783,24 @@ en:
birth_date: "Date of birth"
address: "Address"
phone_number: "Phone number"
#add a new manager to the platform
manager_new:
add_a_manager: "Add a manager"
manager_successfully_created: "Manager successfully created. {GENDER, select, female{She} other{He}} receive {GENDER, select, female{her} other{his}} connection directives by e-mail."
failed_to_create_manager: "Unable to create the manager:"
man: "Man"
woman: "Woman"
pseudonym: "Pseudonym"
pseudonym_is_required: "Pseudonym is required."
first_name: "First name"
first_name_is_required: "First name is required."
surname: "Last name"
surname_is_required: "Last name is required."
email_address: "Email address"
email_is_required: "Email address is required."
birth_date: "Date of birth"
address: "Address"
phone_number: "Phone number"
#add a new authentication provider (SSO)
authentication_new:
local_database: "Local Database"
@ -987,6 +1013,7 @@ en:
advanced: "Advanced settings"
customize_home_page_css: "Customise the stylesheet og the home page"
home_css_notice_html: "You can customize the stylesheet which will apply to the home page, using the <a href=\"https://sass-lang.com/documentation\" target=\"_blank\">SASS</a> syntax. These styles will be automatically subordinated to the <code>.home-page</code> selector to prevent any risk of breaking the application. Meanwhile please be careful, any changes in the home page editor at the top of the page may broke your styles, always refer to the HTML code."
an_error_occurred_saving_the_setting: "An error occurred while saving the setting. Please try again later."
sort_by:
default: "Default"
name: "Name"
@ -1064,9 +1091,12 @@ en:
welcome:
title: "Trainings"
content: "Here you can create, modify and delete trainings. It is also the place where you can validate the training courses followed by your members."
welcome_manager:
title: "Trainings"
content: "This is the place where you can view the trainings and their associations with the machines. It is also the place where you can validate the training courses followed by your members."
trainings:
title: "Manage trainings"
content: "<p>When creating a training, you can define a default number of places. However, the number of actual places may be modified for each session.</p><p>The training sessions are scheduled from the administrator tab « Calendar ».</p><p>Another thing: it is possible to associate one or more machines with a training. This makes it a prerequisite for the reservation of these machines.</p>"
content: "<p>With each training, a default number of places is associated. However, the number of actual places may be modified for each session.</p><p>The training sessions are scheduled from the administrator tab « Calendar ».</p><p>Furthermore, a training may be associated with one or more machines. This makes it a prerequisite for the reservation of these machines.</p>"
filter:
title: "Filter"
content: "By default, only active courses are displayed here. Display the others by choosing another filter here."
@ -1124,6 +1154,9 @@ en:
welcome:
title: "Invoices"
content: "<p>Here you will be able to download invoices and credit notes issued, as well as manage everything related to accounting and invoicing.</p><p>If you use third-party software to manage your invoices, it is possible to deactivate the billing module. For this, contact your system administrator.</p>"
welcome_manager:
title: "Invoices"
content: "Here you will be able to download invoices and create credit notes."
list:
title: "Invoices list"
content: "By default, this table lists all the invoices and credit notes issued by Fab-manager. You can sort the list in a different order by clicking on the header of each column."

View File

@ -193,8 +193,8 @@ pt:
booking: "Reserva"
sold_out: "Esgotado"
cancelled: "Cancelado"
without_reservation: "Sem reservas"
free_admission: "Admissão gratuita"
no_reservations: "Sem reservas"
free_admission: "Entrada gratuita"
view_reservations: "Ver reservas"
load_the_next_events: "Load the next events..."
categories: "Categorias"

View File

@ -29,6 +29,7 @@ en:
#left menu
notifications: "Notifications"
admin: "Admin"
manager: "Manager"
reduce_panel: "Reduce panel"
#left menu (public)
home: "Home"
@ -146,7 +147,7 @@ en:
from_date_to_date: "From {START} to {END}"
on_the_date: "On the {DATE}"
from_time_to_time: "From {START} to {END}"
free_entry: "Free entry"
without_reservation: "Without reservation"
free_admission: "Free admission"
full_price: "Full price: "
event_full: "Event full"
@ -241,8 +242,8 @@ en:
your_subscription_expires_on_the_DATE: "Your subscription expires on the {DATE}"
my_group: "My group"
his_group: "{GENDER, select, male{His} female{Her} other{Its}} group"
he_wants_to_change_group: "{ROLE, select, admin{The user wants} other{I want}} to change group"
change_my_group: "Change {ROLE, select, admin{{GENDER, select, male{his} female{her} other{its}}} other{my}} group"
he_wants_to_change_group: "{ROLE, select, member{I want} other{The user wants}} to change group"
change_my_group: "Change {ROLE, select, member{my} other{{GENDER, select, male{his} female{her} other{its}}}} group"
summary: "Summary"
your_subscription_has_expired_on_the_DATE: "Your subscription has expired on the {DATE}"
subscription_price: "Subscription price"
@ -269,7 +270,7 @@ en:
cancelled: "Cancelled"
free_admission: "Free admission"
still_available: "available place(s)"
free_entry: "Free entry"
without_reservation: "Without reservation"
add_an_event: "Add an event"
load_the_next_events: "Load the next events..."
full_price_: "Full price:"
@ -290,7 +291,7 @@ en:
full_price_: "Full price:"
tickets_still_availables: "Tickets still available:"
sold_out: "Sold out."
free_entry: "Free entry"
without_reservation: "Without reservation"
cancelled: "Cancelled"
ticket: "{NUMBER, plural, one{ticket} other{tickets}}"
make_a_gift_of_this_reservation: "Make a gift of this reservation"
@ -387,8 +388,8 @@ en:
title: "Subscriptions"
content: "Subscriptions provide a way to segment your prices and provide benefits to regular users."
admin:
title: "Administrator section"
content: "<p>All of the elements below are only accessible to administrators. They allow you to manage and configure Fab-manager.</p><p>At the end of this visit, click on one of them to find out more.</p>"
title: "{ROLE} section"
content: "<p>All of the elements below are only accessible to administrators and managers. They allow you to manage and configure Fab-manager.</p><p>At the end of this visit, click on one of them to find out more.</p>"
about:
title: "About"
content: "A page that you can fully customize, to present your activity and your structure."
@ -423,13 +424,25 @@ en:
welcome:
title: "Machines"
content: "<p>Machines are the tools available for your users. You must create here the machines which can then be reserved by the members.</p><p>You can also create entries for non-bookable or free access machines, then you just need to not associate availability slots with them.</p>"
welcome_manager:
title: "Machines"
content: "Machines are the tools available for the users to reserve."
view:
title: "View"
content: "To modify or delete a machine, click here first. You will not be able to delete a machine that has already been associated with availability slots, but you can deactivate it."
reserve:
title: "Reserve"
content: "Click here to access an agenda showing free slots. This will let you book this machine for an user and manage existing reservations."
spaces:
welcome:
title: "Spaces"
content: "<p>Spaces are places available for your users. For example, a meeting room or a woodshop. You must create here the spaces which can then be reserved by members.</p><p>The specificity of the spaces is that they can be reserved by several users at the same time.</p>"
welcome_manager:
title: "Spaces"
content: "<p>Spaces are places available to users, by reservation. For example, a meeting room or a woodshop.</p><p>The specificity of the spaces is that they can be reserved by several users at the same time.</p>"
view:
title: "View"
content: "To modify or delete a space, click here first. You will not be able to delete a space that has already been associated with availability slots, but you can deactivate it."
reserve:
title: "Reserve"
content: "Click here to access an agenda showing free slots. This will let you book this space for an user and manage existing reservations."

View File

@ -148,7 +148,7 @@ es:
on_the_date: "El {DATE}"
from_time_to_time: "Desde {START} hasta {END}"
without_reservation: "Sin reserva"
free_admission: "Admisión gratuita"
free_admission: "Entrada gratuita"
full_price: "Precio completo: "
event_full: "Evento lleno"
still_available: "Asiento(s) disponible(s): "

View File

@ -148,7 +148,7 @@ pt:
on_the_date: "Em {DATE}"
from_time_to_time: "Das {START} até {END}"
without_reservation: "Sem reservas"
free_admission: "Admissão grátis"
free_admission: "Entrada grátis"
full_price: "Valor inteira: "
event_full: "Evento lotado"
still_available: "Locais disponíveis: "

View File

@ -121,7 +121,7 @@ If set to 'false' the phone number won't be required to register a new user on t
BOOK_SLOT_AT_SAME_TIME
If set to 'false', users won't be able to book a machine/formation/event slot if they already have a reservation the same day at the same time.
If set to 'true', users will be able to book a machine/formation/event slot, even if they already have a reservation the same day at the same time.
<a name="USER_CONFIRMATION_NEEDED_TO_SIGN_IN"></a>
USER_CONFIRMATION_NEEDED_TO_SIGN_IN

View File

@ -48,7 +48,10 @@ namespace :fablab do
desc 'sync users to the stripe database'
task sync_members: :environment do
User.members.each do |member|
puts 'We create all non-existing customers on stripe. This may take a while, please wait...'
total = User.online_payers.count
User.online_payers.each_with_index do |member, index|
print_on_line "#{index} / #{total}"
begin
stp_customer = Stripe::Customer.retrieve member.stp_customer_id
StripeWorker.perform_async(:create_stripe_customer, member.id) if stp_customer.nil? || stp_customer[:deleted]
@ -56,6 +59,12 @@ namespace :fablab do
StripeWorker.perform_async(:create_stripe_customer, member.id)
end
end
puts 'Done'
end
def print_on_line(str)
print "#{str}\r"
$stdout.flush
end
end
end