mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
Merge branch 'dev' for release 4.0.0
This commit is contained in:
commit
2300acc7e4
@ -6,7 +6,8 @@
|
||||
"globals": {
|
||||
"Application": true,
|
||||
"angular": true,
|
||||
"Fablab": true
|
||||
"Fablab": true,
|
||||
"moment": true,
|
||||
}
|
||||
}
|
||||
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,5 +1,28 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## v4.0.0 2019 June 17
|
||||
|
||||
- Configurable privacy policy and data protection officer
|
||||
- Alert users on privacy policy update
|
||||
- Abuses reports management panel
|
||||
- Refactored user's profile to keep invoicing data after an user was deleted
|
||||
- Refactored user's profile to keep statistical data after an user was deleted
|
||||
- Ability to delete an user (fixes #129 and #120)
|
||||
- Ask user acceptance before deposing analytics cookies
|
||||
- Fix a bug: (spanish) some translations are not loaded correctly
|
||||
- Fix a bug: some users may not appear in the admin's general listing
|
||||
- Fix a bug: Availabilities export report an erroneous number of reservations for machine availabilities (#131)
|
||||
- Fix a bug: close period reminder is sent before the first invoice's first anniversary
|
||||
- Fix a bug: Canceled reservations are not removed from statistics (#133)
|
||||
- Improved translations syntax according to YML specifications
|
||||
- Refactored some Ruby code to match style guide
|
||||
- [TODO DEPLOY] `rake fablab:fix:users_group_ids`
|
||||
- [TODO DEPLOY] `rake db:migrate`
|
||||
- [TODO DEPLOY] `rake db:seed`
|
||||
- [TODO DEPLOY] `rake fablab:setup:migrate_pdf_invoices_folders`
|
||||
- [TODO DEPLOY] `rake fablab:maintenance:delete_inactive_users` (will prompt for confirmation)
|
||||
- [TODO DEPLOY] `rake fablab:maintenance:rebuild_stylesheet`
|
||||
|
||||
## v3.1.2 2019 May 27
|
||||
|
||||
- Fix a bug: when generating an Avoir at a previous date, the resulting checksum may be invalid
|
||||
@ -8,7 +31,7 @@
|
||||
- [TODO DEPLOY] `rake fablab:setup:chain_invoices_items_records`
|
||||
- [TODO DEPLOY] `rake fablab:setup:chain_invoices_records`
|
||||
- [TODO DEPLOY] `rake fablab:setup:chain_history_values_records`
|
||||
- [TODO DEPLOY] -> (only dev) yarn install
|
||||
- [TODO DEPLOY] -> (only dev) yarn install
|
||||
|
||||
## v3.1.1 2019 April 8
|
||||
|
||||
@ -48,6 +71,7 @@
|
||||
- [TODO DEPLOY] /!\ Before deploying, you must check (and eventually) correct your VAT history using the rails console. Missing rates can be added later but dates and rates (including date of activation, disabling) MUST be correct. These values are very likely wrong if your installation was made prior to 2.8.0 with VAT enabled. Other cases must be checked too.
|
||||
- [TODO DEPLOY] -> (only dev) if applicable, you must first downgrade bundler to v1 `gem uninstall bundler --version=2.0.1 && gem install bundler --version=1.7.3 && bundle install`
|
||||
- [TODO DEPLOY] if you have changed your VAT rate in the past, add its history into database. You can use a rate of "0" to disable VAT. Eg. `rake fablab:setup:add_vat_rate[20,2017-01-01]`
|
||||
- [TODO DEPLOY] `rake db:migrate`
|
||||
- [TODO DEPLOY] `rake fablab:setup:set_environment_to_invoices`
|
||||
- [TODO DEPLOY] `rake fablab:setup:chain_invoices_items_records`
|
||||
- [TODO DEPLOY] `rake fablab:setup:chain_invoices_records`
|
||||
|
55
README.md
55
README.md
@ -14,9 +14,7 @@ FabManager is the Fab Lab management solution. It provides a comprehensive, web-
|
||||
4.1. [General Guidelines](#general-guidelines)<br/>
|
||||
4.2. [Virtual Machine Instructions](#virtual-machine-instructions)
|
||||
5. [PostgreSQL](#postgresql)<br/>
|
||||
5.1. [Install PostgreSQL 9.4](#setup-postgresql)<br/>
|
||||
5.2. [Run the PostgreSQL command line interface](#run-postgresql-cli)<br/>
|
||||
5.3. [PostgreSQL Limitations](#postgresql-limitations)
|
||||
5.1. [Install PostgreSQL 9.4](#setup-postgresql)
|
||||
6. [ElasticSearch](#elasticsearch)<br/>
|
||||
6.1. [Install ElasticSearch](#setup-elasticsearch)<br/>
|
||||
6.2. [Rebuild statistics](#rebuild-stats)<br/>
|
||||
@ -293,55 +291,8 @@ We will use docker to easily install the required version of PostgreSQL.
|
||||
On MacOS, you'll have to set the host to 127.0.0.1 (or localhost).
|
||||
See [environment.md](doc/environment.md) for more details.
|
||||
|
||||
4. Finally, have a look at the [PostgreSQL Limitations](#postgresql-limitations) section or some errors will occurs preventing you from finishing the installation procedure.
|
||||
|
||||
|
||||
<a name="run-postgresql-cli"></a>
|
||||
### Run the PostgreSQL command line interface
|
||||
|
||||
You may want to access the psql command line tool to check the content of the database, or to run some maintenance routines.
|
||||
This can be achieved doing the following:
|
||||
|
||||
1. Enter into the PostgreSQL container
|
||||
```bash
|
||||
docker exec -it fabmanager-postgres bash
|
||||
```
|
||||
|
||||
2. Run the PostgreSQL administration command line interface, logged as the postgres user
|
||||
|
||||
```bash
|
||||
su postgres
|
||||
psql
|
||||
```
|
||||
|
||||
<a name="postgresql-limitations"></a>
|
||||
### PostgreSQL Limitations
|
||||
|
||||
- While setting up the database, we'll need to activate two PostgreSQL extensions: [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html).
|
||||
This can only be achieved if the user, configured in `config/database.yml`, was granted the _SUPERUSER_ role **OR** if these extensions were white-listed.
|
||||
So here's your choices, mainly depending on your security requirements:
|
||||
- Use the default PostgreSQL super-user (postgres) as the database user. This is the default behavior in fab-manager.
|
||||
- Set your user as _SUPERUSER_; run the following command in `psql` (after replacing `username` with you user name):
|
||||
|
||||
```sql
|
||||
ALTER USER username WITH SUPERUSER;
|
||||
```
|
||||
|
||||
- Install and configure the PostgreSQL extension [pgextwlist](https://github.com/dimitri/pgextwlist).
|
||||
Please follow the instructions detailed on the extension website to whitelist `unaccent` and `trigram` for the user configured in `config/database.yml`.
|
||||
- Some users may want to use another DBMS than PostgreSQL.
|
||||
This is currently not supported, because of some PostgreSQL specific instructions that cannot be efficiently handled with the ActiveRecord ORM:
|
||||
- `app/controllers/api/members_controllers.rb@list` is using `ILIKE`
|
||||
- `app/controllers/api/invoices_controllers.rb@list` is using `ILIKE` and `date_trunc()`
|
||||
- `db/migrate/20160613093842_create_unaccent_function.rb` is using [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html) modules and defines a PL/pgSQL function (`f_unaccent()`)
|
||||
- `app/controllers/api/members_controllers.rb@search` is using `f_unaccent()` (see above) and `regexp_replace()`
|
||||
- `db/migrate/20150604131525_add_meta_data_to_notifications.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype.
|
||||
- `db/migrate/20160915105234_add_transformation_to_o_auth2_mapping.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype.
|
||||
- `db/migrate/20181217103441_migrate_settings_value_to_history_values.rb` is using `SELECT DISTINCT ON`.
|
||||
- `db/migrate/20190107111749_protect_accounting_periods.rb` is using `CREATE RULE` and `DROP RULE`.
|
||||
- If you intend to contribute to the project code, you will need to run the test suite with `rake test`.
|
||||
This also requires your user to have the _SUPERUSER_ role.
|
||||
Please see the [known issues](#known-issues) section for more information about this.
|
||||
4 . Finally, you may want to have a look at detailed informations about PostgreSQL usage in fab-manager.
|
||||
Some information about that is available in the [PostgreSQL Readme](doc/postgresql_readme.md).
|
||||
|
||||
<a name="elasticsearch"></a>
|
||||
## ElasticSearch
|
||||
|
@ -23,13 +23,21 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
|
||||
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock'])
|
||||
.config(['$httpProvider', 'AuthProvider', 'growlProvider', 'unsavedWarningsConfigProvider', 'AnalyticsProvider', 'uibDatepickerPopupConfig', '$provide', '$translateProvider',
|
||||
function ($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider) {
|
||||
// Google analytics
|
||||
AnalyticsProvider.setAccount(Fablab.gaId);
|
||||
// track all routes (or not)
|
||||
AnalyticsProvider.trackPages(true);
|
||||
AnalyticsProvider.setDomainName(Fablab.defaultHost);
|
||||
AnalyticsProvider.useAnalytics(true);
|
||||
AnalyticsProvider.setPageEvent('$stateChangeSuccess');
|
||||
// Google analytics
|
||||
// first we check the user acceptance
|
||||
const cookiesConsent = document.cookie.replace(/(?:(?:^|.*;\s*)fab-manager-cookies-consent\s*=\s*([^;]*).*$)|^.*$/, '$1');
|
||||
if (cookiesConsent === 'accept') {
|
||||
AnalyticsProvider.setAccount(Fablab.gaId);
|
||||
// track all routes (or not)
|
||||
AnalyticsProvider.trackPages(true);
|
||||
AnalyticsProvider.setDomainName(Fablab.defaultHost);
|
||||
AnalyticsProvider.useAnalytics(true);
|
||||
AnalyticsProvider.setPageEvent('$stateChangeSuccess');
|
||||
} else {
|
||||
// if the cookies were not explicitly accepted, delete them
|
||||
document.cookie = '_ga=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
document.cookie = '_gid=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
}
|
||||
|
||||
// Custom messages for the date-picker widget
|
||||
uibDatepickerPopupConfig.closeText = Fablab.translations.app.shared.buttons.close;
|
||||
@ -122,15 +130,15 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
|
||||
Analytics.pageView();
|
||||
|
||||
/**
|
||||
* This helper method builds and return an array contaning every integers between
|
||||
* This helper method builds and return an array containing every integers between
|
||||
* the provided start and end.
|
||||
* @param start {number}
|
||||
* @param end {number}
|
||||
* @return {Array} [start .. end]
|
||||
*/
|
||||
$rootScope.intArray = function (start, end) {
|
||||
var arr = [];
|
||||
for (var i = start; i < end; i++) { arr.push(i); }
|
||||
const arr = [];
|
||||
for (let i = start; i < end; i++) { arr.push(i); }
|
||||
return arr;
|
||||
};
|
||||
}]).constant('angularMomentConfig', {
|
||||
|
@ -1,29 +1,20 @@
|
||||
/* eslint-disable
|
||||
no-return-assign,
|
||||
no-undef,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('AboutController', ['$scope', 'Setting', 'CustomAsset', function ($scope, Setting, CustomAsset) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
Setting.get({ name: 'about_title' }, data => $scope.aboutTitle = data.setting);
|
||||
Setting.get({ name: 'about_title' }, data => { $scope.aboutTitle = data.setting; });
|
||||
|
||||
Setting.get({ name: 'about_body' }, data => $scope.aboutBody = data.setting);
|
||||
Setting.get({ name: 'about_body' }, data => { $scope.aboutBody = data.setting; });
|
||||
|
||||
Setting.get({ name: 'about_contacts' }, data => $scope.aboutContacts = data.setting);
|
||||
Setting.get({ name: 'about_contacts' }, data => { $scope.aboutContacts = data.setting; });
|
||||
|
||||
Setting.get({ name: 'privacy_body' }, data => { $scope.privacyPolicy = data.setting; });
|
||||
|
||||
// retrieve the CGU
|
||||
CustomAsset.get({ name: 'cgu-file' }, cgu => $scope.cgu = cgu.custom_asset);
|
||||
CustomAsset.get({ name: 'cgu-file' }, cgu => { $scope.cgu = cgu.custom_asset; });
|
||||
|
||||
// retrieve the CGV
|
||||
return CustomAsset.get({ name: 'cgv-file' }, cgv => $scope.cgv = cgv.custom_asset);
|
||||
CustomAsset.get({ name: 'cgv-file' }, cgv => { $scope.cgv = cgv.custom_asset; });
|
||||
}
|
||||
]);
|
||||
|
52
app/assets/javascripts/controllers/admin/abuses.js
Normal file
52
app/assets/javascripts/controllers/admin/abuses.js
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Controller used in abuses management page
|
||||
*/
|
||||
Application.Controllers.controller('AbusesController', ['$scope', '$state', 'Abuse', 'abusesPromise', 'dialogs', 'growl', '_t',
|
||||
function ($scope, $state, Abuse, abusesPromise, dialogs, growl, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// List of all reported abuses
|
||||
$scope.abuses = [];
|
||||
|
||||
/**
|
||||
* Callback handling a click on the ✓ button: confirm before delete
|
||||
*/
|
||||
$scope.confirmProcess = function (abuseId) {
|
||||
dialogs.confirm(
|
||||
{
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('manage_abuses.confirmation_required'),
|
||||
msg: _t('manage_abuses.report_will_be_destroyed')
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () { // cancel confirmed
|
||||
Abuse.remove({ id: abuseId }, function () { // successfully canceled
|
||||
growl.success(_t('manage_abuses.report_removed'));
|
||||
Abuse.query({}, function (abuses) {
|
||||
$scope.abuses = abuses.abuses.filter(a => a.signaled_type === 'Project');
|
||||
});
|
||||
}
|
||||
, function () { // error while canceling
|
||||
growl.error(_t('manage_abuses.failed_to_remove'));
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// we display only abuses related to projects
|
||||
$scope.abuses = abusesPromise.abuses.filter(a => a.signaled_type === 'Project');
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
]);
|
@ -1,2 +0,0 @@
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Sanity-check the conversion and remove this comment.
|
@ -264,8 +264,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
* @return {string} 'male' or 'female'
|
||||
*/
|
||||
var getGender = function (user) {
|
||||
if (user.profile) {
|
||||
if (user.profile.gender === 'true') { return 'male'; } else { return 'female'; }
|
||||
if (user.statistic_profile) {
|
||||
if (user.statistic_profile.gender === 'true') { return 'male'; } else { return 'female'; }
|
||||
} else { return 'other'; }
|
||||
};
|
||||
|
||||
|
@ -397,8 +397,11 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
* Full reload the results list
|
||||
*/
|
||||
$scope.handleFilterChange = function () {
|
||||
resetSearchInvoice();
|
||||
return invoiceSearch();
|
||||
if (searchTimeout) clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(function() {
|
||||
resetSearchInvoice();
|
||||
invoiceSearch();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -407,7 +410,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
*/
|
||||
$scope.showNextInvoices = function () {
|
||||
$scope.page += 1;
|
||||
return invoiceSearch(true);
|
||||
invoiceSearch(true);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -484,6 +487,11 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Will temporize the search query to prevent overloading the API
|
||||
*/
|
||||
var searchTimeout = null;
|
||||
|
||||
/**
|
||||
* Output the given integer with leading zeros. If the given value is longer than the given
|
||||
* length, it will be truncated.
|
||||
@ -519,7 +527,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
/**
|
||||
* Run a search query with the current parameters set concerning invoices, then affect or concat the results
|
||||
* to $scope.invoices
|
||||
* @param concat {boolean} if true, the result will be append to $scope.invoices instead of being affected
|
||||
* @param [concat] {boolean} if true, the result will be append to $scope.invoices instead of being affected
|
||||
*/
|
||||
var invoiceSearch = function (concat) {
|
||||
Invoice.list({
|
||||
|
@ -225,8 +225,11 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
* Callback when the search field content changes: reload the search results
|
||||
*/
|
||||
$scope.updateTextSearch = function () {
|
||||
resetSearchMember();
|
||||
return memberSearch();
|
||||
if (searchTimeout) clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(function() {
|
||||
resetSearchMember();
|
||||
memberSearch();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -252,6 +255,11 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Will temporize the search query to prevent overloading the API
|
||||
*/
|
||||
var searchTimeout = null;
|
||||
|
||||
/**
|
||||
* Iterate through the provided array and return the index of the requested admin
|
||||
* @param admins {Array} full list of users with role 'admin'
|
||||
@ -267,13 +275,13 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
*/
|
||||
var resetSearchMember = function () {
|
||||
$scope.member.noMore = false;
|
||||
return $scope.member.page = 1;
|
||||
$scope.member.page = 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a search query with the current parameters set ($scope.member[searchText,order,page])
|
||||
* and affect or append the result in $scope.members, depending on the concat parameter
|
||||
* @param concat {boolean} if true, the result will be append to $scope.members instead of being affected
|
||||
* @param [concat] {boolean} if true, the result will be append to $scope.members instead of being affected
|
||||
*/
|
||||
var memberSearch = function (concat) {
|
||||
Member.list({
|
||||
@ -314,15 +322,14 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
// Form action on the above URL
|
||||
$scope.method = 'patch';
|
||||
|
||||
// List of tags associables with user
|
||||
// List of tags joinable with user
|
||||
$scope.tags = tagsPromise;
|
||||
|
||||
// The user to edit
|
||||
$scope.user = memberPromise;
|
||||
|
||||
// Should the passord be modified?
|
||||
$scope.password =
|
||||
{ change: false };
|
||||
// Should the password be modified?
|
||||
$scope.password = { change: false };
|
||||
|
||||
// the user subscription
|
||||
if (($scope.user.subscribed_plan != null) && ($scope.user.subscription != null)) {
|
||||
@ -537,7 +544,7 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
CSRF.setMetaTags();
|
||||
|
||||
// init the birth date to JS object
|
||||
$scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate();
|
||||
$scope.user.statistic_profile.birthday = moment($scope.user.statistic_profile.birthday).toDate();
|
||||
|
||||
// the user subscription
|
||||
if (($scope.user.subscribed_plan != null) && ($scope.user.subscription != null)) {
|
||||
@ -576,22 +583,24 @@ Application.Controllers.controller('NewMemberController', ['$scope', '$state', '
|
||||
// Form action on the above URL
|
||||
$scope.method = 'post';
|
||||
|
||||
// Should the passord be set manually or generated?
|
||||
$scope.password =
|
||||
{ change: false };
|
||||
// Should the password be set manually or generated?
|
||||
$scope.password = { change: false };
|
||||
|
||||
// Default member's profile parameters
|
||||
$scope.user =
|
||||
{ plan_interval: '' };
|
||||
$scope.user = {
|
||||
plan_interval: '',
|
||||
invoicing_profile: {},
|
||||
statistic_profile: {}
|
||||
};
|
||||
|
||||
// Callback when the admin check/unckeck the box telling that the new user is an organization.
|
||||
// Callback when the admin check/uncheck the box telling that the new user is an organization.
|
||||
// Disable or enable the organization fields in the form, accordingly
|
||||
$scope.toggleOrganization = function () {
|
||||
if ($scope.user.organization) {
|
||||
if (!$scope.user.profile) { $scope.user.profile = {}; }
|
||||
return $scope.user.profile.organization = {};
|
||||
if (!$scope.user.invoicing_profile) { $scope.user.invoicing_profile = {}; }
|
||||
$scope.user.invoicing_profile.organization = {};
|
||||
} else {
|
||||
return $scope.user.profile.organization = undefined;
|
||||
$scope.user.invoicing_profile.organization = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@ -607,9 +616,11 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
|
||||
// default admin profile
|
||||
let getGender;
|
||||
$scope.admin = {
|
||||
profile_attributes: {
|
||||
statistic_profile_attributes: {
|
||||
gender: true
|
||||
}
|
||||
},
|
||||
profile_attributes: {},
|
||||
invoicing_profile_attributes: {}
|
||||
};
|
||||
|
||||
// Default parameters for AngularUI-Bootstrap datepicker
|
||||
@ -652,8 +663,8 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
|
||||
* @return {string} 'male' or 'female'
|
||||
*/
|
||||
return getGender = function (user) {
|
||||
if (user.profile_attributes) {
|
||||
if (user.profile_attributes.gender) { return 'male'; } else { return 'female'; }
|
||||
if (user.statistic_profile_attributes) {
|
||||
if (user.statistic_profile_attributes.gender) { return 'male'; } else { return 'female'; }
|
||||
} else { return 'other'; }
|
||||
};
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ Application.Controllers.controller('NewPlanController', ['$scope', '$uibModal',
|
||||
if ((content.id == null) && (content.plan_ids == null)) {
|
||||
return growl.error(_t('new_plan.unable_to_create_the_subscription_please_try_again'));
|
||||
} else {
|
||||
growl.success(_t('new_plan.successfully_created_subscription(s)_dont_forget_to_redefine_prices'));
|
||||
growl.success(_t('new_plan.successfully_created_subscriptions_dont_forget_to_redefine_prices'));
|
||||
if (content.plan_ids != null) {
|
||||
return $state.go('app.admin.pricing');
|
||||
} else {
|
||||
|
@ -12,8 +12,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('SettingsController', ['$scope', 'Setting', 'growl', 'settingsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'profileImageFile', 'CSRF', '_t',
|
||||
function ($scope, Setting, growl, settingsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, profileImageFile, CSRF, _t) {
|
||||
Application.Controllers.controller('SettingsController', ['$scope', '$filter', '$uibModal', 'Setting', 'growl', 'settingsPromise', 'privacyDraftsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'profileImageFile', 'CSRF', '_t',
|
||||
function ($scope, $filter, $uibModal, Setting, growl, settingsPromise, privacyDraftsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, profileImageFile, CSRF, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// timepickers steps configuration
|
||||
@ -48,10 +48,14 @@ Application.Controllers.controller('SettingsController', ['$scope', 'Setting', '
|
||||
cgv: false
|
||||
};
|
||||
|
||||
// various parametrable settings
|
||||
// full history of privacy policy drafts
|
||||
$scope.privacyDraftsHistory = [];
|
||||
|
||||
// various configurable settings
|
||||
$scope.twitterSetting = { name: 'twitter_name', value: settingsPromise.twitter_name };
|
||||
$scope.aboutTitleSetting = { name: 'about_title', value: settingsPromise.about_title };
|
||||
$scope.aboutBodySetting = { name: 'about_body', value: settingsPromise.about_body };
|
||||
$scope.privacyDpoSetting = { name: 'privacy_dpo', value: settingsPromise.privacy_dpo };
|
||||
$scope.aboutContactsSetting = { name: 'about_contacts', value: settingsPromise.about_contacts };
|
||||
$scope.homeBlogpostSetting = { name: 'home_blogpost', value: settingsPromise.home_blogpost };
|
||||
$scope.machineExplicationsAlert = { name: 'machine_explications_alert', value: settingsPromise.machine_explications_alert };
|
||||
@ -119,6 +123,12 @@ Application.Controllers.controller('SettingsController', ['$scope', 'Setting', '
|
||||
value: (settingsPromise.display_name_enable === 'true')
|
||||
};
|
||||
|
||||
// By default, we display the currently published privacy policy
|
||||
$scope.privacyPolicy = {
|
||||
version: null,
|
||||
bodyTemp: settingsPromise.privacy_body
|
||||
};
|
||||
|
||||
/**
|
||||
* For use with 'ng-class', returns the CSS class name for the uploads previews.
|
||||
* The preview may show a placeholder or the content of the file depending on the upload state.
|
||||
@ -153,8 +163,47 @@ Application.Controllers.controller('SettingsController', ['$scope', 'Setting', '
|
||||
({ value } = setting);
|
||||
}
|
||||
|
||||
return Setting.update({ name: setting.name }, { value }, data => growl.success(_t('settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`settings.${setting.name}`) }))
|
||||
, error => console.log(error));
|
||||
Setting.update(
|
||||
{ name: setting.name },
|
||||
{ value },
|
||||
function () { growl.success(_t('settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`settings.${setting.name}`) })); },
|
||||
function (error) { console.log(error); }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The privacy policy has its own special save function because updating the policy must notify all users
|
||||
*/
|
||||
$scope.savePrivacyPolicy = function () {
|
||||
// open modal
|
||||
const modalInstance = $uibModal.open({
|
||||
templateUrl: '<%= asset_path "admin/settings/save_policy.html" %>',
|
||||
controller: 'SavePolicyController',
|
||||
resolve: {
|
||||
saveCb () { return $scope.save; },
|
||||
privacyPolicy () { return $scope.privacyPolicy; }
|
||||
}
|
||||
});
|
||||
|
||||
// once done, update the client data
|
||||
modalInstance.result.then(function (type) {
|
||||
Setting.get({ name: 'privacy_draft', history: true }, function (data) {
|
||||
// reset history
|
||||
$scope.privacyDraftsHistory = [];
|
||||
data.setting.history.forEach(function (draft) {
|
||||
$scope.privacyDraftsHistory.push({ id: draft.id, name: _t('settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: draft.created_at }), content: draft.value });
|
||||
});
|
||||
if (type === 'privacy_draft') {
|
||||
const orderedHistory = $filter('orderBy')(data.setting.history, 'created_at');
|
||||
const last = orderedHistory[orderedHistory.length - 1];
|
||||
if (last) {
|
||||
$scope.privacyPolicy.version = last.id;
|
||||
}
|
||||
} else {
|
||||
$scope.privacyPolicy.version = null;
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -166,9 +215,9 @@ Application.Controllers.controller('SettingsController', ['$scope', 'Setting', '
|
||||
$scope.submited = function (content) {
|
||||
if ((content.custom_asset == null)) {
|
||||
$scope.alerts = [];
|
||||
return angular.forEach(content, (v, k) =>
|
||||
angular.forEach(v, err => growl.error(err))
|
||||
);
|
||||
return angular.forEach(content, function (v) {
|
||||
angular.forEach(v, function(err) { growl.error(err); })
|
||||
});
|
||||
} else {
|
||||
growl.success(_t('settings.file_successfully_updated'));
|
||||
if (content.custom_asset.name === 'cgu-file') {
|
||||
@ -204,7 +253,25 @@ Application.Controllers.controller('SettingsController', ['$scope', 'Setting', '
|
||||
/**
|
||||
* @param target {String} 'cgu' | 'cgv'
|
||||
*/
|
||||
$scope.addLoader = target => $scope.loader[target] = true;
|
||||
$scope.addLoader = function (target) {
|
||||
$scope.loader[target] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the revision of the displayed privacy policy, from drafts history
|
||||
*/
|
||||
$scope.handlePolicyRevisionChange = function () {
|
||||
if ($scope.privacyPolicy.version === null) {
|
||||
$scope.privacyPolicy.bodyTemp = settingsPromise.privacy_body;
|
||||
return;
|
||||
}
|
||||
for (const draft of $scope.privacyDraftsHistory) {
|
||||
if (draft.id == $scope.privacyPolicy.version) {
|
||||
$scope.privacyPolicy.bodyTemp = draft.content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
@ -245,8 +312,12 @@ Application.Controllers.controller('SettingsController', ['$scope', 'Setting', '
|
||||
}
|
||||
if (profileImageFile.custom_asset) {
|
||||
$scope.methods.profileImage = 'put';
|
||||
return $scope.actionUrl.profileImage += '/profile-image-file';
|
||||
$scope.actionUrl.profileImage += '/profile-image-file';
|
||||
}
|
||||
|
||||
privacyDraftsPromise.setting.history.forEach(function (draft) {
|
||||
$scope.privacyDraftsHistory.push({ id: draft.id, name: _t('settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: moment(draft.created_at).format('L LT') }), content: draft.value });
|
||||
});
|
||||
};
|
||||
|
||||
// init the controller (call at the end !)
|
||||
@ -254,3 +325,36 @@ Application.Controllers.controller('SettingsController', ['$scope', 'Setting', '
|
||||
}
|
||||
|
||||
]);
|
||||
|
||||
|
||||
/**
|
||||
* Controller used in the invoice refunding modal window
|
||||
*/
|
||||
Application.Controllers.controller('SavePolicyController', ['$scope', '$uibModalInstance', '_t', 'growl', 'saveCb', 'privacyPolicy',
|
||||
function ($scope, $uibModalInstance, _t, growl, saveCb, privacyPolicy) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
/**
|
||||
* Save as draft the current text
|
||||
*/
|
||||
$scope.save = function () {
|
||||
saveCb({ name: 'privacy_draft', value: privacyPolicy.bodyTemp });
|
||||
$uibModalInstance.close('privacy_draft');
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish the current text as the new privacy policy
|
||||
*/
|
||||
$scope.publish = function () {
|
||||
saveCb({ name: 'privacy_body', value: privacyPolicy.bodyTemp });
|
||||
growl.info(_t('settings.privacy.users_notified'));
|
||||
$uibModalInstance.close('privacy_body');
|
||||
};
|
||||
/**
|
||||
* Cancel the saving, dismiss the modal window
|
||||
*/
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
}
|
||||
]);
|
@ -86,7 +86,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
return $uibModal.open({
|
||||
templateUrl: '<%= asset_path "shared/signupModal.html" %>',
|
||||
size: 'md',
|
||||
controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', function ($scope, $uibModalInstance, Group, CustomAsset) {
|
||||
controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', 'growl', '_t', function ($scope, $uibModalInstance, Group, CustomAsset, growl, _t) {
|
||||
// default parameters for the date picker in the account creation modal
|
||||
$scope.datePicker = {
|
||||
format: Fablab.uibDateFormat,
|
||||
@ -134,8 +134,13 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
delete $scope.user.organization;
|
||||
// register on server
|
||||
return Auth.register($scope.user).then(function (user) {
|
||||
// creation successful
|
||||
$uibModalInstance.close(user);
|
||||
if (user.id) {
|
||||
// creation successful
|
||||
$uibModalInstance.close(user);
|
||||
} else {
|
||||
// the user was not saved in database, something wrong occurred
|
||||
growl.error(_t('unexpected_error_occurred'));
|
||||
}
|
||||
}, function (error) {
|
||||
// creation failed...
|
||||
// restore organization param
|
||||
|
64
app/assets/javascripts/controllers/cookies.js
Normal file
64
app/assets/javascripts/controllers/cookies.js
Normal file
@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Controller used for the cookies consent modal
|
||||
*/
|
||||
Application.Controllers.controller('CookiesController', ['$scope', '$cookies', 'Setting',
|
||||
function ($scope, $cookies, Setting) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// the acceptation state (undefined if no decision was made until now)
|
||||
$scope.cookiesState = undefined;
|
||||
|
||||
// link pointed by "learn more"
|
||||
$scope.learnMoreUrl = 'https://www.cookiesandyou.com/';
|
||||
|
||||
// current user wallet
|
||||
$scope.declineCookies = function () {
|
||||
const expires = moment().add(13, 'months').toDate();
|
||||
$cookies.put('fab-manager-cookies-consent', 'decline', { expires });
|
||||
readCookie();
|
||||
};
|
||||
|
||||
// current wallet transactions
|
||||
$scope.acceptCookies = function () {
|
||||
const expires = moment().add(13, 'months').toDate();
|
||||
$cookies.put('fab-manager-cookies-consent', 'accept', { expires });
|
||||
readCookie();
|
||||
// enable tracking using code provided by google analytics
|
||||
/* eslint-disable */
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', Fablab.gaId, 'auto');
|
||||
ga('send', 'pageview');
|
||||
/* eslint-enable */
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
readCookie();
|
||||
// if the privacy policy was defined, redirect the user to it
|
||||
Setting.get({ name: 'privacy_body' }, data => {
|
||||
if (data.setting.value) {
|
||||
$scope.learnMoreUrl = '#!/privacy-policy';
|
||||
}
|
||||
});
|
||||
// if the GA_ID environment variable was not set, only functional cookies will be set, so user consent is not required
|
||||
$scope.cookiesState = 'ignore';
|
||||
};
|
||||
|
||||
const readCookie = function () {
|
||||
$scope.cookiesState = $cookies.get('fab-manager-cookies-consent');
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
]);
|
@ -70,10 +70,10 @@ Application.Controllers.controller('MembersController', ['$scope', 'Member', 'me
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller used when editing the current user's profile
|
||||
* Controller used when editing the current user's profile (in dashboard)
|
||||
*/
|
||||
Application.Controllers.controller('EditProfileController', ['$scope', '$rootScope', '$state', '$window', 'Member', 'Auth', 'Session', 'activeProviderPromise', 'growl', 'dialogs', 'CSRF', 'memberPromise', 'groups', '_t',
|
||||
function ($scope, $rootScope, $state, $window, Member, Auth, Session, activeProviderPromise, growl, dialogs, CSRF, memberPromise, groups, _t) {
|
||||
Application.Controllers.controller('EditProfileController', ['$scope', '$rootScope', '$state', '$window', '$sce', '$cookies', '$injector', 'Member', 'Auth', 'Session', 'activeProviderPromise', 'growl', 'dialogs', 'CSRF', 'memberPromise', 'groups', '_t',
|
||||
function ($scope, $rootScope, $state, $window, $sce, $cookies, $injector, Member, Auth, Session, activeProviderPromise, growl, dialogs, CSRF, memberPromise, groups, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// API URL where the form will be posted
|
||||
@ -101,12 +101,14 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
// allow the user to change his password except if he connect from an SSO
|
||||
$scope.preventPassword = false;
|
||||
|
||||
// get the status of cookies acceptance
|
||||
$scope.cookiesStatus = $cookies.get('fab-manager-cookies-consent');
|
||||
|
||||
// mapping of fields to disable
|
||||
$scope.preventField = {};
|
||||
|
||||
// Should the passord be modified?
|
||||
$scope.password =
|
||||
{ change: false };
|
||||
$scope.password = { change: false };
|
||||
|
||||
// Angular-Bootstrap datepicker configuration for birthday
|
||||
$scope.datePicker = {
|
||||
@ -117,6 +119,9 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
}
|
||||
};
|
||||
|
||||
// This boolean value will tell if the current user is the super-admin
|
||||
$scope.isSuperAdmin = memberPromise.id === Fablab.superadminId;
|
||||
|
||||
/**
|
||||
* Return the group object, identified by the ID set in $scope.userGroup
|
||||
*/
|
||||
@ -137,10 +142,10 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
$rootScope.currentUser = user;
|
||||
Auth._currentUser.group_id = user.group_id;
|
||||
$scope.group.change = false;
|
||||
return growl.success(_t('your_group_has_been_successfully_changed'));
|
||||
return growl.success(_t('edit_profile.your_group_has_been_successfully_changed'));
|
||||
}
|
||||
, function (err) {
|
||||
growl.error(_t('an_unexpected_error_prevented_your_group_from_being_changed'));
|
||||
growl.error(_t('edit_profile.an_unexpected_error_prevented_your_group_from_being_changed'));
|
||||
return console.error(err);
|
||||
});
|
||||
|
||||
@ -194,7 +199,13 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_your_account') + ' ' + _t('all_data_relative_to_your_projects_will_be_lost')
|
||||
msg: $sce.trustAsHtml(
|
||||
_t('edit_profile.confirm_delete_your_account') + '<br/>' +
|
||||
'<strong>' + _t('edit_profile.all_data_will_be_lost') + '</strong><br/><br/>' +
|
||||
_t('edit_profile.invoicing_data_kept') + '<br/>' +
|
||||
_t('edit_profile.statistic_data_anonymized') + '<br/>' +
|
||||
_t('edit_profile.no_further_access_to_projects')
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -203,12 +214,12 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
Member.remove({ id: user.id }, () =>
|
||||
Auth.logout().then(function () {
|
||||
$state.go('app.public.home');
|
||||
return growl.success(_t('your_user_account_has_been_successfully_deleted_goodbye'));
|
||||
return growl.success(_t('edit_profile.your_user_account_has_been_successfully_deleted_goodbye'));
|
||||
})
|
||||
|
||||
, function (error) {
|
||||
console.log(error);
|
||||
return growl.error(_t('an_error_occured_preventing_your_account_from_being_deleted'));
|
||||
return growl.error(_t('edit_profile.an_error_occured_preventing_your_account_from_being_deleted'));
|
||||
})
|
||||
);
|
||||
|
||||
@ -249,6 +260,15 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
return $window.location.href = $scope.activeProvider.link_to_sso_connect;
|
||||
});
|
||||
|
||||
/**
|
||||
* Destroy the cookie used to save the user's preference, this will trigger the choice popup again
|
||||
*/
|
||||
$scope.resetCookies = function () {
|
||||
$cookies.remove('fab-manager-cookies-consent');
|
||||
$scope.cookiesStatus = undefined;
|
||||
$injector.get('$state').reload();
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -258,7 +278,7 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
CSRF.setMetaTags();
|
||||
|
||||
// init the birth date to JS object
|
||||
$scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate();
|
||||
$scope.user.statistic_profile.birthday = moment($scope.user.statistic_profile.birthday).toDate();
|
||||
|
||||
if ($scope.activeProvider.providable_type !== 'DatabaseProvider') {
|
||||
$scope.preventPassword = true;
|
||||
|
@ -150,8 +150,8 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
* @return {string} 'male' or 'female'
|
||||
*/
|
||||
$scope.getGender = function (user) {
|
||||
if (user && user.profile) {
|
||||
if (user.profile.gender === 'true') { return 'male'; } else { return 'female'; }
|
||||
if (user && user.statistic_profile) {
|
||||
if (user.statistic_profile.gender === 'true') { return 'male'; } else { return 'female'; }
|
||||
} else { return 'other'; }
|
||||
};
|
||||
|
||||
|
10
app/assets/javascripts/controllers/privacy.js
Normal file
10
app/assets/javascripts/controllers/privacy.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('PrivacyController', ['$scope', 'Setting', function ($scope, Setting) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
Setting.get({ name: 'privacy_body' }, data => { $scope.privacyBody = data.setting; });
|
||||
|
||||
Setting.get({ name: 'privacy_dpo' }, data => { $scope.privacyDpo = data.setting; });
|
||||
}
|
||||
]);
|
@ -206,7 +206,7 @@ Application.Controllers.controller('CompleteProfileController', ['$scope', '$roo
|
||||
CSRF.setMetaTags();
|
||||
|
||||
// init the birth date to JS object
|
||||
$scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate();
|
||||
$scope.user.statistic_profile.birthday = moment($scope.user.statistic_profile.birthday).toDate();
|
||||
|
||||
// bind fields protection with sso fields
|
||||
angular.forEach(activeProviderPromise.mapping, function (map) { $scope.preventField[map] = true; });
|
||||
|
@ -257,6 +257,7 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// Number of projects added to the page when the user clicks on 'load more projects'
|
||||
// -- dependency in app/models/project.rb
|
||||
const PROJECTS_PER_PAGE = 16;
|
||||
|
||||
/* PUBLIC SCOPE */
|
||||
@ -323,7 +324,7 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
||||
$scope.projectsPagination = new paginationService.Instance(OpenlabProject, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreOpenlabCallback);
|
||||
return OpenlabProject.query({ q: $scope.search.q, page: currentPage, per_page: PROJECTS_PER_PAGE }, function (projectsPromise) {
|
||||
if (projectsPromise.errors != null) {
|
||||
growl.error(_t('openlab_search_not_available_at_the_moment'));
|
||||
growl.error(_t('projects_list.openlab_search_not_available_at_the_moment'));
|
||||
$scope.openlab.searchOverWholeNetwork = false;
|
||||
return $scope.triggerSearch();
|
||||
} else {
|
||||
|
@ -1,14 +1,3 @@
|
||||
/* eslint-disable
|
||||
no-return-assign,
|
||||
no-undef,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('WalletController', ['$scope', 'walletPromise', 'transactionsPromise',
|
||||
@ -19,6 +8,6 @@ Application.Controllers.controller('WalletController', ['$scope', 'walletPromise
|
||||
$scope.wallet = walletPromise;
|
||||
|
||||
// current wallet transactions
|
||||
return $scope.transactions = transactionsPromise;
|
||||
$scope.transactions = transactionsPromise;
|
||||
}
|
||||
]);
|
||||
|
@ -27,6 +27,10 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "shared/leftnav.html" %>',
|
||||
controller: 'MainNavController'
|
||||
},
|
||||
'cookies': {
|
||||
templateUrl: '<%= asset_path "shared/cookies.html" %>',
|
||||
controller: 'CookiesController'
|
||||
},
|
||||
'main': {}
|
||||
},
|
||||
resolve: {
|
||||
@ -98,6 +102,18 @@ angular.module('application.router', ['ui.router'])
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.home').$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.privacy', {
|
||||
url: '/privacy-policy',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: '<%= asset_path "shared/privacy.html" %>',
|
||||
controller: 'PrivacyController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.privacy').$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
// profile completion (SSO import passage point)
|
||||
.state('app.logged.profileCompletion', {
|
||||
@ -658,6 +674,19 @@ angular.module('application.router', ['ui.router'])
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.project_elements').$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.manage_abuses', {
|
||||
url: '/admin/abuses',
|
||||
views: {
|
||||
'main@': {
|
||||
templateUrl: '<%= asset_path "admin/abuses/index.html" %>',
|
||||
controller: 'AbusesController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
abusesPromise: ['Abuse', function(Abuse) { return Abuse.query().$promise; }],
|
||||
translations: ['Translations', function(Translations) { return Translations.query('app.admin.manage_abuses').$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
// trainings
|
||||
.state('app.admin.trainings', {
|
||||
@ -1035,6 +1064,8 @@ angular.module('application.router', ['ui.router'])
|
||||
names: `['twitter_name', \
|
||||
'about_title', \
|
||||
'about_body', \
|
||||
'privacy_body', \
|
||||
'privacy_dpo', \
|
||||
'about_contacts', \
|
||||
'home_blogpost', \
|
||||
'machine_explications_alert', \
|
||||
@ -1060,6 +1091,7 @@ angular.module('application.router', ['ui.router'])
|
||||
'display_name_enable', \
|
||||
'machines_sort_by']` }).$promise;
|
||||
}],
|
||||
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
|
||||
cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }],
|
||||
cgvFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgv-file' }).$promise; }],
|
||||
faviconFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'favicon-file' }).$promise; }],
|
||||
|
@ -3,8 +3,8 @@
|
||||
Application.Services.factory('Abuse', ['$resource', function ($resource) {
|
||||
return $resource('/api/abuses/:id',
|
||||
{ id: '@id' }, {
|
||||
update: {
|
||||
method: 'PUT'
|
||||
query: {
|
||||
isArray: false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -420,6 +420,10 @@
|
||||
}
|
||||
|
||||
|
||||
.last-update {
|
||||
text-align: right;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -346,6 +346,10 @@ p, .widget p {
|
||||
vertical-align: super
|
||||
}
|
||||
|
||||
.help-cursor {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $screen-lg-min) {
|
||||
.b-r-lg {border-right: 1px solid $border-color; }
|
||||
.hide-b-r-lg { border: none !important; }
|
||||
|
@ -34,6 +34,8 @@
|
||||
@import "app.plugins";
|
||||
@import "modules/invoice";
|
||||
@import "modules/signup";
|
||||
@import "modules/abuses";
|
||||
@import "modules/cookies";
|
||||
|
||||
@import "app.responsive";
|
||||
|
||||
|
31
app/assets/stylesheets/modules/abuses.scss
Normal file
31
app/assets/stylesheets/modules/abuses.scss
Normal file
@ -0,0 +1,31 @@
|
||||
li.abuse {
|
||||
|
||||
list-style: none;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 2em;
|
||||
|
||||
.signaled {
|
||||
background-color: #f5f5f5;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 1em;
|
||||
position: relative;
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.report {
|
||||
padding: 2em;
|
||||
|
||||
cite {
|
||||
display: block;
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
30
app/assets/stylesheets/modules/cookies.scss
Normal file
30
app/assets/stylesheets/modules/cookies.scss
Normal file
@ -0,0 +1,30 @@
|
||||
.cookies-consent {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
bottom: 3rem;
|
||||
left: 3rem;
|
||||
width: 40rem;
|
||||
background-color: #f5f5f5;
|
||||
padding: 3rem;
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
-webkit-box-shadow: 0 4px 10px 2px rgba(224,224,224,0.43);
|
||||
-moz-box-shadow: 0 4px 10px 2px rgba(224,224,224,0.43);
|
||||
box-shadow: 0 4px 10px 2px rgba(224,224,224,0.43);
|
||||
|
||||
.cookies-actions {
|
||||
display: flex;
|
||||
button {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
button.decline {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
button.accept {
|
||||
background-color: red;
|
||||
border: 0;
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
}
|
41
app/assets/templates/admin/abuses/index.html
Normal file
41
app/assets/templates/admin/abuses/index.html
Normal file
@ -0,0 +1,41 @@
|
||||
<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>{{ 'manage_abuses.abuses_list' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="m-lg">
|
||||
<div class="row m-b-md">
|
||||
<span ng-show="abuses.length === 0" translate>{{ 'manage_abuses.no_reports' }}</span>
|
||||
<ul ng-show="abuses.length > 0">
|
||||
<li class="abuse" ng-repeat="abuse in abuses">
|
||||
<div class="signaled">
|
||||
<a ui-sref="app.public.projects_show({id:abuse.signaled.slug})">{{abuse.signaled.name}}</a>,
|
||||
<span translate>{{ 'manage_abuses.published_by' }}</span>
|
||||
<a ui-sref="app.admin.members_edit({id:abuse.signaled.author.id})">{{abuse.signaled.author.full_name}}</a>,
|
||||
<span translate>{{ 'manage_abuses.at_date' }}</span>
|
||||
<span>{{abuse.signaled.published_at | amDateFormat:'L' }}</span>
|
||||
<button class="btn btn-success" ng-click="confirmProcess(abuse.id)">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="report">
|
||||
<span translate>{{ 'manage_abuses.at_date' }}</span>
|
||||
<span>{{abuse.created_at | amDateFormat:'L' }}</span>,
|
||||
<a href="mailto:{{abuse.email}}">{{abuse.first_name}} {{abuse.last_name}}</a>
|
||||
<span translate>{{ 'manage_abuses.has_reported' }}</span>
|
||||
<cite>{{ abuse.message }}</cite>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
@ -78,7 +78,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="tagAssociate" class="m-t-lg">
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.restrict_this_slot_with_labels_(optional)' }}</p>
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.restrict_this_slot_with_labels_optional' }}</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<ui-select multiple ng-model="availability.tag_ids" class="form-control">
|
||||
|
@ -49,7 +49,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description" translate>{{ 'invoices.description_(optional)' }}</label>
|
||||
<label for="description" translate>{{ 'invoices.description_optional' }}</label>
|
||||
<p translate>{{ 'invoices.will_appear_on_the_refund_invoice' }}</p>
|
||||
<textarea class="form-control m-t-sm" name="description" ng-model="avoir.description"></textarea>
|
||||
</div>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" translate>{{ 'invoices.invoice_#_' }}</span>
|
||||
<span class="input-group-addon" translate>{{ 'invoices.invoice_num_' }}</span>
|
||||
<input type="text" ng-model="searchInvoice.reference" class="form-control" placeholder="" ng-change="handleFilterChange()">
|
||||
</div>
|
||||
</div>
|
||||
@ -62,7 +62,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:5%"></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderInvoice('reference')">{{ 'invoices.invoice_#' | 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:15%"><a href="" ng-click="setOrderInvoice('reference')">{{ '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')">{{ '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>
|
||||
|
||||
@ -83,7 +83,9 @@
|
||||
<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})">{{ invoice.name }} </a>
|
||||
<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" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="!invoice.is_avoir">
|
||||
@ -128,12 +130,12 @@
|
||||
</div>
|
||||
<div class="invoice-buyer-infos">
|
||||
<strong translate>{{ 'invoices.john_smith' }}</strong>
|
||||
<div translate>{{ 'invoices.john_smith@example_com' }}</div>
|
||||
<div translate>{{ 'invoices.john_smith_at_example_com' }}</div>
|
||||
</div>
|
||||
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'invoices.invoice_reference_' | translate }} {{mkReference()}}</div>
|
||||
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'invoices.code_' | translate }} {{invoice.code.model}}</div>
|
||||
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'invoices.code_disabled' }}</div>
|
||||
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'invoices.order_#' | translate }} {{mkNumber()}}</div>
|
||||
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'invoices.order_num' | translate }} {{mkNumber()}}</div>
|
||||
<div class="invoice-date">{{ 'invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
|
||||
<div class="invoice-object">
|
||||
{{ 'invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
|
||||
@ -217,7 +219,7 @@
|
||||
<li ng-click="invoice.reference.help = 'addYear.html'">{{ 'invoices.year' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addMonth.html'">{{ 'invoices.month' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addDay.html'">{{ 'invoices.day' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ 'invoices.#_of_invoice' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ 'invoices.num_of_invoice' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addOnlineInfo.html'">{{ 'invoices.online_sales' | translate }}</li>
|
||||
<%# <li ng-click="invoice.reference.help = 'addWalletInfo.html'">{{ 'invoices.wallet' | translate }}</li> %>
|
||||
<li ng-click="invoice.reference.help = 'addRefundInfo.html'">{{ 'invoices.refund' | translate }}</li>
|
||||
@ -244,60 +246,60 @@
|
||||
|
||||
<script type="text/ng-template" id="addYear.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>YY</strong></td><td translate>{{ 'invoices.2_digits_year_(eg_70)' }}</td></tr>
|
||||
<tr><td><strong>YYYY</strong></td><td translate>{{ 'invoices.4_digits_year_(eg_1970)' }}</td></tr>
|
||||
<tr><td><strong>YY</strong></td><td translate>{{ 'invoices.2_digits_year' }}</td></tr>
|
||||
<tr><td><strong>YYYY</strong></td><td translate>{{ '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>{{ 'invoices.month_number_(eg_1)' }}</td></tr>
|
||||
<tr><td><strong>MM</strong></td><td translate>{{ 'invoices.2_digits_month_number_(eg_01)' }}</td></tr>
|
||||
<tr><td><strong>MMM</strong></td><td translate>{{ 'invoices.3_characters_month_name_(eg_JAN)' }}</td></tr>
|
||||
<tr><td><strong>M</strong></td><td translate>{{ 'invoices.month_number' }}</td></tr>
|
||||
<tr><td><strong>MM</strong></td><td translate>{{ 'invoices.2_digits_month_number' }}</td></tr>
|
||||
<tr><td><strong>MMM</strong></td><td translate>{{ '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>{{ 'invoices.day_in_the_month_(eg_1)' }}</td></tr>
|
||||
<tr><td><strong>DD</strong></td><td translate>{{ 'invoices.2_digits_day_in_the_month_(eg_01)' }}</td></tr>
|
||||
<tr><td><strong>D</strong></td><td translate>{{ 'invoices.day_in_the_month' }}</td></tr>
|
||||
<tr><td><strong>DD</strong></td><td translate>{{ '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>{{ 'invoices.(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day)' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'invoices.(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month)' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'invoices.(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year)' }}</td></tr>
|
||||
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'invoices.n_digits_daily_count_of_invoices' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'invoices.n_digits_monthly_count_of_invoices' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'invoices.n_digits_annual_amount_of_invoices' }}</td></tr>
|
||||
</table>
|
||||
<span class="bottom-notes" translate>{{ '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>{{ 'invoices.(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order)' }}</td></tr>
|
||||
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'invoices.(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day)' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'invoices.(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month)' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'invoices.(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year)' }}</td></tr>
|
||||
<tr><td><strong>nn...nn</strong></td><td translate>{{ 'invoices.n_digits_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'invoices.n_digits_daily_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'invoices.n_digits_monthly_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'invoices.n_digits_annual_amount_of_orders' }}</td></tr>
|
||||
</table>
|
||||
<span class="bottom-notes" translate>{{ '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>{{ 'invoices.add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'invoices.(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe)' | translate }}</td></tr>
|
||||
<tr><td><strong>X[texte]</strong></td><td>{{ 'invoices.add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ '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>{{ 'invoices.add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'invoices.(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet)' | translate }}</td></tr>
|
||||
<tr><td><strong>W[texte]</strong></td><td>{{ 'invoices.add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ '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>{{ 'invoices.add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'invoices.this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ 'invoices.(eg_R[/A]_will_add_/A_to_the_refund_invoices)' | translate }}</td></tr>
|
||||
<tr><td><strong>R[texte]</strong></td><td>{{ 'invoices.add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'invoices.this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ 'invoices.eg_RA_will_add_A_to_the_refund_invoices' | translate }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
@ -345,7 +347,7 @@
|
||||
<li ng-click="invoice.number.help = 'addYear.html'">{{ 'invoices.year' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addMonth.html'">{{ 'invoices.month' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addDay.html'">{{ 'invoices.day' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addOrderNumber.html'">{{ 'invoices.order_#' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addOrderNumber.html'">{{ 'invoices.order_num' | translate }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
|
@ -14,11 +14,11 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" translate>{{ 'trainings' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden" name="user[training_ids][]" value="" />
|
||||
<input type="hidden" name="user[statistic_profile_attributes][training_ids][]" value="" />
|
||||
<ui-select multiple ng-model="user.training_ids" class="form-control">
|
||||
<ui-select-match>
|
||||
<span ng-bind="$item.name"></span>
|
||||
<input type="hidden" name="user[training_ids][]" value="{{$item.id}}" />
|
||||
<input type="hidden" name="user[statistic_profile_attributes][training_ids][]" value="{{$item.id}}" />
|
||||
</ui-select-match>
|
||||
<ui-select-choices ui-disable-choice="t.disabled" repeat="t.id as t in (trainings | filter: $select.search)">
|
||||
<span ng-bind-html="t.name | highlight: $select.search"></span>
|
||||
|
@ -194,7 +194,7 @@
|
||||
<table class="table" ng-if="user.invoices.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:25%" translate>{{ 'invoice_#' }}</th>
|
||||
<th style="width:25%" translate>{{ 'invoice_num' }}</th>
|
||||
<th style="width:25%" translate>{{ 'date' }}</th>
|
||||
<th style="width:25%" translate>{{ 'price' }}</th>
|
||||
<th style="width:25%"></th>
|
||||
|
@ -33,7 +33,7 @@
|
||||
ng-model="plan.group_id"
|
||||
required="required"
|
||||
ng-disabled="method == 'PATCH'">
|
||||
<option value="all" translate>{{ 'plan_form.transversal_(all_groups)' }}</option>
|
||||
<option value="all" translate>{{ 'plan_form.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>
|
||||
</optgroup>
|
||||
|
@ -7,10 +7,14 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'projects_elements_management' }}</h1>
|
||||
<h1 translate>{{ 'project_elements.projects_elements_management' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-ng btn-warning b-2x rounded m-t-sm upper text-sm" ui-sref="app.admin.manage_abuses" role="button" translate>{{ 'project_elements.manage_abuses' }}</a>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -26,7 +30,7 @@
|
||||
<uib-tab heading="{{ 'themes' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/project_elements/themes.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
<uib-tab heading="{{ 'licences' | translate }}">
|
||||
<uib-tab heading="{{ 'project_elements.licences' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/project_elements/licences.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addLicence()" translate>{{ 'add_a_new_licence' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addLicence()" translate>{{ 'project_elements.add_a_new_licence' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addComponent()" translate>{{ 'add_a_material' }}</button>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addComponent()" translate>{{ 'project_elements.add_a_material' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTheme()" translate>{{ 'add_a_new_theme' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTheme()" translate>{{ 'project_elements.add_a_new_theme' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
@ -118,7 +118,7 @@
|
||||
<form class="col-md-6" method="post" action="{{actionUrl.cgv}}" novalidate name="cgvForm" ng-upload="submited(content)" ng-submit="addLoader('cgv')" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="cgv-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.cgv">
|
||||
<label for="tnc_file" class="control-label m-r" translate>{{ 'settings.general_terms_and_conditions_(T&C)' }}</label>
|
||||
<label for="tnc_file" class="control-label m-r" translate>{{ 'settings.general_terms_and_conditions' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(cgvFile.custom_asset_file_attributes.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
@ -142,7 +142,7 @@
|
||||
<form class="col-md-6" method="post" action="{{actionUrl.cgu}}" novalidate name="cguForm" ng-upload="submited(content)" ng-submit="addLoader('cgu')" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="cgu-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.cgu">
|
||||
<label for="tos_file" class="control-label m-r" translate>{{ 'settings.terms_of_service_(TOS)' }}</label>
|
||||
<label for="tos_file" class="control-label m-r" translate>{{ 'settings.terms_of_service' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(cguFile.custom_asset_file_attributes.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
@ -181,7 +181,7 @@
|
||||
<form class="custom-logo-container" method="post" action="{{actionUrl.logo}}" novalidate name="logoForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="logo-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.logo">
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_(white_background)' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_white_background' }}</h3>
|
||||
<div class="custom-logo" style="background-image: url({{customLogo}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!customLogo" class="img-responsive">
|
||||
<img base-sixty-four-image="customLogo" ng-show="customLogo && customLogo.base64">
|
||||
@ -207,7 +207,7 @@
|
||||
<form class="custom-logo-container" method="post" action="{{actionUrl.logoBlack}}" novalidate name="logoBlackForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="logo-black-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.logoBlack">
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_(black_background)' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_black_background' }}</h3>
|
||||
<div class="custom-logo bg-dark" style="background-image: url({{customLogoBlack}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-black-xs" bs-holder ng-show="!customLogoBlack" class="img-responsive">
|
||||
<img base-sixty-four-image="customLogoBlack" ng-show="customLogoBlack && customLogoBlack.base64">
|
||||
@ -344,4 +344,4 @@
|
||||
<div class="col-md-4">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,6 +31,11 @@
|
||||
<uib-tab heading="{{ 'settings.about' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/about.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'settings.privacy.title' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/privacy.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'settings.reservations' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/reservations.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
30
app/assets/templates/admin/settings/privacy.html
Normal file
30
app/assets/templates/admin/settings/privacy.html
Normal file
@ -0,0 +1,30 @@
|
||||
<div class="panel panel-default m-t-md">
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<select class="form-control m-b" ng-options="d.id as d.name for d in privacyDraftsHistory" ng-model="privacyPolicy.version" ng-change="handlePolicyRevisionChange()">
|
||||
<option value="" translate>{{ 'settings.privacy.current_policy' }}</option>
|
||||
</select>
|
||||
<div class="text-justify" ng-model="privacyPolicy.bodyTemp" medium-editor options='{"placeholder": "{{ "settings.input_the_main_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.drag_and_drop_to_insert_images' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="savePrivacyPolicy()" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-2">
|
||||
<div ng-model="privacyDpoSetting.value" medium-editor options='{"placeholder": "{{ "settings.privacy.input_the_dpo" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(privacyDpoSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
@ -65,7 +65,7 @@
|
||||
</div>
|
||||
<div class="row" ng-show="enableMove.value">
|
||||
<form class="col-md-4" name="moveDelayForm">
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'settings.prior_period_hours' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -94,7 +94,7 @@
|
||||
</div>
|
||||
<div class="row" ng-show="enableCancel.value">
|
||||
<form class="col-md-4" name="cancelDelayForm">
|
||||
<label for="cancelDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<label for="cancelDelay" class="control-label m-r" translate>{{ 'settings.prior_period_hours' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -132,7 +132,7 @@
|
||||
</div>
|
||||
<div class="row" ng-show="enableReminder.value">
|
||||
<form class="col-md-4" name="reminderDelayForm">
|
||||
<label for="reminderDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<label for="reminderDelay" class="control-label m-r" translate>{{ 'settings.prior_period_hours' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -172,4 +172,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
12
app/assets/templates/admin/settings/save_policy.html
Normal file
12
app/assets/templates/admin/settings/save_policy.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red" translate>{{ 'settings.privacy.save_or_publish' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p translate>{{ 'settings.privacy.save_or_publish_body' }}</p>
|
||||
<p translate>{{ 'settings.privacy.publish_will_notify' }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="save()" translate>{{ 'save' }}</button>
|
||||
<button class="btn btn-warning" ng-click="publish()" translate>{{ 'settings.privacy.publish' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
@ -263,9 +263,15 @@
|
||||
<tbody>
|
||||
<tr ng-repeat="datum in data">
|
||||
<td>{{formatDate(datum._source.date)}}</td>
|
||||
<td><a href="" ui-sref="app.admin.members_edit({id:datum._source.userId})">{{getUserNameFromId(datum._source.userId)}}</a></td>
|
||||
<td>
|
||||
<a ng-show="datum._source.userId" ui-sref="app.admin.members_edit({id:datum._source.userId})">{{getUserNameFromId(datum._source.userId)}}</a>
|
||||
<span class="text-gray text-italic" ng-hide="datum._source.userId" translate>{{ 'deleted_user' }}</span>
|
||||
</td>
|
||||
<td>{{formatSex(datum._source.gender)}}</td>
|
||||
<td><span ng-if="datum._source.age">{{datum._source.age}} {{ 'years_old' | translate }}</span><span ng-if="!datum._source.age" translate>{{ 'unknown' }}</span></td>
|
||||
<td>
|
||||
<span ng-if="datum._source.age">{{datum._source.age}} {{ 'years_old' | translate }}</span>
|
||||
<span ng-if="!datum._source.age" translate>{{ 'unknown' }}</span>
|
||||
</td>
|
||||
<td>{{formatSubtype(datum._source.subType)}}</td>
|
||||
<td ng-if="!type.active.simple">{{datum._source.stat}}</td>
|
||||
<td ng-repeat="field in selectedIndex.additional_fields">
|
||||
|
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<form role="form" name="subscriptionForm" novalidate>
|
||||
<div class="form-group">
|
||||
<label translate>{{ 'until_(expiration_date)' }}</label>
|
||||
<label translate>{{ 'until_expiration_date' }}</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="subscription[expired_at]"
|
||||
|
@ -6,8 +6,9 @@
|
||||
{{ 'you_can_validate_the_training_of_the_following_members' | translate }}</p>
|
||||
<ul class="list-unstyled" ng-if="availability.reservation_users.length > 0">
|
||||
<li ng-repeat="user in availability.reservation_users">
|
||||
{{user.full_name}}
|
||||
<input type="checkbox" ng-checked="user.is_valid" ng-disabled="user.is_valid" ng-click="toggleSelection(user)" />
|
||||
<label for="{{user.id}}" ng-show="user.id">{{user.full_name}}</label>
|
||||
<span class="text-gray text-italic" ng-hide="user.id" translate>{{ 'deleted_user' }}</span>
|
||||
<input type="checkbox" ng-checked="user.is_valid" ng-disabled="user.is_valid || !user.id" ng-click="toggleSelection(user)" id="{{user.id}}" />
|
||||
</li>
|
||||
</ul>
|
||||
<p ng-if="availability.reservation_users.length == 0" translate>{{ 'no_reservation' }}</p>
|
||||
@ -15,4 +16,4 @@
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" ng-disabled="usersToValid.length == 0" translate>{{ 'validate_the_trainings' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,72 +13,79 @@
|
||||
</span>
|
||||
<div class="font-sbold m-t-sm">{{user.name}}</div>
|
||||
<div>{{user.email}}</div>
|
||||
<div class="text-xs" ng-if="user.last_sign_in_at"><i>{{ 'last_activity_on_' | translate }} {{user.last_sign_in_at | amDateFormat: 'LL'}}</i></div>
|
||||
<div class="text-xs" ng-if="user.last_sign_in_at"><i>{{ 'edit_profile.last_activity_on_' | translate }} {{user.last_sign_in_at | amDateFormat: 'LL'}}</i></div>
|
||||
</div>
|
||||
<div class="widget-content no-bg b-b auto wrapper">
|
||||
<div class="m-b-md">
|
||||
<h3 class="text-u-c" translate>{{ 'group' }}</h3>
|
||||
<div ng-show="!group.change">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{getUserGroup().name}}</span>
|
||||
</uib-alert>
|
||||
<button class="btn text-black btn-warning-full btn-sm m-t-n-sm"
|
||||
ng-click="group.change = !group.change"
|
||||
ng-hide="user.subscribed_plan.name || user.role === 'admin'"
|
||||
translate>
|
||||
{{ 'i_want_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="userGroup"></select>
|
||||
<button class="btn btn-success m-t" ng-click="selectGroup()">Changer mon groupe</button>
|
||||
</div>
|
||||
<h3 class="text-u-c" translate>{{ 'edit_profile.group' }}</h3>
|
||||
<div ng-show="!group.change">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{getUserGroup().name}}</span>
|
||||
</uib-alert>
|
||||
<button class="btn text-black btn-warning-full btn-sm m-t-n-sm"
|
||||
ng-click="group.change = !group.change"
|
||||
ng-hide="user.subscribed_plan.name || user.role === 'admin'"
|
||||
translate>
|
||||
{{ 'edit_profile.i_want_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="userGroup"></select>
|
||||
<button class="btn btn-success m-t" ng-click="selectGroup()">Changer mon groupe</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-hide="fablabWithoutPlans">
|
||||
<h3 class="text-u-c" translate>{{ 'subscription' }}</h3>
|
||||
<div ng-show="user.subscribed_plan">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{ user.subscribed_plan | humanReadablePlanName }}</span>
|
||||
<div class="font-sbold" ng-if="user.subscription">{{ 'your_subscription_expires_on_' | translate }} {{user.subscription.expired_at | amDateFormat: 'LL'}}</div>
|
||||
</uib-alert>
|
||||
<h3 class="text-u-c" translate>{{ 'edit_profile.subscription' }}</h3>
|
||||
<div ng-show="user.subscribed_plan">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{ user.subscribed_plan | humanReadablePlanName }}</span>
|
||||
<div class="font-sbold" ng-if="user.subscription">{{ 'edit_profile.your_subscription_expires_on_' | translate }} {{user.subscription.expired_at | amDateFormat: 'LL'}}</div>
|
||||
</uib-alert>
|
||||
|
||||
</div>
|
||||
<div ng-show="!user.subscribed_plan.name">{{ 'no_subscriptions' | translate }} <br><a class="btn text-black btn-warning-full btn-sm m-t-xs" ui-sref="app.public.plans" translate>{{ 'i_want_to_subscribe' }}</a></div>
|
||||
</div>
|
||||
<div ng-show="!user.subscribed_plan.name">{{ 'edit_profile.no_subscriptions' | translate }} <br><a class="btn text-black btn-warning-full btn-sm m-t-xs" ui-sref="app.public.plans" translate>{{ 'edit_profile.i_want_to_subscribe' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'trainings' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.training_reservations.length > 0 || user.trainings.length > 0">
|
||||
<li ng-repeat="r in user.training_reservations | trainingReservationsFilter:'future'">
|
||||
{{r.reservable.name}} - {{ 'to_come' | translate }}
|
||||
</li>
|
||||
<li ng-repeat="t in user.trainings">
|
||||
{{t.name}} - {{ 'approved' | translate }}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.training_reservations.length == 0 && user.trainings.length == 0" translate>{{ 'no_trainings' }}</div>
|
||||
<h3 class="text-u-c" translate>{{ 'edit_profile.trainings' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.training_reservations.length > 0 || user.trainings.length > 0">
|
||||
<li ng-repeat="r in user.training_reservations | trainingReservationsFilter:'future'">
|
||||
{{r.reservable.name}} - {{ 'edit_profile.to_come' | translate }}
|
||||
</li>
|
||||
<li ng-repeat="t in user.trainings">
|
||||
{{t.name}} - {{ 'edit_profile.approved' | translate }}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.training_reservations.length == 0 && user.trainings.length == 0" translate>{{ 'edit_profile.no_trainings' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'projects' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.all_projects.length > 0">
|
||||
<li ng-repeat="p in user.all_projects">
|
||||
{{p.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.all_projects.length == 0" translate>{{ 'no_projects' }}</div>
|
||||
<h3 class="text-u-c" translate>{{ 'edit_profile.projects' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.all_projects.length > 0">
|
||||
<li ng-repeat="p in user.all_projects">
|
||||
{{p.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.all_projects.length == 0" translate>{{ 'edit_profile.no_projects' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'labels' }}</h3>
|
||||
<span ng-if="user.tags.length > 0" ng-repeat="t in user.tags">
|
||||
<span class='label label-success text-white'>{{t.name}}</span>
|
||||
</span>
|
||||
<div ng-if="user.tags.length == 0" translate>{{ 'no_labels' }}</div>
|
||||
<h3 class="text-u-c" translate>{{ 'edit_profile.labels' }}</h3>
|
||||
<span ng-if="user.tags.length > 0" ng-repeat="t in user.tags">
|
||||
<span class='label label-success text-white'>{{t.name}}</span>
|
||||
</span>
|
||||
<div ng-if="user.tags.length == 0" translate>{{ 'edit_profile.no_labels' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-content no-bg text-center auto wrapper">
|
||||
<button class="btn text-white btn-danger btn-sm" ng-click="deleteUser(user)"><i class="fa fa-warning"></i> {{ 'delete_my_account' | translate }}</button>
|
||||
<div class="widget-content no-bg b-b auto wrapper">
|
||||
<h3 class="text-u-c" translate>{{ 'edit_profile.cookies' }}</h3>
|
||||
<div ng-show="cookiesStatus === 'accept'" translate>{{ 'edit_profile.cookies_accepted' }}</div>
|
||||
<div ng-show="cookiesStatus === 'decline'" translate>{{ 'edit_profile.cookies_declined' }}</div>
|
||||
<div ng-hide="cookiesStatus" translate>{{ 'edit_profile.cookies_unset' }}</div>
|
||||
<button ng-click="resetCookies()" ng-show="cookiesStatus" class="btn text-black btn-warning-full btn-sm m-t-xs" translate>{{ 'edit_profile.reset_cookies' }}</button>
|
||||
</div>
|
||||
<div class="widget-content no-bg text-center auto wrapper" ng-hide="isSuperAdmin">
|
||||
<button class="btn text-white btn-danger btn-sm" ng-click="deleteUser(user)"><i class="fa fa-warning m-r-xs"></i> {{ 'edit_profile.delete_my_account' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -87,7 +94,7 @@
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b">
|
||||
<h1 class="red text-u-c" translate>{{ 'edit_my_profile' }}</h1>
|
||||
<h1 class="red text-u-c" translate>{{ 'edit_profile.edit_my_profile' }}</h1>
|
||||
</div>
|
||||
<form role="form" name="userForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
|
||||
<div class="widget-content no-bg auto">
|
||||
@ -101,13 +108,13 @@
|
||||
<div class="panel-body row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-href="{{activeProvider.link_to_sso_profile}}" target="_blank">
|
||||
<i class="fa fa-edit"></i> {{ 'change_my_data' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'edit_profile.change_my_data' | translate }}
|
||||
</a>
|
||||
<p>{{ 'once_your_data_are_up_to_date_' | translate }} <strong translate>{{ '_click_on_the_synchronization_button_opposite_' }}</strong> {{ 'or' | translate}} <strong translate>{{ '_disconnect_then_reconnect_' }}</strong> {{ '_for_your_changes_to_take_effect' | translate }}</p>
|
||||
<p>{{ 'edit_profile.once_your_data_are_up_to_date_' | translate }} <strong translate>{{ 'edit_profile._click_on_the_synchronization_button_opposite_' }}</strong> {{ 'edit_profile.or' | translate}} <strong translate>{{ 'edit_profile._disconnect_then_reconnect_' }}</strong> {{ 'edit_profile._for_your_changes_to_take_effect' | translate }}</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-click="syncProfile()">
|
||||
<i class="fa fa-refresh"></i> {{ 'sync_my_profile' | translate }}
|
||||
<i class="fa fa-refresh"></i> {{ 'edit_profile.sync_my_profile' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -119,7 +126,7 @@
|
||||
</section>
|
||||
</div>
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="userForm.$invalid"/>
|
||||
<input type="submit" value="{{ 'edit_profile.confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="userForm.$invalid"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -78,9 +78,9 @@
|
||||
|
||||
</div> <!-- ./panel-body -->
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit"
|
||||
ng-value="submitName"
|
||||
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
|
||||
<input type="submit"
|
||||
ng-value="submitName"
|
||||
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
|
||||
ng-disabled="eventForm.$invalid || event.category_id === null"/>
|
||||
</div>
|
||||
</section>
|
||||
@ -246,7 +246,7 @@
|
||||
<input ng-model="event.amount" type="number" name="event[amount]" class="form-control" id="event_amount" required>
|
||||
<div class="input-group-addon">{{currencySymbol}}</div>
|
||||
</div>
|
||||
<span class="help-block" translate>{{ '0_=_free' }}</span>
|
||||
<span class="help-block" translate>{{ '0_equal_free' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-repeat="price in event.prices" ng-show="!price._destroy">
|
||||
|
@ -152,7 +152,7 @@
|
||||
{{ 'you_can_find_your_reservation_s_details_on_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'dashboard' }}</a>
|
||||
</div>
|
||||
<div class="well well-warning m-t-sm" ng-if="reservations && !reserve.toReserve" ng-repeat="reservation in reservations">
|
||||
<div class="font-sbold text-u-c text-sm">{{ 'you_booked_(DATE)' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}</div>
|
||||
<div class="font-sbold text-u-c text-sm">{{ 'you_booked_DATE' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}</div>
|
||||
<div class="font-sbold text-sm" ng-if="reservation.nb_reserve_places > 0">{{ 'full_price_' | translate }} {{reservation.nb_reserve_places}} {{ 'ticket' | translate:{NUMBER:reservation.nb_reserve_places}:"messageformat" }}</div>
|
||||
<div class="font-sbold text-sm" ng-repeat="ticket in reservation.tickets">
|
||||
{{ticket.event_price_category.price_category.name}} : {{ticket.booked}} {{ 'ticket' | translate:{NUMBER:ticket.booked}:"messageformat" }}
|
||||
|
@ -91,7 +91,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label class="col-sm-2 control-label" translate>{{ 'machine_form.attached_files_(pdf)' }}</label>
|
||||
<label class="col-sm-2 control-label" translate>{{ 'machine_form.attached_files_pdf' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div ng-repeat="file in machine.machine_files_attributes" ng-show="!file._destroy">
|
||||
<input type="hidden" ng-model="file.id" name="machine[machine_files_attributes][][id]" ng-value="file.id" />
|
||||
|
@ -7,13 +7,13 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'the_fablab_projects' }}</h1>
|
||||
<h1 translate>{{ 'projects_list.the_fablab_projects' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin','member'])">
|
||||
<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.logged.projects_new" role="button" translate>{{ 'add_a_project' }}</a>
|
||||
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.logged.projects_new" role="button" translate>{{ 'projects_list.add_a_project' }}</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@ -23,10 +23,10 @@
|
||||
<section class="m-lg">
|
||||
<div class="row m-b-md">
|
||||
<div class="col-md-12 m-b">
|
||||
<a href="javascript:void(0);" class="text-sm pull-right" name="button" ng-click="resetFiltersAndTriggerSearch()" ng-show="!openlab.searchOverWholeNetwork"><i class="fa fa-refresh"></i> {{ 'reset_all_filters' | translate }}</a>
|
||||
<a href="javascript:void(0);" class="text-sm pull-right" name="button" ng-click="resetFiltersAndTriggerSearch()" ng-show="!openlab.searchOverWholeNetwork"><i class="fa fa-refresh"></i> {{ 'projects_list.reset_all_filters' | translate }}</a>
|
||||
|
||||
<span ng-if="openlab.projectsActive" uib-tooltip="{{ 'tooltip_openlab_projects_switch' | translate }}" tooltip-trigger="mouseenter">
|
||||
<label for="searchOverWholeNetwork" class="control-label m-r text-sm" translate>{{ 'search_over_the_whole_network' }}</label>
|
||||
<span ng-if="openlab.projectsActive" uib-tooltip="{{ 'projects_list.tooltip_openlab_projects_switch' | translate }}" tooltip-trigger="mouseenter">
|
||||
<label for="searchOverWholeNetwork" class="control-label m-r text-sm" translate>{{ 'projects_list.search_over_the_whole_network' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="openlab.searchOverWholeNetwork"
|
||||
type="checkbox"
|
||||
@ -44,7 +44,7 @@
|
||||
<div class="input-group-addon"><i class="fa fa-search"></i></div>
|
||||
<input type="search" class="form-control" placeholder="Mots-clés" ng-model="search.q"/>
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-warning" translate>{{ 'search' }}</button>
|
||||
<button type="submit" class="btn btn-warning" translate>{{ 'projects_list.search' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -53,27 +53,27 @@
|
||||
<span ng-if="!openlab.searchOverWholeNetwork">
|
||||
<div class="col-md-3 m-b" ng-show="isAuthenticated()">
|
||||
<select ng-model="search.from" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control">
|
||||
<option value="" translate>{{ 'all_projects' }}</option>
|
||||
<option value="mine" translate>{{ 'my_projects' }}</option>
|
||||
<option value="collaboration" translate>{{ 'projects_to_whom_i_take_part_in' }}</option>
|
||||
<option value="" translate>{{ 'projects_list.all_projects' }}</option>
|
||||
<option value="mine" translate>{{ 'projects_list.my_projects' }}</option>
|
||||
<option value="collaboration" translate>{{ 'projects_list.projects_to_whom_i_take_part_in' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 m-b">
|
||||
<select ng-model="search.machine_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="m.id as m.name for m in machines">
|
||||
<option value="" translate>{{ 'all_machines' }}</option>
|
||||
<option value="" translate>{{ 'projects_list.all_machines' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 m-b">
|
||||
<select ng-model="search.theme_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in themes">
|
||||
<option value="" translate>{{ 'all_themes' }}</option>
|
||||
<option value="" translate>{{ 'projects_list.all_themes' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 m-b">
|
||||
<select ng-model="search.component_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in components">
|
||||
<option value="" translate>{{ 'all_materials' }}</option>
|
||||
<option value="" translate>{{ 'projects_list.all_materials' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</span>
|
||||
@ -81,7 +81,7 @@
|
||||
|
||||
|
||||
<div class="row">
|
||||
<span class="col-md-12" ng-show="projects && (projects.length == 0)"> {{ 'project_search_result_is_empty' | translate }} </span>
|
||||
<span class="col-md-12" ng-show="projects && (projects.length == 0)"> {{ 'projects_list.project_search_result_is_empty' | translate }} </span>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3" ng-repeat="project in projects" ng-click="showProject(project)">
|
||||
|
||||
<div class="card card-project">
|
||||
@ -99,7 +99,7 @@
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<span class="badge" ng-if="project.state == 'draft'" translate>{{ 'rough_draft' }}</span>
|
||||
<span class="badge" ng-if="project.state == 'draft'" translate>{{ 'projects_list.rough_draft' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-overlay">
|
||||
@ -119,7 +119,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<a class="btn btn-warning" ng-click="loadMore()" ng-if="projectsPagination.hasNextPage()" translate>{{ 'load_next_projects' }}</a>
|
||||
<a class="btn btn-warning" ng-click="loadMore()" ng-if="projectsPagination.hasNextPage()" translate>{{ 'projects_list.load_next_projects' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -76,7 +76,12 @@
|
||||
<div class="thumb-lg m-b-xs">
|
||||
<fab-user-avatar ng-model="project.author.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
|
||||
</div>
|
||||
<div><a class="text-sm font-sbold" ui-sref="app.logged.members_show({id: project.author.slug})"><i> {{ 'by_name' | translate:{NAME:project.author.first_name} }}</i></a></div>
|
||||
<div>
|
||||
<a ng-show="project.author_id" class="text-sm font-sbold" ui-sref="app.logged.members_show({id: project.author.slug})">
|
||||
<i> {{ 'by_name' | translate:{NAME:project.author.first_name} }}</i>
|
||||
</a>
|
||||
<span ng-hide="project.author_id" class="text-sm font-sbold text-gray" translate>{{ 'deleted_user' }}</span>
|
||||
</div>
|
||||
<small class="text-xs m-b"><i>{{ 'posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}}</i></small>
|
||||
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
<div class="row m-t">
|
||||
<div class="col-sm-offset-3 col-sm-6">
|
||||
<div class="form-group" ng-class="{'has-error': adminForm['admin[profile_attributes][gender]'].$dirty && adminForm['admin[profile_attributes][gender]'].$invalid}">
|
||||
<div class="form-group" ng-class="{'has-error': adminForm['admin[statistic_profile_attributes][gender]'].$dirty && adminForm['admin[statistic_profile_attributes][gender]'].$invalid}">
|
||||
<label class="checkbox-inline btn btn-default">
|
||||
<input type="radio"
|
||||
name="admin[profile_attributes][gender]"
|
||||
ng-model="admin.profile_attributes.gender"
|
||||
name="admin[statistic_profile_attributes][gender]"
|
||||
ng-model="admin.statistic_profile_attributes.gender"
|
||||
ng-value="true"
|
||||
required/>
|
||||
<i class="fa fa-male m-l-sm"></i> {{ 'man' | translate }}
|
||||
</label>
|
||||
<label class="checkbox-inline btn btn-default">
|
||||
<input type="radio"
|
||||
name="admin[profile_attributes][gender]"
|
||||
ng-model="admin.profile_attributes.gender"
|
||||
name="admin[statistic_profile_attributes][gender]"
|
||||
ng-model="admin.statistic_profile_attributes.gender"
|
||||
ng-value="false"/>
|
||||
<i class="fa fa-female m-l-sm"></i> {{ 'woman' | translate }}
|
||||
</label>
|
||||
@ -73,13 +73,13 @@
|
||||
<span class="help-block" ng-show="adminForm['admin[email]'].$dirty && adminForm['admin[email]'].$error.required" translate>{{ 'email_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': adminForm['admin[profile_attributes][birthday]'].$dirty && adminForm['admin[profile_attributes][birthday]'].$invalid}">
|
||||
<div class="form-group" ng-class="{'has-error': adminForm['admin[statistic_profile_attributes][birthday]'].$dirty && adminForm['admin[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="admin.profile_attributes.birthday"
|
||||
ng-model="admin.statistic_profile_attributes.birthday"
|
||||
uib-datepicker-popup="{{datePicker.format}}"
|
||||
datepicker-options="datePicker.options"
|
||||
is-open="datePicker.opened"
|
||||
@ -87,8 +87,8 @@
|
||||
ng-click="openDatePicker($event)"
|
||||
/>
|
||||
<input type="hidden"
|
||||
name="admin[profile_attributes][birthday]"
|
||||
value="{{admin.profile_attributes.birthday | toIsoDate}}" />
|
||||
name="admin[statistic_profile_attributes][birthday]"
|
||||
value="{{admin.statistic_profile_attributes.birthday | toIsoDate}}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -96,11 +96,11 @@
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-map-marker"></i> </span>
|
||||
<input type="hidden"
|
||||
name="admin[profile_attributes][address_attributes][id]"
|
||||
ng-value="admin.profile_attributes.address.id" />
|
||||
<input ng-model="admin.profile_attributes.address_attributes.address"
|
||||
name="admin[invoicing_profile_attributes][address_attributes][id]"
|
||||
ng-value="admin.invoicing_profile_attributes.address.id" />
|
||||
<input ng-model="admin.invoicing_profile_attributes.address_attributes.address"
|
||||
type="text"
|
||||
name="admin[profile_attributes][address_attributes][address]"
|
||||
name="admin[invoicing_profile_attributes][address_attributes][address]"
|
||||
class="form-control"
|
||||
id="user_address"
|
||||
placeholder="{{ 'address' | translate }}">
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
<input name="_method" type="hidden" ng-value="method">
|
||||
<input name="user[profile_attributes][id]" type="hidden" ng-value="user.profile.id">
|
||||
<input name="user[invoicing_profile_attributes][id]" type="hidden" ng-value="user.invoicing_profile.id">
|
||||
<input name="user[statistic_profile_attributes][id]" type="hidden" ng-value="user.statistic_profile.id">
|
||||
|
||||
<div class="row m-t">
|
||||
<div class="col-sm-3 col-sm-offset-1">
|
||||
@ -37,33 +39,33 @@
|
||||
</div>
|
||||
<div class="col-sm-offset-1 col-sm-6">
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$invalid}">
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[statistic_profile_attributes][gender]'].$dirty && userForm['user[statistic_profile_attributes][gender]'].$invalid}">
|
||||
<label class="checkbox-inline btn btn-default">
|
||||
<input type="radio"
|
||||
name="user[profile_attributes][gender]"
|
||||
ng-model="user.profile.gender"
|
||||
name="user[statistic_profile_attributes][gender]"
|
||||
ng-model="user.statistic_profile.gender"
|
||||
value="true"
|
||||
ng-disabled="preventField['profile.gender'] && user.profile.gender && !userForm['user[profile_attributes][gender]'].$dirty"
|
||||
ng-disabled="preventField['profile.gender'] && user.statistic_profile.gender && !userForm['user[statistic_profile_attributes][gender]'].$dirty"
|
||||
required/>
|
||||
<i class="fa fa-male m-l-sm"></i> {{ 'man' | translate }}
|
||||
</label>
|
||||
<label class="checkbox-inline btn btn-default">
|
||||
<input type="radio"
|
||||
name="user[profile_attributes][gender]"
|
||||
ng-model="user.profile.gender"
|
||||
name="user[statistic_profile_attributes][gender]"
|
||||
ng-model="user.statistic_profile.gender"
|
||||
value="false"
|
||||
ng-disabled="preventField['profile.gender'] && user.profile.gender && !userForm['user[profile_attributes][gender]'].$dirty"/>
|
||||
ng-disabled="preventField['profile.gender'] && user.statistic_profile.gender && !userForm['user[statistic_profile_attributes][gender]'].$dirty"/>
|
||||
<i class="fa fa-female m-l-sm"></i> {{ 'woman' | translate }}
|
||||
</label>
|
||||
<span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent m-l-xs help-cursor" title="{{ 'used_for_statistics' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
|
||||
<span class="help-block" ng-show="userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$error.required" translate>{{ 'gender_is_required' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[statistic_profile_attributes][gender]'].$dirty && userForm['user[statistic_profile_attributes][gender]'].$error.required" translate>{{ 'gender_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[username]'].$dirty && userForm['user[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 class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
<input type="text"
|
||||
name="user[username]"
|
||||
@ -81,7 +83,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][last_name]'].$dirty && userForm['user[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>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][last_name]"
|
||||
ng-model="user.profile.last_name"
|
||||
@ -96,7 +98,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][first_name]'].$dirty && userForm['user[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>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][first_name]"
|
||||
ng-model="user.profile.first_name"
|
||||
@ -111,7 +113,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[email]'].$dirty && userForm['user[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>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-envelope"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="email"
|
||||
name="user[email]"
|
||||
ng-model="user.email"
|
||||
@ -144,7 +146,7 @@
|
||||
required/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.required" translate>{{ 'password_is_required' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.minlength" translate>{{ 'password_is_too_short_(minimum_8_characters)' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.minlength" translate>{{ 'password_is_too_short' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$invalid}" ng-if="password.change">
|
||||
@ -161,84 +163,84 @@
|
||||
match="user.password"/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$error.required" translate>{{ 'confirmation_of_password_is_required' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$error.minlength" translate>{{ 'confirmation_of_password_is_too_short_(minimum_8_characters)' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$error.minlength" translate>{{ 'confirmation_of_password_is_too_short' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[password_confirmation]'].$error.match" translate>{{ 'confirmation_mismatch_with_password' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="user.profile.organization" ng-class="{'has-error': userForm['user[profile_attributes][organization_attributes][name]'].$dirty && userForm['user[profile_attributes][organization_attributes][name]'].$invalid}">
|
||||
<div class="form-group" ng-if="user.invoicing_profile.organization" ng-class="{'has-error': userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-building-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-building-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="hidden"
|
||||
name="user[profile_attributes][organization_attributes][id]"
|
||||
ng-value="user.profile.organization.id" />
|
||||
name="user[invoicing_profile_attributes][organization_attributes][id]"
|
||||
ng-value="user.invoicing_profile.organization.id" />
|
||||
<input type="text"
|
||||
name="user[profile_attributes][organization_attributes][name]"
|
||||
ng-model="user.profile.organization.name"
|
||||
name="user[invoicing_profile_attributes][organization_attributes][name]"
|
||||
ng-model="user.invoicing_profile.organization.name"
|
||||
class="form-control"
|
||||
placeholder="{{ 'organization_name' | translate }}"
|
||||
ng-required="user.profile.organization"
|
||||
ng-disabled="preventField['profile.organization_name'] && user.profile.organization.name && !userForm['user[profile_attributes][organization_attributes][name]'].$dirty">
|
||||
ng-required="user.invoicing_profile.organization"
|
||||
ng-disabled="preventField['profile.organization_name'] && user.invoicing_profile.organization.name && !userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$dirty">
|
||||
</div>
|
||||
<span class="help-block" ng-show="userForm['user[profile_attributes][organization_attributes][name]'].$dirty && userForm['user[profile_attributes][organization_attributes][name]'].$error.required" translate>{{ 'organization_name_is_required' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[invoicing_][organization_attributes][name]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][name]'].$error.required" translate>{{ 'organization_name_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="user.profile.organization" ng-class="{'has-error': userForm['user[profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[profile_attributes][organization_attributes][address_attributes][address]'].$invalid}">
|
||||
<div class="form-group" ng-if="user.invoicing_profile.organization" ng-class="{'has-error': userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-map-marker"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-map-marker"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="hidden"
|
||||
name="user[profile_attributes][organization_attributes][address_attributes][id]"
|
||||
ng-value="user.profile.organization.address.id" />
|
||||
name="user[invoicing_profile_attributes][organization_attributes][address_attributes][id]"
|
||||
ng-value="user.invoicing_profile.organization.address.id" />
|
||||
<input type="text"
|
||||
name="user[profile_attributes][organization_attributes][address_attributes][address]"
|
||||
ng-model="user.profile.organization.address.address"
|
||||
name="user[invoicing_profile_attributes][organization_attributes][address_attributes][address]"
|
||||
ng-model="user.invoicing_profile.organization.address.address"
|
||||
class="form-control"
|
||||
placeholder="{{ 'organization_address' | translate }}"
|
||||
ng-required="user.profile.organization"
|
||||
ng-disabled="preventField['profile.organization_address'] && user.profile.organization.address.address && !userForm['user[profile_attributes][organization_attributes][address_attributes][address]'].$dirty">
|
||||
ng-required="user.invoicing_profile.organization"
|
||||
ng-disabled="preventField['profile.organization_address'] && user.invoicing_profile.organization.address.address && !userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty">
|
||||
</div>
|
||||
<span class="help-block" ng-show="userForm['user[profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[profile_attributes][organization_attributes][address_attributes][address]'].$error.required" translate>{{ 'organization_address_is_required' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$error.required" translate>{{ 'organization_address_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][birthday]'].$dirty && userForm['user[profile_attributes][birthday]'].$invalid}">
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[statistic_profile_attributes][birthday]'].$dirty && userForm['user[statistic_profile_attributes][birthday]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_statistics' | translate }}"><i class="fa fa-calendar-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
id="user_birthday"
|
||||
class="form-control"
|
||||
ng-model="user.profile.birthday"
|
||||
ng-model="user.statistic_profile.birthday"
|
||||
uib-datepicker-popup="{{datePicker.format}}"
|
||||
datepicker-options="datePicker.options"
|
||||
is-open="datePicker.opened"
|
||||
placeholder="{{ 'date_of_birth' | translate }}"
|
||||
ng-click="openDatePicker($event)"
|
||||
ng-disabled="preventField['profile.birthday'] && user.profile.birthday && !userForm['user[profile_attributes][birthday]'].$dirty"
|
||||
ng-disabled="preventField['profile.birthday'] && user.statistic_profile.birthday && !userForm['user[statistic_profile_attributes][birthday]'].$dirty"
|
||||
required/>
|
||||
<input type="hidden"
|
||||
name="user[profile_attributes][birthday]"
|
||||
value="{{user.profile.birthday | toIsoDate}}" />
|
||||
name="user[statistic_profile_attributes][birthday]"
|
||||
value="{{user.statistic_profile.birthday | toIsoDate}}" />
|
||||
</div>
|
||||
<span class="help-block" ng-show="userForm['user[profile_attributes][birthday]'].$dirty && userForm['user[profile_attributes][birthday]'].$error.required" translate>{{ 'date_of_birth_is_required' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[statistic_profile_attributes][birthday]'].$dirty && userForm['user[statistic_profile_attributes][birthday]'].$error.required" translate>{{ 'date_of_birth_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-map-marker"></i> </span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-map-marker"></i> </span>
|
||||
<input type="hidden"
|
||||
name="user[profile_attributes][address_attributes][id]"
|
||||
ng-value="user.profile.address.id" />
|
||||
name="user[invoicing_profile_attributes][address_attributes][id]"
|
||||
ng-value="user.invoicing_profile.address.id" />
|
||||
<input type="text"
|
||||
name="user[profile_attributes][address_attributes][address]"
|
||||
ng-model="user.profile.address.address"
|
||||
name="user[invoicing_profile_attributes][address_attributes][address]"
|
||||
ng-model="user.invoicing_profile.address.address"
|
||||
class="form-control"
|
||||
id="user_address"
|
||||
ng-disabled="preventField['profile.address'] && user.profile.address.address && !userForm['user[profile_attributes][address_attributes][address]'].$dirty"
|
||||
ng-disabled="preventField['profile.address'] && user.invoicing_profile.address.address && !userForm['user[invoicing_profile_attributes][address_attributes][address]'].$dirty"
|
||||
placeholder="{{ 'address' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][phone]'].$dirty && userForm['user[profile_attributes][phone]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-phone"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_reservation' | translate }}"><i class="fa fa-phone"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][phone]"
|
||||
ng-model="user.profile.phone"
|
||||
@ -253,7 +255,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][website]'].$dirty && userForm['user[profile_attributes][website]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-globe"></i> </span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-globe"></i> </span>
|
||||
<input type="url"
|
||||
name="user[profile_attributes][website]"
|
||||
ng-model="user.profile.website"
|
||||
@ -267,7 +269,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][job]'].$dirty && userForm['user[profile_attributes][job]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-briefcase"></i> </span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-briefcase"></i> </span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][job]"
|
||||
ng-model="user.profile.job"
|
||||
@ -279,7 +281,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user_interest" translate>{{ 'interests' }}</label>
|
||||
<label for="user_interest" class="help-cursor" title="{{ 'used_for_profile' | translate }}" translate>{{ 'interests' }}</label>
|
||||
<textarea name="user[profile_attributes][interest]"
|
||||
ng-model="user.profile.interest"
|
||||
rows="5"
|
||||
@ -290,7 +292,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user_software_mastered" translate>{{ 'CAD_softwares_mastered' }}</label>
|
||||
<label for="user_software_mastered" class="help-cursor" title="{{ 'used_for_profile' | translate }}" translate>{{ 'CAD_softwares_mastered' }}</label>
|
||||
<textarea name="user[profile_attributes][software_mastered]"
|
||||
ng-model="user.profile.software_mastered"
|
||||
rows="5"
|
||||
@ -302,7 +304,7 @@
|
||||
|
||||
<!-- allow contact-->
|
||||
<div class="form-group">
|
||||
<label for="allowContact" translate>{{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' }}</label>
|
||||
<label for="allowContact" class="help-cursor" title="{{ 'public_profile' | translate }}" translate>{{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="user.is_allow_contact"
|
||||
id="allowContact"
|
||||
@ -331,7 +333,7 @@
|
||||
<div id="social" ng-init="social={}">
|
||||
<div class="form-group" ng-show="social.facebook || user.profile.facebook" ng-class="{'has-error': userForm['user[profile_attributes][facebook]'].$dirty && userForm['user[profile_attributes][facebook]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-facebook"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-facebook"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][facebook]"
|
||||
ng-model="user.profile.facebook"
|
||||
@ -346,7 +348,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.twitter || user.profile.twitter" ng-class="{'has-error': userForm['user[profile_attributes][twitter]'].$dirty && userForm['user[profile_attributes][twitter]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-twitter"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-twitter"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][twitter]"
|
||||
ng-model="user.profile.twitter"
|
||||
@ -361,7 +363,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.google_plus || user.profile.google_plus" ng-class="{'has-error': userForm['user[profile_attributes][google_plus]'].$dirty && userForm['user[profile_attributes][google_plus]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-google-plus"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-google-plus"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][google_plus]"
|
||||
ng-model="user.profile.google_plus"
|
||||
@ -376,7 +378,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.viadeo || user.profile.viadeo" ng-class="{'has-error': userForm['user[profile_attributes][viadeo]'].$dirty && userForm['user[profile_attributes][viadeo]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-viadeo"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-viadeo"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][viadeo]"
|
||||
ng-model="user.profile.viadeo"
|
||||
@ -391,7 +393,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.linkedin || user.profile.linkedin" ng-class="{'has-error': userForm['user[profile_attributes][linkedin]'].$dirty && userForm['user[profile_attributes][linkedin]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-linkedin"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-linkedin"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][linkedin]"
|
||||
ng-model="user.profile.linkedin"
|
||||
@ -406,7 +408,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.instagram || user.profile.instragram" ng-class="{'has-error': userForm['user[profile_attributes][instagram]'].$dirty && userForm['user[profile_attributes][instagram]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-instagram"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-instagram"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][instagram]"
|
||||
ng-model="user.profile.instagram"
|
||||
@ -421,7 +423,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.youtube || user.profile.youtube" ng-class="{'has-error': userForm['user[profile_attributes][youtube]'].$dirty && userForm['user[profile_attributes][youtube]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-youtube"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-youtube"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][youtube]"
|
||||
ng-model="user.profile.youtube"
|
||||
@ -436,7 +438,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.vimeo || user.profile.vimeo" ng-class="{'has-error': userForm['user[profile_attributes][vimeo]'].$dirty && userForm['user[profile_attributes][vimeo]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-vimeo"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-vimeo"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][vimeo]"
|
||||
ng-model="user.profile.vimeo"
|
||||
@ -451,7 +453,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.dailymotion || user.profile.dailymotion" ng-class="{'has-error': userForm['user[profile_attributes][dailymotion]'].$dirty && userForm['user[profile_attributes][dailymotion]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><img src="<%= asset_path('social/dailymotion.png') %>" alt="d" class="fa-img"/></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><img src="<%= asset_path('social/dailymotion.png') %>" alt="d" class="fa-img"/></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][dailymotion]"
|
||||
ng-model="user.profile.dailymotion"
|
||||
@ -467,7 +469,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.github || user.profile.github" ng-class="{'has-error': userForm['user[profile_attributes][github]'].$dirty && userForm['user[profile_attributes][github]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-github"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-github"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][github]"
|
||||
ng-model="user.profile.github"
|
||||
@ -482,7 +484,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.echosciences || user.profile.echosciences" ng-class="{'has-error': userForm['user[profile_attributes][echosciences]'].$dirty && userForm['user[profile_attributes][echosciences]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><img src="<%= asset_path('social/echosciences.png') %>" alt="d" class="fa-img"/></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><img src="<%= asset_path('social/echosciences.png') %>" alt="d" class="fa-img"/></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][echosciences]"
|
||||
ng-model="user.profile.echosciences"
|
||||
@ -497,7 +499,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.pinterest || user.profile.pinterest" ng-class="{'has-error': userForm['user[profile_attributes][pinterest]'].$dirty && userForm['user[profile_attributes][pinterest]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-pinterest"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-pinterest"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][pinterest]"
|
||||
ng-model="user.profile.pinterest"
|
||||
@ -512,7 +514,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.lastfm || user.profile.lastfm" ng-class="{'has-error': userForm['user[profile_attributes][lastfm]'].$dirty && userForm['user[profile_attributes][lastfm]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lastfm"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-lastfm"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][lastfm]"
|
||||
ng-model="user.profile.lastfm"
|
||||
@ -527,7 +529,7 @@
|
||||
|
||||
<div class="form-group" ng-show="social.flickr || user.profile.flickr" ng-class="{'has-error': userForm['user[profile_attributes][flickr]'].$dirty && userForm['user[profile_attributes][flickr]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-flickr"></i></span>
|
||||
<span class="input-group-addon help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-flickr"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][flickr]"
|
||||
ng-model="user.profile.flickr"
|
||||
|
@ -11,15 +11,18 @@
|
||||
<div class="col-sm-offset-1 col-md-offset-3 col-sm-7 col-md-5 col-lg-4 m-b-lg">
|
||||
<span ng-bind-html="aboutBody.value"></span>
|
||||
<p ng-show="cgu">
|
||||
<a href="{{cgu.custom_asset_file_attributes.attachment_url}}" target="_blank" translate>{{ 'read_the_fablab_policy' }}</a>
|
||||
<a href="{{cgu.custom_asset_file_attributes.attachment_url}}" target="_blank" translate>{{ 'about.read_the_fablab_policy' }}</a>
|
||||
</p>
|
||||
<p ng-show="cgv">
|
||||
<a href="{{cgv.custom_asset_file_attributes.attachment_url}}" target="_blank" translate>{{ 'read_the_fablab_s_general_terms_and_conditions' }}</a>
|
||||
<a href="{{cgv.custom_asset_file_attributes.attachment_url}}" target="_blank" translate>{{ 'about.read_the_fablab_s_general_terms_and_conditions' }}</a>
|
||||
</p>
|
||||
<p ng-show="privacyPolicy.value">
|
||||
<a ui-sref="app.public.privacy" translate>{{ 'about.privacy_policy' }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-offset-0 col-md-offset-0 col-lg-offset-1 col-sm-4 col-md-4">
|
||||
<h2 class="about-title-aside text-u-c" translate translate>{{ 'your_fablab_s_contacts' }}</h2>
|
||||
<h2 class="about-title-aside text-u-c" translate>{{ 'about.your_fablab_s_contacts' }}</h2>
|
||||
<span ng-bind-html="aboutContacts.value"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
10
app/assets/templates/shared/cookies.html
Normal file
10
app/assets/templates/shared/cookies.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="cookies-consent" ng-hide="cookiesState">
|
||||
<p class="cookies-infos">
|
||||
<span translate>{{ 'cookies.about_cookies' }}</span>
|
||||
<a ng-href="{{learnMoreUrl}}" target="{{ learnMoreUrl.startsWith('http') ? '_blank' : '_self' }}" translate>{{ 'cookies.learn_more' }}</a>
|
||||
</p>
|
||||
<div class="cookies-actions">
|
||||
<button class="decline" ng-click="declineCookies()" translate>{{ 'cookies.decline' }}</button>
|
||||
<button class="accept" ng-click="acceptCookies()" translate>{{ 'cookies.accept' }}</button>
|
||||
</div>
|
||||
</div>
|
@ -21,7 +21,7 @@
|
||||
ng-minlength="8">
|
||||
</div>
|
||||
<span class="help-block" ng-show="passwordEditForm.password.$dirty && passwordEditForm.password.$error.required" translate>{{ 'password_is_required' }}</span>
|
||||
<span class="help-block" ng-show="passwordEditForm.password.$dirty && passwordEditForm.password.$error.minlength" translate>{{ 'password_is_too_short_(minimum_8_characters)' }}</span>
|
||||
<span class="help-block" ng-show="passwordEditForm.password.$dirty && passwordEditForm.password.$error.minlength" translate>{{ 'password_is_too_short' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -51,4 +51,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
24
app/assets/templates/shared/privacy.html.erb
Normal file
24
app/assets/templates/shared/privacy.html.erb
Normal file
@ -0,0 +1,24 @@
|
||||
<div class="about-fablab scrollable">
|
||||
<div class="row padder">
|
||||
<header class="about-picture">
|
||||
<div class="col-sm-offset-2 col-md-offset-3 col-sm-10 col-md-8">
|
||||
<h1 class="about-title text-u-c" translate>{{ 'privacy.title' }}</h1>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="row padder">
|
||||
<div class="col-sm-offset-1 col-md-offset-3 col-sm-7 col-md-5 col-lg-4 m-b-lg">
|
||||
<div class="last-update text-gray">
|
||||
<span translate>{{ 'privacy.last_update' }}</span>
|
||||
<span>{{ privacyBody.last_update | amDateFormat:'LL' }}</span>
|
||||
</div>
|
||||
<span ng-bind-html="privacyBody.value"></span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-offset-0 col-md-offset-0 col-lg-offset-1 col-sm-4 col-md-4" ng-show="privacyDpo.value">
|
||||
<h2 class="about-title-aside text-u-c" translate>{{ 'privacy.dpo' }}</h2>
|
||||
<span ng-bind-html="privacyDpo.value"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -12,17 +12,17 @@
|
||||
<label class="checkbox-inline">
|
||||
<input type="radio"
|
||||
name="gender"
|
||||
ng-model="user.profile_attributes.gender"
|
||||
ng-model="user.statistic_profile_attributes.gender"
|
||||
value="true"
|
||||
required/> {{ 'man' | translate }}
|
||||
</label>
|
||||
<label class="checkbox-inline">
|
||||
<input type="radio"
|
||||
name="gender"
|
||||
ng-model="user.profile_attributes.gender"
|
||||
ng-model="user.statistic_profile_attributes.gender"
|
||||
value="false"/> {{ 'woman' | translate }}
|
||||
</label>
|
||||
<span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent m-l-xs help-cursor" title="{{ 'used_for_statistics' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.gender.$dirty && signupForm.gender.$error.required" translate>{{ 'gender_is_required'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -36,7 +36,7 @@
|
||||
class="form-control"
|
||||
placeholder="{{ 'your_first_name' | translate }}"
|
||||
required>
|
||||
<span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent m-l-xs help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.first_name.$dirty && signupForm.first_name.$error.required" translate>{{ 'first_name_is_required' }}</span>
|
||||
</div>
|
||||
<div class="m-b visible-xs"></div>
|
||||
@ -47,7 +47,7 @@
|
||||
class="form-control"
|
||||
placeholder="{{ 'your_surname' | translate }}"
|
||||
required>
|
||||
<span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent m-l-xs help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.last_name.$dirty && signupForm.last_name.$error.required" translate>{{ 'surname_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +63,7 @@
|
||||
placeholder="{{ 'your_pseudonym' | translate }}"
|
||||
required>
|
||||
</div>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent help-cursor" title="{{ 'used_for_profile' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.username.$dirty && signupForm.username.$error.required" translate>{{ 'pseudonym_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -79,7 +79,7 @@
|
||||
placeholder="{{ 'your_email_address' | translate }}"
|
||||
required>
|
||||
</div>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.email.$dirty && signupForm.email.$error.required" translate>{{ 'email_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -98,7 +98,7 @@
|
||||
</div>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.password.$dirty && signupForm.password.$error.required" translate>{{ 'password_is_required' }}</span>
|
||||
<span class="help-block" ng-show="signupForm.password.$dirty && signupForm.password.$error.minlength" translate>{{ 'password_is_too_short_(minimum_8_characters)' }}</span>
|
||||
<span class="help-block" ng-show="signupForm.password.$dirty && signupForm.password.$error.minlength" translate>{{ 'password_is_too_short' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -142,7 +142,7 @@
|
||||
placeholder="{{ 'name_of_your_organization' | translate }}"
|
||||
ng-required="user.organization">
|
||||
</div>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.organization_name.$dirty && signupForm.organization_name.$error.required" translate>{{ 'organization_name_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -158,7 +158,7 @@
|
||||
placeholder="{{ 'address_of_your_organization' | translate }}"
|
||||
ng-required="user.organization">
|
||||
</div>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.organization_address.$dirty && signupForm.organization_address.$error.required" translate>{{ 'organization_address_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -169,7 +169,7 @@
|
||||
<select ng-model="user.group_id" class="form-control" name="group_id" ng-options="g.id as g.name for g in groups" required>
|
||||
<option value="" translate>{{ 'your_user_s_profile' }}</option>
|
||||
</select>
|
||||
<span class="exponent exponent-select"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent exponent-select help-cursor" title="{{ 'used_for_invoicing' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<span class="help-block" ng-show="signupForm.group_id.$dirty && signupForm.group_id.$error.required" translate>{{ 'user_s_profile_is_required' }}</span>
|
||||
</div>
|
||||
@ -182,7 +182,7 @@
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="birthday"
|
||||
ng-model="user.profile_attributes.birthday"
|
||||
ng-model="user.statistic_profile_attributes.birthday"
|
||||
uib-datepicker-popup="{{datePicker.format}}"
|
||||
datepicker-options="datePicker.options"
|
||||
is-open="datePicker.opened"
|
||||
@ -190,7 +190,7 @@
|
||||
ng-click="openDatePicker($event)"
|
||||
required/>
|
||||
</div>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent help-cursor" title="{{ 'used_for_statistics' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.birthday.$dirty && signupForm.birthday.$error.required" translate>{{ 'birth_date_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -206,7 +206,7 @@
|
||||
placeholder="{{ 'phone_number' | translate }}"
|
||||
required>
|
||||
</div>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="exponent help-cursor" title="{{ 'used_for_reservation' | translate }}"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
<span class="help-block" ng-show="signupForm.phone.$dirty && signupForm.phone.$error.required" translate>{{ 'phone_number_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -218,7 +218,7 @@
|
||||
id="is_allow_contact"
|
||||
ng-model="user.is_allow_contact"
|
||||
value="true"/>
|
||||
<label for="is_allow_contact" translate>{{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' }}</label>
|
||||
<label for="is_allow_contact" class="help-cursor" title="{{ 'public_profile' | translate }}" translate>{{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -88,7 +88,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label class="col-sm-2 control-label" translate>{{ 'space.attached_files_(pdf)' }}</label>
|
||||
<label class="col-sm-2 control-label" translate>{{ 'space.attached_files_pdf' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div ng-repeat="file in space.space_files_attributes" ng-show="!file._destroy">
|
||||
<input type="hidden" ng-model="file.id" name="space[space_files_attributes][][id]" ng-value="file.id" />
|
||||
|
@ -75,7 +75,7 @@
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<label for="description" translate>{{ 'description_(optional)' }}</label>
|
||||
<label for="description" translate>{{ 'description_optional' }}</label>
|
||||
<p translate>{{ 'will_appear_on_the_refund_invoice' }}</p>
|
||||
<textarea class="form-control m-t-sm"
|
||||
id="description"
|
||||
|
@ -4,6 +4,12 @@
|
||||
# Typical action is an user reporting an abuse on a project
|
||||
class API::AbusesController < API::ApiController
|
||||
before_action :authenticate_user!, except: :create
|
||||
before_action :set_abuse, only: %i[destroy]
|
||||
|
||||
def index
|
||||
authorize Abuse
|
||||
@abuses = Abuse.all
|
||||
end
|
||||
|
||||
def create
|
||||
@abuse = Abuse.new(abuse_params)
|
||||
@ -14,8 +20,18 @@ class API::AbusesController < API::ApiController
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize Abuse
|
||||
@abuse.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_abuse
|
||||
@abuse = Abuse.find(params[:id])
|
||||
end
|
||||
|
||||
def abuse_params
|
||||
params.require(:abuse).permit(:signaled_type, :signaled_id, :first_name, :last_name, :email, :message)
|
||||
end
|
||||
|
@ -11,24 +11,13 @@ class API::AdminsController < API::ApiController
|
||||
|
||||
def create
|
||||
authorize :admin
|
||||
generated_password = Devise.friendly_token.first(8)
|
||||
@admin = User.new(admin_params.merge(password: generated_password))
|
||||
@admin.send :set_slug
|
||||
res = UserService.create_admin(admin_params)
|
||||
|
||||
# we associate the admin group to prevent linking any other 'normal' group (which won't be deletable afterwards)
|
||||
@admin.group = Group.find_by(slug: 'admins')
|
||||
|
||||
# if the authentication is made through an SSO, generate a migration token
|
||||
@admin.generate_auth_migration_token unless AuthProvider.active.providable_type == DatabaseProvider.name
|
||||
|
||||
if @admin.save(validate: false)
|
||||
@admin.send_confirmation_instructions
|
||||
@admin.add_role(:admin)
|
||||
@admin.remove_role(:member)
|
||||
UsersMailer.delay.notify_user_account_created(@admin, generated_password)
|
||||
if res[:saved]
|
||||
@admin = res[:user]
|
||||
render :create, status: :created
|
||||
else
|
||||
render json: @admin.errors.full_messages, status: :unprocessable_entity
|
||||
render json: res[:user].errors.full_messages, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
@ -45,7 +34,11 @@ class API::AdminsController < API::ApiController
|
||||
private
|
||||
|
||||
def admin_params
|
||||
params.require(:admin).permit(:username, :email, profile_attributes: [:first_name, :last_name, :gender,
|
||||
:birthday, :phone, address_attributes: [:address]])
|
||||
params.require(:admin).permit(
|
||||
:username, :email,
|
||||
profile_attributes: %i[first_name last_name phone],
|
||||
invoicing_profile_attributes: [address_attributes: [:address]],
|
||||
statistic_profile_attributes: %i[gender birthday]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -23,8 +23,8 @@ class API::AvailabilitiesController < API::ApiController
|
||||
def public
|
||||
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
|
||||
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
|
||||
@reservations = Reservation.includes(:slots, user: [:profile])
|
||||
.references(:slots, :user)
|
||||
@reservations = Reservation.includes(:slots, :statistic_profile)
|
||||
.references(:slots)
|
||||
.where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
|
||||
|
||||
machine_ids = params[:m] || []
|
||||
@ -93,7 +93,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
|
||||
def reservations
|
||||
authorize Availability
|
||||
@reservation_slots = @availability.slots.includes(reservations: [user: [:profile]]).order('slots.start_at ASC')
|
||||
@reservation_slots = @availability.slots.includes(reservations: [statistic_profile: [user: [:profile]]]).order('slots.start_at ASC')
|
||||
end
|
||||
|
||||
def export_availabilities
|
||||
|
@ -37,7 +37,7 @@ class API::ExportsController < API::ApiController
|
||||
elsif params[:category] == 'availabilities'
|
||||
case params[:type]
|
||||
when 'index'
|
||||
export = export.where('created_at > ?', Availability.maximum('updated_at'))
|
||||
export = export.where('created_at > ?', [Availability.maximum('updated_at'), Reservation.maximum('updated_at')].max)
|
||||
else
|
||||
raise ArgumentError, "Unknown type availabilities/#{params[:type]}"
|
||||
end
|
||||
|
@ -8,7 +8,7 @@ class API::InvoicesController < API::ApiController
|
||||
def index
|
||||
authorize Invoice
|
||||
@invoices = Invoice.includes(
|
||||
:avoir, :invoiced, invoice_items: %i[subscription invoice_item], user: %i[profile trainings]
|
||||
:avoir, :invoiced, :invoicing_profile, invoice_items: %i[subscription invoice_item]
|
||||
).all.order('reference DESC')
|
||||
end
|
||||
|
||||
|
@ -65,7 +65,7 @@ class API::MembersController < API::ApiController
|
||||
|
||||
def destroy
|
||||
authorize @member
|
||||
@member.soft_destroy
|
||||
@member.destroy
|
||||
sign_out(@member)
|
||||
head :no_content
|
||||
end
|
||||
@ -189,29 +189,31 @@ class API::MembersController < API::ApiController
|
||||
|
||||
def user_params
|
||||
if current_user.id == params[:id].to_i
|
||||
params.require(:user).permit(:username, :email, :password, :password_confirmation, :group_id, :is_allow_contact,
|
||||
:is_allow_newsletter,
|
||||
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest,
|
||||
:software_mastered, :website, :job, :facebook, :twitter,
|
||||
:google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
|
||||
params.require(:user).permit(:username, :email, :password, :password_confirmation, :group_id, :is_allow_contact, :is_allow_newsletter,
|
||||
profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job,
|
||||
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
|
||||
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
|
||||
user_avatar_attributes: %i[id attachment destroy],
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name,
|
||||
address_attributes: %i[id address]]])
|
||||
user_avatar_attributes: %i[id attachment destroy]],
|
||||
invoicing_profile_attributes: [
|
||||
:id,
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name, address_attributes: %i[id address]]
|
||||
],
|
||||
statistic_profile_attributes: %i[id gender birthday])
|
||||
|
||||
elsif current_user.admin?
|
||||
params.require(:user).permit(:username, :email, :password, :password_confirmation,
|
||||
:is_allow_contact, :is_allow_newsletter, :group_id,
|
||||
training_ids: [], tag_ids: [],
|
||||
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest,
|
||||
:software_mastered, :website, :job, :facebook, :twitter,
|
||||
:google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
|
||||
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,
|
||||
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
|
||||
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
|
||||
user_avatar_attributes: %i[id attachment destroy],
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name,
|
||||
address_attributes: %i[id address]]])
|
||||
user_avatar_attributes: %i[id attachment destroy]],
|
||||
invoicing_profile_attributes: [
|
||||
:id,
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name, address_attributes: %i[id address]]
|
||||
],
|
||||
statistic_profile_attributes: [:id, :gender, :birthday, training_ids: []])
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -41,8 +41,8 @@ class API::PricesController < API::ApiController
|
||||
# user
|
||||
user = User.find(price_parameters[:user_id])
|
||||
# reservable
|
||||
if price_parameters[:reservable_id].nil?
|
||||
@amount = {elements: nil, total: 0, before_coupon: 0}
|
||||
if [nil, ''].include? price_parameters[:reservable_id]
|
||||
@amount = { elements: nil, total: 0, before_coupon: 0 }
|
||||
else
|
||||
reservable = price_parameters[:reservable_type].constantize.find(price_parameters[:reservable_id])
|
||||
@amount = Price.compute(current_user.admin?,
|
||||
|
@ -20,7 +20,7 @@ class API::ProjectsController < API::ApiController
|
||||
end
|
||||
|
||||
def create
|
||||
@project = Project.new(project_params.merge(author_id: current_user.id))
|
||||
@project = Project.new(project_params.merge(author_statistic_profile_id: current_user.statistic_profile.id))
|
||||
if @project.save
|
||||
render :show, status: :created, location: @project
|
||||
else
|
||||
@ -71,8 +71,7 @@ class API::ProjectsController < API::ApiController
|
||||
end
|
||||
|
||||
def project_params
|
||||
params.require(:project).permit(:name, :description, :tags, :machine_ids, :component_ids, :theme_ids, :licence_id,
|
||||
:author_id, :licence_id, :state,
|
||||
params.require(:project).permit(:name, :description, :tags, :machine_ids, :component_ids, :theme_ids, :licence_id, :licence_id, :state,
|
||||
user_ids: [], machine_ids: [], component_ids: [], theme_ids: [],
|
||||
project_image_attributes: [:attachment],
|
||||
project_caos_attributes: %i[id attachment _destroy],
|
||||
|
@ -23,10 +23,10 @@ class API::ReservationsController < API::ApiController
|
||||
|
||||
def create
|
||||
method = current_user.admin? ? :local : :stripe
|
||||
user_id = current_user.admin? ? reservation_params[:user_id] : current_user.id
|
||||
user_id = current_user.admin? ? params[:reservation][:user_id] : current_user.id
|
||||
|
||||
@reservation = Reservation.new(reservation_params)
|
||||
is_reserve = Reservations::Reserve.new(user_id, current_user.id)
|
||||
is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id)
|
||||
.pay_and_save(@reservation, method, coupon_params[:coupon_code])
|
||||
|
||||
if is_reserve
|
||||
@ -56,8 +56,7 @@ class API::ReservationsController < API::ApiController
|
||||
end
|
||||
|
||||
def reservation_params
|
||||
params.require(:reservation).permit(:user_id, :message, :reservable_id, :reservable_type, :card_token, :plan_id,
|
||||
:nb_reserve_places,
|
||||
params.require(:reservation).permit(:message, :reservable_id, :reservable_type, :card_token, :plan_id, :nb_reserve_places,
|
||||
tickets_attributes: %i[event_price_category_id booked],
|
||||
slots_attributes: %i[id start_at end_at availability_id offered])
|
||||
end
|
||||
|
@ -11,7 +11,7 @@ class API::SettingsController < API::ApiController
|
||||
def update
|
||||
authorize Setting
|
||||
@setting = Setting.find_or_initialize_by(name: params[:name])
|
||||
if @setting.save && @setting.history_values.create(value: setting_params[:value], user: current_user)
|
||||
if @setting.save && @setting.history_values.create(value: setting_params[:value], invoicing_profile: current_user.invoicing_profile)
|
||||
render status: :ok
|
||||
else
|
||||
render json: @setting.errors.full_messages, status: :unprocessable_entity
|
||||
|
@ -19,7 +19,7 @@ class API::SlotsController < API::ApiController
|
||||
|
||||
def cancel
|
||||
authorize @slot
|
||||
@slot.update_attributes(canceled_at: DateTime.now)
|
||||
SlotService.new.cancel(@slot)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -16,10 +16,10 @@ class API::SubscriptionsController < API::ApiController
|
||||
head 403
|
||||
else
|
||||
method = current_user.admin? ? :local : :stripe
|
||||
user_id = current_user.admin? ? subscription_params[:user_id] : current_user.id
|
||||
user_id = current_user.admin? ? params[:subscription][:user_id] : current_user.id
|
||||
|
||||
@subscription = Subscription.new(subscription_params)
|
||||
is_subscribe = Subscriptions::Subscribe.new(user_id, current_user.id)
|
||||
is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id)
|
||||
.pay_and_save(@subscription, method, coupon_params[:coupon_code], true)
|
||||
|
||||
if is_subscribe
|
||||
@ -35,7 +35,7 @@ class API::SubscriptionsController < API::ApiController
|
||||
|
||||
free_days = params[:subscription][:free] || false
|
||||
|
||||
res = Subscriptions::Subscribe.new(@subscription.user_id, current_user.id)
|
||||
res = Subscriptions::Subscribe.new(current_user.invoicing_profile.id)
|
||||
.extend_subscription(@subscription, subscription_update_params[:expired_at], free_days)
|
||||
if res.is_a?(Subscription)
|
||||
@subscription = res
|
||||
@ -56,7 +56,7 @@ class API::SubscriptionsController < API::ApiController
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def subscription_params
|
||||
params.require(:subscription).permit(:plan_id, :user_id, :card_token)
|
||||
params.require(:subscription).permit(:plan_id, :card_token)
|
||||
end
|
||||
|
||||
def coupon_params
|
||||
|
@ -58,7 +58,7 @@ class API::TrainingsController < API::ApiController
|
||||
authorize Training
|
||||
@training = Training.find(params[:id])
|
||||
@availabilities = @training.availabilities
|
||||
.includes(slots: { reservations: { user: %i[profile trainings] } })
|
||||
.includes(slots: { reservations: { statistic_profile: [:trainings, user: [:profile]] } })
|
||||
.order('start_at DESC')
|
||||
end
|
||||
|
||||
|
@ -14,24 +14,13 @@ class API::UsersController < API::ApiController
|
||||
|
||||
def create
|
||||
if current_user.admin?
|
||||
generated_password = Devise.friendly_token.first(8)
|
||||
@user = User.new(email: partner_params[:email],
|
||||
username: "#{partner_params[:first_name]}#{partner_params[:last_name]}",
|
||||
password: generated_password,
|
||||
password_confirmation: generated_password,
|
||||
group_id: Group.first.id)
|
||||
@user.build_profile(first_name: partner_params[:first_name],
|
||||
last_name: partner_params[:last_name],
|
||||
gender: true,
|
||||
birthday: Time.now,
|
||||
phone: '0000000000')
|
||||
res = UserService.create_partner(partner_params)
|
||||
|
||||
if @user.save
|
||||
@user.remove_role :member
|
||||
@user.add_role :partner
|
||||
if res[:saved]
|
||||
@user = res[:user]
|
||||
render status: :created
|
||||
else
|
||||
render json: @user.errors.full_messages, status: :unprocessable_entity
|
||||
render json: res[:user].errors.full_messages, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
head 403
|
||||
|
@ -5,7 +5,8 @@ class API::WalletController < API::ApiController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def by_user
|
||||
@wallet = Wallet.find_by(user_id: params[:user_id])
|
||||
invoicing_profile = InvoicingProfile.find_by(user_id: params[:user_id])
|
||||
@wallet = Wallet.find_by(invoicing_profile_id: invoicing_profile.id)
|
||||
authorize @wallet
|
||||
render :show
|
||||
end
|
||||
@ -13,7 +14,7 @@ class API::WalletController < API::ApiController
|
||||
def transactions
|
||||
@wallet = Wallet.find(params[:id])
|
||||
authorize @wallet
|
||||
@wallet_transactions = @wallet.wallet_transactions.includes(:invoice, user: [:profile]).order(created_at: :desc)
|
||||
@wallet_transactions = @wallet.wallet_transactions.includes(:invoice, :invoicing_profile).order(created_at: :desc)
|
||||
end
|
||||
|
||||
def credit
|
||||
|
@ -32,12 +32,13 @@ class ApplicationController < ActionController::Base
|
||||
def configure_permitted_parameters
|
||||
devise_parameter_sanitizer.permit(:sign_up,
|
||||
keys: [
|
||||
{ profile_attributes: [
|
||||
:phone, :last_name, :first_name, :gender, :birthday,
|
||||
:interest, :software_mastered, organization_attributes: [
|
||||
:name, address_attributes: [:address]
|
||||
]
|
||||
] },
|
||||
{
|
||||
profile_attributes: %i[phone last_name first_name interest software_mastered],
|
||||
invoicing_profile_attributes: [
|
||||
organization_attributes: [:name, address_attributes: [:address]]
|
||||
],
|
||||
statistic_profile_attributes: %i[gender birthday]
|
||||
},
|
||||
:username, :is_allow_contact, :is_allow_newsletter, :cgu, :group_id
|
||||
])
|
||||
end
|
||||
|
@ -1,5 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Handling a new user registration through the sign-up modal
|
||||
class RegistrationsController < Devise::RegistrationsController
|
||||
# POST /resource
|
||||
# POST /users.json
|
||||
def create
|
||||
build_resource(sign_up_params)
|
||||
|
||||
@ -24,5 +27,4 @@ class RegistrationsController < Devise::RegistrationsController
|
||||
respond_with resource
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Devise controller for handling client sessions
|
||||
class SessionsController < Devise::SessionsController
|
||||
#before_action :set_csrf_headers, only: [:create, :destroy]
|
||||
|
||||
def new
|
||||
active_provider = AuthProvider.active
|
||||
@ -9,9 +11,4 @@ class SessionsController < Devise::SessionsController
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def set_csrf_headers
|
||||
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
|
||||
end
|
||||
end
|
||||
|
3
app/exceptions/no_profile_error.rb
Normal file
3
app/exceptions/no_profile_error.rb
Normal file
@ -0,0 +1,3 @@
|
||||
# Raised when an expected profile (statistic, invoicing or normal) was not found on an user
|
||||
class NoProfileError < StandardError
|
||||
end
|
@ -34,7 +34,7 @@ module AvailabilityHelper
|
||||
def space_slot_border_color(slot)
|
||||
if slot.is_reserved
|
||||
IS_RESERVED_BY_CURRENT_USER
|
||||
elsif slot.is_complete?
|
||||
elsif slot.complete?
|
||||
IS_COMPLETED
|
||||
else
|
||||
SPACE_COLOR
|
||||
|
@ -1,3 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Availability stores time slots that are available to reservation for an associated reservable
|
||||
# Eg. a 3D printer will be reservable on thursday from 9 to 11 pm
|
||||
# Availabilities may be subdivided into Slots (of 1h), for some types of reservables (eg. Machine)
|
||||
class Availability < ActiveRecord::Base
|
||||
|
||||
# elastic initialisations
|
||||
@ -35,8 +40,8 @@ class Availability < ActiveRecord::Base
|
||||
validate :should_be_associated
|
||||
|
||||
## elastic callbacks
|
||||
after_save { AvailabilityIndexerWorker.perform_async(:index, self.id) }
|
||||
after_destroy { AvailabilityIndexerWorker.perform_async(:delete, self.id) }
|
||||
after_save { AvailabilityIndexerWorker.perform_async(:index, id) }
|
||||
after_destroy { AvailabilityIndexerWorker.perform_async(:delete, id) }
|
||||
|
||||
# elastic mapping
|
||||
settings index: { number_of_replicas: 0 } do
|
||||
@ -87,9 +92,7 @@ class Availability < ActiveRecord::Base
|
||||
def title(filter = {})
|
||||
case available_type
|
||||
when 'machines'
|
||||
if filter[:machine_ids]
|
||||
return machines.to_ary.delete_if { |m| !filter[:machine_ids].include?(m.id) }.map(&:name).join(' - ')
|
||||
end
|
||||
return machines.to_ary.delete_if { |m| !filter[:machine_ids].include?(m.id) }.map(&:name).join(' - ') if filter[:machine_ids]
|
||||
|
||||
machines.map(&:name).join(' - ')
|
||||
when 'event'
|
||||
@ -110,7 +113,7 @@ class Availability < ActiveRecord::Base
|
||||
return false if nb_total_places.blank?
|
||||
|
||||
if available_type == 'training' || available_type == 'space'
|
||||
nb_total_places <= slots.to_a.select {|s| s.canceled_at == nil }.size
|
||||
nb_total_places <= slots.to_a.select { |s| s.canceled_at.nil? }.size
|
||||
elsif available_type == 'event'
|
||||
event.nb_free_places.zero?
|
||||
end
|
||||
@ -129,18 +132,19 @@ class Availability < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# the resulting JSON will be indexed in ElasticSearch, as /fablab/availabilities
|
||||
def as_indexed_json
|
||||
json = JSON.parse(to_json)
|
||||
json['hours_duration'] = (end_at - start_at) / (60 * 60)
|
||||
json['subType'] = case available_type
|
||||
when 'machines'
|
||||
machines_availabilities.map{ |ma| ma.machine.friendly_id }
|
||||
machines_availabilities.map { |ma| ma.machine.friendly_id }
|
||||
when 'training'
|
||||
trainings_availabilities.map{ |ta| ta.training.friendly_id }
|
||||
trainings_availabilities.map { |ta| ta.training.friendly_id }
|
||||
when 'event'
|
||||
[event.category.friendly_id]
|
||||
when 'space'
|
||||
spaces_availabilities.map{ |sa| sa.space.friendly_id }
|
||||
spaces_availabilities.map { |sa| sa.space.friendly_id }
|
||||
else
|
||||
[]
|
||||
end
|
||||
@ -156,7 +160,9 @@ class Availability < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def should_be_associated
|
||||
errors.add(:machine_ids, I18n.t('availabilities.must_be_associated_with_at_least_1_machine')) if available_type == 'machines' && machine_ids.count == 0
|
||||
return unless available_type == 'machines' && machine_ids.count.zero?
|
||||
|
||||
errors.add(:machine_ids, I18n.t('availabilities.must_be_associated_with_at_least_1_machine'))
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,6 +1,7 @@
|
||||
class Group < ActiveRecord::Base
|
||||
has_many :plans
|
||||
has_many :users
|
||||
has_many :statistic_profiles
|
||||
has_many :trainings_pricings, dependent: :destroy
|
||||
has_many :machines_prices, -> { where(priceable_type: 'Machine') }, class_name: 'Price', dependent: :destroy
|
||||
has_many :spaces_prices, -> { where(priceable_type: 'Space') }, class_name: 'Price', dependent: :destroy
|
||||
|
@ -5,7 +5,7 @@ require 'checksum'
|
||||
# Setting values, kept history of modifications
|
||||
class HistoryValue < ActiveRecord::Base
|
||||
belongs_to :setting
|
||||
belongs_to :user
|
||||
belongs_to :invoicing_profile
|
||||
|
||||
after_create :chain_record
|
||||
|
||||
@ -18,6 +18,10 @@ class HistoryValue < ActiveRecord::Base
|
||||
footprint == compute_footprint
|
||||
end
|
||||
|
||||
def user
|
||||
invoicing_profile.user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compute_footprint
|
||||
|
@ -12,12 +12,17 @@ class Invoice < ActiveRecord::Base
|
||||
|
||||
has_many :invoice_items, dependent: :destroy
|
||||
accepts_nested_attributes_for :invoice_items
|
||||
belongs_to :user
|
||||
belongs_to :invoicing_profile
|
||||
belongs_to :statistic_profile
|
||||
belongs_to :wallet_transaction
|
||||
belongs_to :coupon
|
||||
|
||||
belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'invoiced_id'
|
||||
belongs_to :reservation, foreign_type: 'Reservation', foreign_key: 'invoiced_id'
|
||||
belongs_to :offer_day, foreign_type: 'OfferDay', foreign_key: 'invoiced_id'
|
||||
|
||||
has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy
|
||||
belongs_to :operator, foreign_key: :operator_id, class_name: 'User'
|
||||
belongs_to :operator_profile, foreign_key: :operator_profile_id, class_name: 'InvoicingProfile'
|
||||
|
||||
before_create :add_environment
|
||||
after_create :update_reference, :chain_record
|
||||
@ -26,9 +31,9 @@ class Invoice < ActiveRecord::Base
|
||||
validates_with ClosedPeriodValidator
|
||||
|
||||
def file
|
||||
dir = "invoices/#{user.id}"
|
||||
dir = "invoices/#{invoicing_profile.id}"
|
||||
|
||||
# create directories if they doesn't exists (invoice & user_id)
|
||||
# create directories if they doesn't exists (invoice & invoicing_profile_id)
|
||||
FileUtils.mkdir_p dir
|
||||
"#{dir}/#{filename}"
|
||||
end
|
||||
@ -37,6 +42,10 @@ class Invoice < ActiveRecord::Base
|
||||
"#{ENV['INVOICE_PREFIX']}-#{id}_#{created_at.strftime('%d%m%Y')}.pdf"
|
||||
end
|
||||
|
||||
def user
|
||||
invoicing_profile.user
|
||||
end
|
||||
|
||||
def generate_reference
|
||||
pattern = Setting.find_by(name: 'invoice_reference').value
|
||||
|
||||
@ -133,7 +142,7 @@ class Invoice < ActiveRecord::Base
|
||||
|
||||
# for debug & used by rake task "fablab:maintenance:regenerate_invoices"
|
||||
def regenerate_invoice_pdf
|
||||
pdf = ::PDF::Invoice.new(self, nil).render
|
||||
pdf = ::PDF::Invoice.new(self, subscription&.expiration_date).render
|
||||
File.binwrite(file, pdf)
|
||||
end
|
||||
|
||||
@ -202,9 +211,12 @@ class Invoice < ActiveRecord::Base
|
||||
##
|
||||
# Check if the current invoice is about a training that was previously validated for the concerned user.
|
||||
# In that case refunding the invoice shouldn't be allowed.
|
||||
# Moreover, an invoice cannot be refunded if the users' account was deleted
|
||||
# @return {Boolean}
|
||||
##
|
||||
def prevent_refund?
|
||||
return true if user.nil?
|
||||
|
||||
if invoiced_type == 'Reservation' && invoiced.reservable_type == 'Training'
|
||||
user.trainings.include?(invoiced.reservable_id)
|
||||
else
|
||||
@ -244,7 +256,7 @@ class Invoice < ActiveRecord::Base
|
||||
def generate_and_send_invoice
|
||||
unless Rails.env.test?
|
||||
puts "Creating an InvoiceWorker job to generate the following invoice: id(#{id}), invoiced_id(#{invoiced_id}), " \
|
||||
"invoiced_type(#{invoiced_type}), user_id(#{user_id})"
|
||||
"invoiced_type(#{invoiced_type}), user_id(#{invoicing_profile.user_id})"
|
||||
end
|
||||
InvoiceWorker.perform_async(id, user&.subscription&.expired_at)
|
||||
end
|
||||
@ -255,8 +267,8 @@ class Invoice < ActiveRecord::Base
|
||||
# @param value {Integer} the integer to pad
|
||||
# @param length {Integer} the length of the resulting string.
|
||||
##
|
||||
def pad_and_truncate (value, length)
|
||||
value.to_s.rjust(length, '0').gsub(/^.*(.{#{length},}?)$/m,'\1')
|
||||
def pad_and_truncate(value, length)
|
||||
value.to_s.rjust(length, '0').gsub(/^.*(.{#{length},}?)$/m, '\1')
|
||||
end
|
||||
|
||||
##
|
||||
|
25
app/models/invoicing_profile.rb
Normal file
25
app/models/invoicing_profile.rb
Normal file
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This table will save the user's profile data needed for legal accounting (invoices, wallet, etc.)
|
||||
# Legal accounting must be kept for 10 years but GDPR requires that an user can delete his account at any time.
|
||||
# The data will be kept even if the user is deleted, but it will be unlinked from the user's account.
|
||||
class InvoicingProfile < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
has_one :address, as: :placeable, dependent: :destroy
|
||||
accepts_nested_attributes_for :address, allow_destroy: true
|
||||
has_one :organization, dependent: :destroy
|
||||
accepts_nested_attributes_for :organization, allow_destroy: false
|
||||
has_many :invoices, dependent: :destroy
|
||||
|
||||
has_one :wallet, dependent: :destroy
|
||||
has_many :wallet_transactions, dependent: :destroy
|
||||
|
||||
has_many :history_values, dependent: :nullify
|
||||
|
||||
has_many :operated_invoices, foreign_key: :operator_profile_id, class_name: 'Invoice', dependent: :nullify
|
||||
|
||||
def full_name
|
||||
# if first_name or last_name is nil, the empty string will be used as a temporary replacement
|
||||
(first_name || '').humanize.titleize + ' ' + (last_name || '').humanize.titleize
|
||||
end
|
||||
end
|
@ -45,6 +45,7 @@ class NotificationType
|
||||
notify_admin_free_disk_space
|
||||
notify_admin_close_period_reminder
|
||||
notify_admin_archive_complete
|
||||
notify_privacy_policy_changed
|
||||
]
|
||||
# deprecated:
|
||||
# - notify_member_subscribed_plan_is_changed
|
||||
|
@ -1,5 +1,6 @@
|
||||
class Organization < ActiveRecord::Base
|
||||
belongs_to :profile
|
||||
belongs_to :invoicing_profile
|
||||
has_one :address, as: :placeable, dependent: :destroy
|
||||
accepts_nested_attributes_for :address, allow_destroy: true
|
||||
|
||||
|
@ -1,13 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# A special plan associated which can be associated with some users (with role 'partner')
|
||||
# These partners will be notified when the subscribers to this plan are realizing some actions
|
||||
class PartnerPlan < Plan
|
||||
resourcify
|
||||
|
||||
before_create :assign_default_values
|
||||
|
||||
def partners
|
||||
User.joins(:roles).where(roles: { name: 'partner', resource_type: 'PartnerPlan', resource_id: self.id })
|
||||
User.joins(:roles).where(roles: { name: 'partner', resource_type: 'PartnerPlan', resource_id: id })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_default_values
|
||||
assign_attributes(is_rolling: false)
|
||||
end
|
||||
|
@ -1,21 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Personal data attached to an user (like first_name, date of birth, etc.)
|
||||
class Profile < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
has_one :user_avatar, as: :viewable, dependent: :destroy
|
||||
accepts_nested_attributes_for :user_avatar,
|
||||
allow_destroy: true,
|
||||
reject_if: proc { |attributes| attributes['attachment'].blank? }
|
||||
has_one :address, as: :placeable, dependent: :destroy
|
||||
accepts_nested_attributes_for :address, allow_destroy: true
|
||||
|
||||
has_one :organization, dependent: :destroy
|
||||
accepts_nested_attributes_for :organization, allow_destroy: false
|
||||
|
||||
validates :first_name, presence: true, length: { maximum: 30 }
|
||||
validates :last_name, presence: true, length: { maximum: 30 }
|
||||
validates :gender, :inclusion => {:in => [true, false]}
|
||||
validates :birthday, presence: true
|
||||
validates_numericality_of :phone, only_integer: true, allow_blank: false
|
||||
|
||||
after_commit :update_invoicing_profile, if: :invoicing_data_was_modified?, on: [:update]
|
||||
|
||||
def full_name
|
||||
# if first_name or last_name is nil, the empty string will be used as a temporary replacement
|
||||
(first_name || '').humanize.titleize + ' ' + (last_name || '').humanize.titleize
|
||||
@ -25,28 +23,30 @@ class Profile < ActiveRecord::Base
|
||||
full_name
|
||||
end
|
||||
|
||||
def age
|
||||
if birthday.present?
|
||||
now = Time.now.utc.to_date
|
||||
(now - birthday).to_f / 365.2425
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def str_gender
|
||||
gender ? 'male' : 'female'
|
||||
end
|
||||
|
||||
def self.mapping
|
||||
# we protect some fields as they are designed to be managed by the system and must not be updated externally
|
||||
blacklist = %w(id user_id created_at updated_at)
|
||||
blacklist = %w[id user_id created_at updated_at]
|
||||
# model-relationships must be added manually
|
||||
additional = [%w(avatar string), %w(address string), %w(organization_name string), %w(organization_address string)]
|
||||
additional = [%w[avatar string], %w[address string], %w[organization_name string], %w[organization_address string]]
|
||||
Profile.column_types
|
||||
.map{|k,v| [k, v.type.to_s]}
|
||||
.delete_if { |col| blacklist.include?(col[0]) }
|
||||
.concat(additional)
|
||||
.map { |k, v| [k, v.type.to_s] }
|
||||
.delete_if { |col| blacklist.include?(col[0]) }
|
||||
.concat(additional)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def invoicing_data_was_modified?
|
||||
first_name_changed? or last_name_changed? or new_record?
|
||||
end
|
||||
|
||||
def update_invoicing_profile
|
||||
raise NoProfileError if user.invoicing_profile.nil?
|
||||
|
||||
user.invoicing_profile.update_attributes(
|
||||
first_name: first_name,
|
||||
last_name: last_name
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,3 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Project is the documentation about an object built by a fab-user
|
||||
# It can describe the steps taken by the fab-user to build his object, provide photos, description, attached CAO files, etc.
|
||||
class Project < ActiveRecord::Base
|
||||
include AASM
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
@ -9,7 +13,8 @@ class Project < ActiveRecord::Base
|
||||
document_type 'projects'
|
||||
|
||||
# kaminari
|
||||
paginates_per 12 # dependency in projects.coffee
|
||||
# -- dependency in app/assets/javascripts/controllers/projects.js.erb
|
||||
paginates_per 16
|
||||
|
||||
# friendlyId
|
||||
extend FriendlyId
|
||||
@ -20,15 +25,15 @@ class Project < ActiveRecord::Base
|
||||
has_many :project_caos, as: :viewable, dependent: :destroy
|
||||
accepts_nested_attributes_for :project_caos, allow_destroy: true, reject_if: :all_blank
|
||||
|
||||
has_and_belongs_to_many :machines, join_table: :projects_machines
|
||||
has_and_belongs_to_many :spaces, join_table: :projects_spaces
|
||||
has_and_belongs_to_many :components, join_table: :projects_components
|
||||
has_and_belongs_to_many :themes, join_table: :projects_themes
|
||||
has_and_belongs_to_many :machines, join_table: 'projects_machines'
|
||||
has_and_belongs_to_many :spaces, join_table: 'projects_spaces'
|
||||
has_and_belongs_to_many :components, join_table: 'projects_components'
|
||||
has_and_belongs_to_many :themes, join_table: 'projects_themes'
|
||||
|
||||
has_many :project_users, dependent: :destroy
|
||||
has_many :users, through: :project_users
|
||||
|
||||
belongs_to :author, foreign_key: :author_id, class_name: 'User'
|
||||
belongs_to :author, foreign_key: :author_statistic_profile_id, class_name: 'StatisticProfile'
|
||||
belongs_to :licence, foreign_key: :licence_id
|
||||
|
||||
has_many :project_steps, dependent: :destroy
|
||||
@ -39,22 +44,22 @@ class Project < ActiveRecord::Base
|
||||
|
||||
after_save :after_save_and_publish
|
||||
|
||||
aasm :column => 'state' do
|
||||
aasm column: 'state' do
|
||||
state :draft, initial: true
|
||||
state :published
|
||||
|
||||
event :publish, :after => :notify_admin_when_project_published do
|
||||
transitions from: :draft, :to => :published
|
||||
event :publish, after: :notify_admin_when_project_published do
|
||||
transitions from: :draft, to: :published
|
||||
end
|
||||
end
|
||||
|
||||
#scopes
|
||||
# scopes
|
||||
scope :published, -> { where("state = 'published'") }
|
||||
|
||||
## elastic
|
||||
# callbacks
|
||||
after_save { ProjectIndexerWorker.perform_async(:index, self.id) }
|
||||
after_destroy { ProjectIndexerWorker.perform_async(:delete, self.id) }
|
||||
after_save { ProjectIndexerWorker.perform_async(:index, id) }
|
||||
after_destroy { ProjectIndexerWorker.perform_async(:delete, id) }
|
||||
|
||||
# mapping
|
||||
settings index: { number_of_replicas: 0 } do
|
||||
@ -64,31 +69,20 @@ class Project < ActiveRecord::Base
|
||||
indexes 'name', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
||||
indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
||||
indexes 'project_steps' do
|
||||
indexes 'title', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
||||
indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
||||
indexes 'title', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
||||
indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# the resulting JSON will be indexed in ElasticSearch, as /fablab/projects
|
||||
def as_indexed_json
|
||||
Jbuilder.new do |json|
|
||||
json.id id
|
||||
json.state state
|
||||
json.author_id author_id
|
||||
json.user_ids user_ids
|
||||
json.machine_ids machine_ids
|
||||
json.theme_ids theme_ids
|
||||
json.component_ids component_ids
|
||||
json.tags tags
|
||||
json.name name
|
||||
json.description description
|
||||
json.project_steps project_steps do |project_step|
|
||||
json.title project_step.title
|
||||
json.description project_step.description
|
||||
end
|
||||
json.created_at created_at
|
||||
json.updated_at updated_at
|
||||
end.target!
|
||||
ApplicationController.new.view_context.render(
|
||||
partial: 'api/projects/indexed',
|
||||
locals: { project: self },
|
||||
formats: [:json],
|
||||
handlers: [:jbuilder]
|
||||
)
|
||||
end
|
||||
|
||||
def self.search(params, current_user)
|
||||
@ -101,43 +95,45 @@ class Project < ActiveRecord::Base
|
||||
bool: {
|
||||
must: [],
|
||||
should: [],
|
||||
filter: [],
|
||||
filter: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if params['q'].blank? # we sort by created_at if there isn't a query
|
||||
# we sort by created_at if there isn't a query
|
||||
if params['q'].blank?
|
||||
search[:sort] = { created_at: { order: :desc } }
|
||||
else # otherwise we search for the word (q) in various fields
|
||||
else
|
||||
# otherwise we search for the word (q) in various fields
|
||||
search[:query][:bool][:must] << {
|
||||
multi_match: {
|
||||
query: params['q'],
|
||||
type: 'most_fields',
|
||||
fields: %w(tags^4 name^5 description^3 project_steps.title^2 project_steps.description)
|
||||
fields: %w[tags^4 name^5 description^3 project_steps.title^2 project_steps.description]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
params.each do |name, value| # we filter by themes, components, machines
|
||||
# we filter by themes, components, machines
|
||||
params.each do |name, value|
|
||||
if name =~ /(.+_id$)/
|
||||
search[:query][:bool][:filter] << { term: { "#{name}s" => value } } if value
|
||||
end
|
||||
end
|
||||
|
||||
if current_user and params.key?('from') # if use select filter 'my project' or 'my collaborations'
|
||||
if params['from'] == 'mine'
|
||||
search[:query][:bool][:filter] << { term: { author_id: current_user.id } }
|
||||
end
|
||||
if params['from'] == 'collaboration'
|
||||
search[:query][:bool][:filter] << { term: { user_ids: current_user.id } }
|
||||
end
|
||||
# if use select filter 'my project' or 'my collaborations'
|
||||
if current_user && params.key?('from')
|
||||
search[:query][:bool][:filter] << { term: { author_id: current_user.id } } if params['from'] == 'mine'
|
||||
search[:query][:bool][:filter] << { term: { user_ids: current_user.id } } if params['from'] == 'collaboration'
|
||||
end
|
||||
|
||||
if current_user # if user is connected, also display his draft projects
|
||||
# if user is connected, also display his draft projects
|
||||
if current_user
|
||||
search[:query][:bool][:should] << { term: { state: 'published' } }
|
||||
search[:query][:bool][:should] << { term: { author_id: current_user.id } }
|
||||
search[:query][:bool][:should] << { term: { user_ids: current_user.id } }
|
||||
else # otherwise display only published projects
|
||||
else
|
||||
# otherwise display only published projects
|
||||
search[:query][:bool][:must] << { term: { state: 'published' } }
|
||||
end
|
||||
|
||||
@ -145,6 +141,7 @@ class Project < ActiveRecord::Base
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify_admin_when_project_published
|
||||
NotificationCenter.call type: 'notify_admin_when_project_published',
|
||||
receiver: User.admins,
|
||||
@ -152,9 +149,9 @@ class Project < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def after_save_and_publish
|
||||
if state_changed? and published?
|
||||
update_columns(published_at: Time.now)
|
||||
notify_admin_when_project_published
|
||||
end
|
||||
return unless state_changed? && published?
|
||||
|
||||
update_columns(published_at: Time.now)
|
||||
notify_admin_when_project_published
|
||||
end
|
||||
end
|
||||
|
@ -33,7 +33,7 @@ module Project::OpenlabSync
|
||||
components: components.map(&:name),
|
||||
themes: themes.map(&:name),
|
||||
author: author&.profile&.full_name,
|
||||
collaborators: users.map { |u| u.profile.full_name },
|
||||
collaborators: users.map { |u| u&.profile&.full_name },
|
||||
steps_body: steps_body,
|
||||
image_path: project_image&.attachment&.medium&.url,
|
||||
project_path: "/#!/projects/#{slug}",
|
||||
|
@ -1,7 +1,7 @@
|
||||
class Reservation < ActiveRecord::Base
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :statistic_profile
|
||||
|
||||
has_many :slots_reservations, dependent: :destroy
|
||||
has_many :slots, through: :slots_reservations
|
||||
@ -224,10 +224,10 @@ class Reservation < ActiveRecord::Base
|
||||
invoice_items
|
||||
end
|
||||
|
||||
def save_with_payment(operator_id, coupon_code = nil)
|
||||
def save_with_payment(operator_profile_id, coupon_code = nil)
|
||||
begin
|
||||
clean_pending_strip_invoice_items
|
||||
build_invoice(user: user, operator_id: operator_id)
|
||||
build_invoice(invoicing_profile: user.invoicing_profile, statistic_profile: user.statistic_profile, operator_profile_id: operator_profile_id)
|
||||
invoice_items = generate_invoice_items(false, coupon_code)
|
||||
rescue StandardError => e
|
||||
logger.error e
|
||||
@ -240,9 +240,9 @@ class Reservation < ActiveRecord::Base
|
||||
# TODO: refactoring
|
||||
customer = Stripe::Customer.retrieve(user.stp_customer_id)
|
||||
if plan_id
|
||||
self.subscription = Subscription.find_or_initialize_by(user_id: user.id)
|
||||
subscription.attributes = { plan_id: plan_id, user_id: user.id, card_token: card_token, expiration_date: nil }
|
||||
if subscription.save_with_payment(operator_id, false)
|
||||
self.subscription = Subscription.find_or_initialize_by(statistic_profile_id: statistic_profile_id)
|
||||
subscription.attributes = { plan_id: plan_id, statistic_profile_id: statistic_profile_id, card_token: card_token, expiration_date: nil }
|
||||
if subscription.save_with_payment(operator_profile_id, false)
|
||||
self.stp_invoice_id = invoice_items.first.refresh.invoice
|
||||
invoice.stp_invoice_id = invoice_items.first.refresh.invoice
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
@ -338,8 +338,8 @@ class Reservation < ActiveRecord::Base
|
||||
end
|
||||
|
||||
# check reservation amount total and strip invoice total to pay is equal
|
||||
# @params stp_invoice[Stripe::Invoice]
|
||||
# @params coupon_code[String]
|
||||
# @param stp_invoice[Stripe::Invoice]
|
||||
# @param coupon_code[String]
|
||||
# return Boolean
|
||||
def is_equal_reservation_total_and_stp_invoice_total(stp_invoice, coupon_code = nil)
|
||||
compute_amount_total_to_pay(coupon_code) == stp_invoice.total
|
||||
@ -368,16 +368,20 @@ class Reservation < ActiveRecord::Base
|
||||
pending_invoice_items.each(&:delete)
|
||||
end
|
||||
|
||||
def save_with_local_payment(operator_id, coupon_code = nil)
|
||||
build_invoice(user: user, operator_id: operator_id)
|
||||
def save_with_local_payment(operator_profile_id, coupon_code = nil)
|
||||
build_invoice(
|
||||
invoicing_profile: user.invoicing_profile,
|
||||
statistic_profile: user.statistic_profile,
|
||||
operator_profile_id: operator_profile_id
|
||||
)
|
||||
generate_invoice_items(true, coupon_code)
|
||||
|
||||
return false unless valid?
|
||||
|
||||
if plan_id
|
||||
self.subscription = Subscription.find_or_initialize_by(user_id: user.id)
|
||||
subscription.attributes = { plan_id: plan_id, user_id: user.id, expiration_date: nil }
|
||||
if subscription.save_with_local_payment(operator_id, false)
|
||||
self.subscription = Subscription.find_or_initialize_by(statistic_profile_id: statistic_profile_id)
|
||||
subscription.attributes = { plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil }
|
||||
if subscription.save_with_local_payment(operator_profile_id, false)
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: subscription.plan.amount,
|
||||
description: subscription.plan.name,
|
||||
@ -405,6 +409,10 @@ class Reservation < ActiveRecord::Base
|
||||
total
|
||||
end
|
||||
|
||||
def user
|
||||
statistic_profile.user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def machine_not_already_reserved
|
||||
|
@ -1,6 +1,6 @@
|
||||
class Role < ActiveRecord::Base
|
||||
has_and_belongs_to_many :users, :join_table => :users_roles
|
||||
belongs_to :resource, :polymorphic => true
|
||||
has_and_belongs_to_many :users, join_table: 'users_roles'
|
||||
belongs_to :resource, polymorphic: true
|
||||
|
||||
scopify
|
||||
end
|
||||
|
@ -4,6 +4,9 @@ class Setting < ActiveRecord::Base
|
||||
{ in: %w[about_title
|
||||
about_body
|
||||
about_contacts
|
||||
privacy_draft
|
||||
privacy_body
|
||||
privacy_dpo
|
||||
twitter_name
|
||||
home_blogpost
|
||||
machine_explications_alert
|
||||
@ -39,19 +42,30 @@ class Setting < ActiveRecord::Base
|
||||
display_name_enable
|
||||
machines_sort_by] }
|
||||
|
||||
after_update :update_stylesheet if :value_changed?
|
||||
after_update :update_stylesheet, :notify_privacy_policy_changed if :value_changed?
|
||||
|
||||
def update_stylesheet
|
||||
Stylesheet.first&.rebuild! if %w[main_color secondary_color].include? name
|
||||
end
|
||||
|
||||
def notify_privacy_policy_changed
|
||||
return unless name == 'privacy_body'
|
||||
|
||||
NotifyPrivacyUpdateWorker.perform_async(id)
|
||||
end
|
||||
|
||||
def value
|
||||
last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).first
|
||||
last_value&.value
|
||||
end
|
||||
|
||||
def last_update
|
||||
last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).first
|
||||
last_value&.created_at
|
||||
end
|
||||
|
||||
def value=(val)
|
||||
admin = User.admins.first
|
||||
save && history_values.create(user: admin, value: val)
|
||||
save && history_values.create(invoicing_profile: admin.invoicing_profile, value: val)
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Time range of duration defined by ApplicationHelper::SLOT_DURATION, slicing an Availability.
|
||||
# During a slot a Reservation is possible
|
||||
class Slot < ActiveRecord::Base
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
|
||||
@ -22,11 +26,12 @@ class Slot < ActiveRecord::Base
|
||||
super
|
||||
end
|
||||
|
||||
def is_complete?
|
||||
def complete?
|
||||
reservations.length >= availability.nb_total_places
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify_member_and_admin_slot_is_modified
|
||||
NotificationCenter.call type: 'notify_member_slot_is_modified',
|
||||
receiver: reservation.user,
|
||||
@ -47,7 +52,8 @@ class Slot < ActiveRecord::Base
|
||||
|
||||
def can_be_modified?
|
||||
return false if (start_at - Time.now) / 1.day < 1
|
||||
return true
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def dates_were_modified?
|
||||
|
40
app/models/statistic_profile.rb
Normal file
40
app/models/statistic_profile.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
AVG_DAYS_PER_YEAR = 365.2425
|
||||
|
||||
# This table will save the user's profile data needed for statistical purposes.
|
||||
# GDPR requires that an user can delete his account at any time but we need to keep the statistics original data to being able to
|
||||
# rebuild them at any time.
|
||||
# The data will be kept even if the user is deleted, but it will be unlinked from the user's account (ie. anonymized)
|
||||
class StatisticProfile < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :group
|
||||
belongs_to :role
|
||||
|
||||
has_many :subscriptions, dependent: :destroy
|
||||
accepts_nested_attributes_for :subscriptions, allow_destroy: false
|
||||
|
||||
has_many :reservations, dependent: :destroy
|
||||
accepts_nested_attributes_for :reservations, allow_destroy: false
|
||||
|
||||
# Trainings that were validated by an admin
|
||||
has_many :statistic_profile_trainings, dependent: :destroy
|
||||
has_many :trainings, through: :statistic_profile_trainings
|
||||
|
||||
# Projects that the current user is the author
|
||||
has_many :my_projects, foreign_key: :author_statistic_profile_id, class_name: 'Project', dependent: :destroy
|
||||
|
||||
def str_gender
|
||||
gender ? 'male' : 'female'
|
||||
end
|
||||
|
||||
def age
|
||||
if birthday.present?
|
||||
now = Time.now.utc.to_date
|
||||
(now - birthday).to_f / AVG_DAYS_PER_YEAR
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -1,15 +1,19 @@
|
||||
class UserTraining < ActiveRecord::Base
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Stores trainings validated per user (non validated trainings are only recorded in reservations)
|
||||
class StatisticProfileTraining < ActiveRecord::Base
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :statistic_profile
|
||||
belongs_to :training
|
||||
|
||||
after_commit :notify_user_training_valid, on: :create
|
||||
|
||||
private
|
||||
|
||||
def notify_user_training_valid
|
||||
NotificationCenter.call type: 'notify_user_training_valid',
|
||||
receiver: user,
|
||||
receiver: statistic_profile.user,
|
||||
attached_object: self
|
||||
end
|
||||
end
|
@ -81,6 +81,7 @@ class Stylesheet < ActiveRecord::Base
|
||||
.about-picture { background: linear-gradient( rgba(255,255,255,0.12), rgba(255,255,255,0.13) ), linear-gradient( #{Stylesheet.primary_with_alpha(0.78)}, #{Stylesheet.primary_with_alpha(0.82)} ), url('/about-fablab.jpg') no-repeat; }
|
||||
.social-icons > div:hover { background-color: #{Stylesheet.secondary}; }
|
||||
.profile-top { background: linear-gradient( rgba(255,255,255,0.12), rgba(255,255,255,0.13) ), linear-gradient(#{Stylesheet.primary_with_alpha(0.78)}, #{Stylesheet.primary_with_alpha(0.82)} ), url('#{CustomAsset.get_url('profile-image-file') || '/about-fablab.jpg'}') no-repeat; }
|
||||
.profile-top .social-links a:hover { background-color: #{Stylesheet.secondary} !important; border-color: #{Stylesheet.secondary} !important; }"
|
||||
.profile-top .social-links a:hover { background-color: #{Stylesheet.secondary} !important; border-color: #{Stylesheet.secondary} !important; }
|
||||
section#cookies-modal div.cookies-consent .cookies-actions button.accept { background-color: #{Stylesheet.secondary}; }"
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,7 @@ class Subscription < ActiveRecord::Base
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
|
||||
belongs_to :plan
|
||||
belongs_to :user
|
||||
belongs_to :statistic_profile
|
||||
|
||||
has_many :invoices, as: :invoiced, dependent: :destroy
|
||||
has_many :offer_days, dependent: :destroy
|
||||
@ -18,9 +18,9 @@ class Subscription < ActiveRecord::Base
|
||||
after_save :notify_partner_subscribed_plan, if: :of_partner_plan?
|
||||
|
||||
# Stripe subscription payment
|
||||
# @params [invoice] if true then subscription pay itself, dont pay with reservation
|
||||
# if false then subscription pay with reservation
|
||||
def save_with_payment(operator_id, invoice = true, coupon_code = nil)
|
||||
# @param invoice if true then subscription pay itself, dont pay with reservation
|
||||
# if false then subscription pay with reservation
|
||||
def save_with_payment(operator_profile_id, invoice = true, coupon_code = nil)
|
||||
return unless valid?
|
||||
|
||||
begin
|
||||
@ -75,7 +75,7 @@ class Subscription < ActiveRecord::Base
|
||||
# generate invoice
|
||||
stp_invoice = Stripe::Invoice.all(customer: user.stp_customer_id, limit: 1).data.first
|
||||
if invoice
|
||||
db_invoice = generate_invoice(operator_id, stp_invoice.id, coupon_code)
|
||||
db_invoice = generate_invoice(operator_profile_id, stp_invoice.id, coupon_code)
|
||||
# debit wallet
|
||||
wallet_transaction = debit_user_wallet
|
||||
if wallet_transaction
|
||||
@ -127,9 +127,9 @@ class Subscription < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# @params [invoice] if true then only the subscription is payed, without reservation
|
||||
# if false then the subscription is payed with reservation
|
||||
def save_with_local_payment(operator_id, invoice = true, coupon_code = nil)
|
||||
# @param invoice if true then only the subscription is payed, without reservation
|
||||
# if false then the subscription is payed with reservation
|
||||
def save_with_local_payment(operator_profile_id, invoice = true, coupon_code = nil)
|
||||
return false unless valid?
|
||||
|
||||
set_expiration_date
|
||||
@ -142,7 +142,7 @@ class Subscription < ActiveRecord::Base
|
||||
# debit wallet
|
||||
wallet_transaction = debit_user_wallet
|
||||
|
||||
invoc = generate_invoice(operator_id, nil, coupon_code)
|
||||
invoc = generate_invoice(operator_profile_id, nil, coupon_code)
|
||||
if wallet_transaction
|
||||
invoc.wallet_amount = @wallet_amount_debit
|
||||
invoc.wallet_transaction_id = wallet_transaction.id
|
||||
@ -152,7 +152,7 @@ class Subscription < ActiveRecord::Base
|
||||
true
|
||||
end
|
||||
|
||||
def generate_invoice(operator_id, stp_invoice_id = nil, coupon_code = nil)
|
||||
def generate_invoice(operator_profile_id, stp_invoice_id = nil, coupon_code = nil)
|
||||
coupon_id = nil
|
||||
total = plan.amount
|
||||
|
||||
@ -165,13 +165,27 @@ class Subscription < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', user: user, total: total, stp_invoice_id: stp_invoice_id, coupon_id: coupon_id, operator_id: operator_id)
|
||||
invoice.invoice_items.push InvoiceItem.new(amount: plan.amount, stp_invoice_item_id: stp_subscription_id, description: plan.name, subscription_id: self.id)
|
||||
invoice = Invoice.new(
|
||||
invoiced_id: id,
|
||||
invoiced_type: 'Subscription',
|
||||
invoicing_profile: user.invoicing_profile,
|
||||
statistic_profile: user.statistic_profile,
|
||||
total: total,
|
||||
stp_invoice_id: stp_invoice_id,
|
||||
coupon_id: coupon_id,
|
||||
operator_profile_id: operator_profile_id
|
||||
)
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: plan.amount,
|
||||
stp_invoice_item_id: stp_subscription_id,
|
||||
description: plan.name,
|
||||
subscription_id: id
|
||||
)
|
||||
invoice
|
||||
end
|
||||
|
||||
def generate_and_save_invoice(operator_id, stp_invoice_id = nil)
|
||||
generate_invoice(operator_id, stp_invoice_id).save
|
||||
def generate_and_save_invoice(operator_profile_id, stp_invoice_id = nil)
|
||||
generate_invoice(operator_profile_id, stp_invoice_id).save
|
||||
end
|
||||
|
||||
def cancel
|
||||
@ -208,11 +222,18 @@ class Subscription < ActiveRecord::Base
|
||||
expiration_date
|
||||
end
|
||||
|
||||
def free_extend(expiration)
|
||||
def free_extend(expiration, operator_profile_id)
|
||||
return false if expiration <= expired_at
|
||||
|
||||
od = offer_days.create(start_at: expired_at, end_at: expiration)
|
||||
invoice = Invoice.new(invoiced_id: od.id, invoiced_type: 'OfferDay', user: user, total: 0)
|
||||
invoice = Invoice.new(
|
||||
invoiced_id: od.id,
|
||||
invoiced_type: 'OfferDay',
|
||||
invoicing_profile: user.invoicing_profile,
|
||||
statistic_profile: user.statistic_profile,
|
||||
operator_profile_id: operator_profile_id,
|
||||
total: 0
|
||||
)
|
||||
invoice.invoice_items.push InvoiceItem.new(amount: 0, description: plan.name, subscription_id: id)
|
||||
invoice.save
|
||||
|
||||
@ -223,6 +244,10 @@ class Subscription < ActiveRecord::Base
|
||||
false
|
||||
end
|
||||
|
||||
def user
|
||||
statistic_profile.user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify_member_subscribed_plan
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user