1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-30 11:24:21 +01:00

Merge branch 'dev' for release 2.8.2

This commit is contained in:
Sylvain 2019-01-22 10:04:05 +01:00
commit c73f9424ae
182 changed files with 15001 additions and 18027 deletions

2
.gitignore vendored
View File

@ -34,6 +34,8 @@
# XLSX exports
/exports/*
# Archives of cLosed accounting periods
/accounting/*
.DS_Store

View File

@ -1,5 +1,5 @@
Metrics/LineLength:
Max: 125
Max: 130
Metrics/MethodLength:
Max: 30
Metrics/CyclomaticComplexity:
@ -7,10 +7,16 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 9
Metrics/AbcSize:
Max: 42
Max: 45
Metrics/ClassLength:
Max: 200
Style/BracesAroundHashParameters:
EnforcedStyle: context_dependent
Style/RegexpLiteral:
EnforcedStyle: slashes
Style/EmptyElse:
EnforcedStyle: empty
Style/ClassAndModuleChildren:
EnforcedStyle: compact
Style/AndOr:
EnforcedStyle: conditionals

View File

@ -1,5 +1,22 @@
# Changelog Fab Manager
## v2.8.2 2019 January 22
- Removed ability to disable invoicing for an user
- Fixed a missing translation in plan form
- Fix a bug: error handling on password recovery
- Fix a bug: error handling on machine attachment upload
- Fix a bug: first day of week is ignored in statistics custom filter
- Fix a bug: rails DSB locale is invalid
- Fix a bug: unable to delete an admin who has changed a setting
- Fix a bug: unable to create/edit a plan of 12 months or 52 weeks
- Fix a bug: Unable to search in user autocomplete fields
- Fix a bug: Invalid translation in new partner modal
- Improved user autocompletion when using multiple words
- Refactored frontend invoices translations
- Updated RailRoady 1.4.0 to 1.5.3
- [TODO DEPLOY] `bundle install`
## v2.8.1 2019 January 02
- Fix ES upgrade: when docker-compose file is using ${PWD}, the ES config volume is attached to the wrong container

View File

@ -318,7 +318,7 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
railroady (1.4.0)
railroady (1.5.3)
rails (4.2.11)
actionmailer (= 4.2.11)
actionpack (= 4.2.11)

View File

@ -160,10 +160,14 @@ This procedure is not easy to follow so if you don't need to write some code for
- **Please note**: Your password length must be between 8 and 128 characters, otherwise db:seed will be rejected. This is configured in [config/initializers/devise.rb](config/initializers/devise.rb)
```bash
# for dev
rake db:create
rake db:migrate
ADMIN_EMAIL='youradminemail' ADMIN_PASSWORD='youradminpassword' rake db:seed
rake fablab:es_build_stats
# for tests
RAILS_ENV=test rake db:create
RAILS_ENV=test rake db:migrate
```
14. Create the pids folder used by Sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml`
@ -252,6 +256,9 @@ environment.
rake db:migrate
ADMIN_EMAIL='youradminemail' ADMIN_PASSWORD='youradminpassword' rake db:seed
rake fablab:es_build_stats
# for tests
RAILS_ENV=test rake db:create
RAILS_ENV=test rake db:migrate
```
11. Start the application and visit `localhost:3000` on your browser to check that it works:

View File

@ -105,7 +105,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
* @param invoice {Object} invoice inherited from angular's $resource
*/
$scope.generateAvoirForInvoice = function (invoice) {
// open modal
// open modal
const modalInstance = $uibModal.open({
templateUrl: '<%= asset_path "admin/invoices/avoirModal.html" %>',
controller: 'AvoirModalController',
@ -119,7 +119,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
$scope.invoices.unshift(res.avoir);
return Invoice.get({ id: invoice.id }, function (data) {
invoice.has_avoir = data.has_avoir;
return growl.success(_t('refund_invoice_successfully_created'));
return growl.success(_t('invoices.refund_invoice_successfully_created'));
});
});
};
@ -193,10 +193,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
modalInstance.result.then(function (model) {
Setting.update({ name: 'invoice_reference' }, { value: model }, function (data) {
$scope.invoice.reference.model = model;
growl.success(_t('invoice_reference_successfully_saved'));
growl.success(_t('invoices.invoice_reference_successfully_saved'));
}
, function (error) {
growl.error(_t('an_error_occurred_while_saving_invoice_reference'));
growl.error(_t('invoices.an_error_occurred_while_saving_invoice_reference'));
console.error(error);
});
});
@ -231,24 +231,24 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
Setting.update({ name: 'invoice_code-value' }, { value: result.model }, function (data) {
$scope.invoice.code.model = result.model;
if (result.active) {
return growl.success(_t('invoicing_code_succesfully_saved'));
return growl.success(_t('invoices.invoicing_code_succesfully_saved'));
}
}
, function (error) {
growl.error(_t('an_error_occurred_while_saving_the_invoicing_code'));
growl.error(_t('invoices.an_error_occurred_while_saving_the_invoicing_code'));
return console.error(error);
});
return Setting.update({ name: 'invoice_code-active' }, { value: result.active ? 'true' : 'false' }, function (data) {
$scope.invoice.code.active = result.active;
if (result.active) {
return growl.success(_t('code_successfully_activated'));
return growl.success(_t('invoices.code_successfully_activated'));
} else {
return growl.success(_t('code_successfully_disabled'));
return growl.success(_t('invoices.code_successfully_disabled'));
}
}
, function (error) {
growl.error(_t('an_error_occurred_while_activating_the_invoicing_code'));
growl.error(_t('invoices.an_error_occurred_while_activating_the_invoicing_code'));
return console.error(error);
});
});
@ -277,10 +277,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
return modalInstance.result.then(function (model) {
Setting.update({ name: 'invoice_order-nb' }, { value: model }, function (data) {
$scope.invoice.number.model = model;
return growl.success(_t('order_number_successfully_saved'));
return growl.success(_t('invoices.order_number_successfully_saved'));
}
, function (error) {
growl.error(_t('an_error_occurred_while_saving_the_order_number'));
growl.error(_t('invoices.an_error_occurred_while_saving_the_order_number'));
return console.error(error);
});
});
@ -301,11 +301,15 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
},
active () {
return $scope.invoice.VAT.active;
},
history () {
return Setting.get({ name: 'invoice_VAT-rate', history: true }).$promise;
}
},
controller: ['$scope', '$uibModalInstance', 'rate', 'active', function ($scope, $uibModalInstance, rate, active) {
controller: ['$scope', '$uibModalInstance', 'rate', 'active', 'history', function ($scope, $uibModalInstance, rate, active, history) {
$scope.rate = rate;
$scope.isSelected = active;
$scope.history = history.setting.history;
$scope.ok = function () { $uibModalInstance.close({ rate: $scope.rate, active: $scope.isSelected }); };
return $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
@ -316,24 +320,24 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
Setting.update({ name: 'invoice_VAT-rate' }, { value: result.rate + '' }, function (data) {
$scope.invoice.VAT.rate = result.rate;
if (result.active) {
return growl.success(_t('VAT_rate_successfully_saved'));
return growl.success(_t('invoices.VAT_rate_successfully_saved'));
}
}
, function (error) {
growl.error(_t('an_error_occurred_while_saving_the_VAT_rate'));
growl.error(_t('invoices.an_error_occurred_while_saving_the_VAT_rate'));
return console.error(error);
});
return Setting.update({ name: 'invoice_VAT-active' }, { value: result.active ? 'true' : 'false' }, function (data) {
$scope.invoice.VAT.active = result.active;
if (result.active) {
return growl.success(_t('VAT_successfully_activated'));
return growl.success(_t('invoices.VAT_successfully_activated'));
} else {
return growl.success(_t('VAT_successfully_disabled'));
return growl.success(_t('invoices.VAT_successfully_disabled'));
}
}
, function (error) {
growl.error(_t('an_error_occurred_while_activating_the_VAT'));
growl.error(_t('invoices.an_error_occurred_while_activating_the_VAT'));
return console.error(error);
});
});
@ -346,10 +350,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
const parsed = parseHtml($scope.invoice.text.content);
return Setting.update({ name: 'invoice_text' }, { value: parsed }, function (data) {
$scope.invoice.text.content = parsed;
return growl.success(_t('text_successfully_saved'));
return growl.success(_t('invoices.text_successfully_saved'));
}
, function (error) {
growl.error(_t('an_error_occurred_while_saving_the_text'));
growl.error(_t('invoices.an_error_occurred_while_saving_the_text'));
return console.error(error);
});
};
@ -361,10 +365,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
const parsed = parseHtml($scope.invoice.legals.content);
return Setting.update({ name: 'invoice_legals' }, { value: parsed }, function (data) {
$scope.invoice.legals.content = parsed;
return growl.success(_t('address_and_legal_information_successfully_saved'));
return growl.success(_t('invoices.address_and_legal_information_successfully_saved'));
}
, function (error) {
growl.error(_t('an_error_occurred_while_saving_the_address_and_the_legal_information'));
growl.error(_t('invoices.an_error_occurred_while_saving_the_address_and_the_legal_information'));
return console.error(error);
});
};
@ -418,9 +422,9 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
return Setting.update(
{ name: 'invoice_logo' },
{ value: $scope.invoice.logo.base64 },
function (data) { growl.success(_t('logo_successfully_saved')); },
function (data) { growl.success(_t('invoices.logo_successfully_saved')); },
function (error) {
growl.error(_t('an_error_occurred_while_saving_the_logo'));
growl.error(_t('invoices.an_error_occurred_while_saving_the_logo'));
return console.error(error);
}
);
@ -498,7 +502,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
*/
Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModalInstance', 'invoice', 'Invoice', 'growl', '_t',
function ($scope, $uibModalInstance, invoice, Invoice, growl, _t) {
/* PUBLIC SCOPE */
/* PUBLIC SCOPE */
// invoice linked to the current refund
$scope.invoice = invoice;
@ -515,11 +519,11 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
// Possible refunding methods
$scope.avoirModes = [
{ name: _t('none'), value: 'none' },
{ name: _t('by_cash'), value: 'cash' },
{ name: _t('by_cheque'), value: 'cheque' },
{ name: _t('by_transfer'), value: 'transfer' },
{ name: _t('by_wallet'), value: 'wallet' }
{ name: _t('invoices.none'), value: 'none' },
{ name: _t('invoices.by_cash'), value: 'cash' },
{ name: _t('invoices.by_cheque'), value: 'cheque' },
{ name: _t('invoices.by_transfer'), value: 'transfer' },
{ name: _t('invoices.by_wallet'), value: 'wallet' }
];
// If a subscription was took with the current invoice, should it be canceled or not
@ -542,14 +546,14 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
$scope.openDatePicker = function ($event) {
$event.preventDefault();
$event.stopPropagation();
return $scope.datePicker.opened = true;
$scope.datePicker.opened = true;
};
/**
* Validate the refunding and generate a refund invoice
*/
$scope.ok = function () {
// check that at least 1 element of the invoice is refunded
// check that at least 1 element of the invoice is refunded
$scope.avoir.invoice_items_ids = [];
for (let itemId in $scope.partial) {
const refundItem = $scope.partial[itemId];
@ -557,7 +561,7 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
}
if ($scope.avoir.invoice_items_ids.length === 0) {
return growl.error(_t('you_must_select_at_least_one_element_to_create_a_refund'));
return growl.error(_t('invoices.you_must_select_at_least_one_element_to_create_a_refund'));
} else {
return Invoice.save(
{ avoir: $scope.avoir },
@ -565,13 +569,13 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
$uibModalInstance.close({ avoir, invoice: $scope.invoice });
},
function (err) { // failed
growl.error(_t('unable_to_create_the_refund'));
growl.error(_t('invoices.unable_to_create_the_refund'));
}
);
}
};
/**q
/**
* Cancel the refund, dismiss the modal window
*/
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
@ -592,7 +596,7 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
});
if (invoice.stripe) {
return $scope.avoirModes.push({ name: _t('online_payment'), value: 'stripe' });
return $scope.avoirModes.push({ name: _t('invoices.online_payment'), value: 'stripe' });
}
};

View File

@ -94,9 +94,9 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
minDate: null,
maxDate: moment().toDate(),
options: {
startingDay: 1
startingDay: Fablab.weekStartingDay
}
} // France: the week starts on monday
}
};
// available custom filters

View File

@ -142,7 +142,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
$scope.user.organization = orga;
// display errors
angular.forEach(error.data.errors, function (v, k) {
angular.forEach(function (v, err) {
angular.forEach(v, function (err) {
$scope.alerts.push({
msg: k + ': ' + err,
type: 'danger'
@ -175,9 +175,9 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
return $scope.changePassword = function () {
$scope.alerts = [];
return $http.put('/users/password.json', { user: $scope.user }).success(function () { $uibModalInstance.close(); }).error(function (data) {
angular.forEach(data.errors, function (v, k) {
angular.forEach(function (v, err) {
return $http.put('/users/password.json', { user: $scope.user }).then(function () { $uibModalInstance.close(); }).catch(function (data) {
angular.forEach(data.data.errors, function (v, k) {
angular.forEach(v, function (err) {
$scope.alerts.push({
msg: k + ': ' + err,
type: 'danger'
@ -400,7 +400,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
$scope.user = { email: '' };
return $scope.sendReset = function () {
$scope.alerts = [];
return $http.post('/users/password.json', { user: $scope.user }).success(function () { $uibModalInstance.close(); }).error(function () {
return $http.post('/users/password.json', { user: $scope.user }).then(function () { $uibModalInstance.close(); }).catch(function () {
$scope.alerts.push({
msg: _t('your_email_address_is_unknown'),
type: 'danger'

View File

@ -43,7 +43,7 @@ class MachinesController {
if ((content.id == null)) {
$scope.alerts = [];
angular.forEach(content, function (v, k) {
angular.forEach(function (v, err) {
angular.forEach(v, function (err) {
$scope.alerts.push({
msg: k + ': ' + err,
type: 'danger'

View File

@ -178,4 +178,9 @@
border-radius: 5px;
font-size: small;
}
}
}
.no-user-label {
font-style: italic;
color: #5a5a5a;
}

View File

@ -1,10 +1,10 @@
<div class="modal-header">
<h3 class="text-center red" translate>{{ 'create_a_refund_on_this_invoice' }}</h3>
<h3 class="text-center red" translate>{{ 'invoices.create_a_refund_on_this_invoice' }}</h3>
</div>
<div class="modal-body">
<form name="avoirForm" novalidate="novalidate">
<div class="form-group" ng-class="{'has-error': avoirForm.avoir_date.$dirty && avoirForm.avoir_date.$invalid }">
<label translate>{{ 'creation_date_for_the_refund' }}</label>
<label translate>{{ 'invoices.creation_date_for_the_refund' }}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text"
@ -18,24 +18,24 @@
ng-click="openDatePicker($event)"
required/>
</div>
<span class="help-block" ng-show="avoirForm.avoir_date.$dirty && avoirForm.avoir_date.$error.required" translate>{{ 'creation_date_is_required' }}</span>
<span class="help-block" ng-show="avoirForm.avoir_date.$dirty && avoirForm.avoir_date.$error.required" translate>{{ 'invoices.creation_date_is_required' }}</span>
</div>
<div class="form-group">
<label translate>{{ 'refund_mode' }}</label>
<label translate>{{ 'invoices.refund_mode' }}</label>
<select class="form-control m-t-sm" name="avoir_mode" ng-model="avoir.avoir_mode" ng-options="mode.value as mode.name for mode in avoirModes" required></select>
</div>
<div class="form-group" ng-if="invoice.is_subscription_invoice">
<label translate>{{ 'do_you_want_to_disable_the_user_s_subscription' }}</label>
<label translate>{{ 'invoices.do_you_want_to_disable_the_user_s_subscription' }}</label>
<select class="form-control m-t-sm" name="subscription_to_expire" ng-model="avoir.subscription_to_expire" ng-options="value as key for (key, value) in subscriptionExpireOptions" required></select>
</div>
<div ng-show="!invoice.is_subscription_invoice && invoice.items.length > 1" class="form-group">
<label translate>{{ 'elements_to_refund' }}</label>
<label translate>{{ 'invoices.elements_to_refund' }}</label>
<table class="table partial-avoir-table">
<thead>
<tr>
<th class="input-col"></th>
<th class="label-col" translate>{{ 'description' }}</th>
<th class="amount-col" translate>{{ 'price' }}</th>
<th class="label-col" translate>{{ 'invoices.description' }}</th>
<th class="amount-col" translate>{{ 'invoices.price' }}</th>
</tr>
</thead>
<tbody>
@ -48,8 +48,8 @@
</table>
</div>
<div>
<label for="description" translate>{{ 'description_(optional)' }}</label>
<p translate>{{ 'will_appear_on_the_refund_invoice' }}</p>
<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>
</form>
@ -57,4 +57,4 @@
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" ng-disabled="avoirForm.$invalid" translate>{{ 'confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
</div>
</div>

View File

@ -7,7 +7,7 @@
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1 translate>{{ 'invoices' }}</h1>
<h1 translate>{{ 'invoices.invoices' }}</h1>
</section>
</div>
@ -18,14 +18,14 @@
<div class="row">
<div class="col-md-12">
<uib-tabset justified="true">
<uib-tab heading="{{ 'invoices_list' | translate }}">
<h3 class="m-t-xs"><i class="fa fa-filter"></i> {{ 'filter_invoices' | translate }}</h3>
<uib-tab heading="{{ 'invoices.invoices_list' | translate }}">
<h3 class="m-t-xs"><i class="fa fa-filter"></i> {{ 'invoices.filter_invoices' | translate }}</h3>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" translate>{{ 'invoice_#_' }}</span>
<span class="input-group-addon" translate>{{ 'invoices.invoice_#_' }}</span>
<input type="text" ng-model="searchInvoice.reference" class="form-control" placeholder="" ng-change="handleFilterChange()">
</div>
</div>
@ -34,7 +34,7 @@
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" translate>{{ 'customer_' }}</span>
<span class="input-group-addon" translate>{{ 'invoices.customer_' }}</span>
<input type="text" ng-model="searchInvoice.name" class="form-control" placeholder="" ng-change="handleFilterChange()">
</div>
</div>
@ -43,7 +43,7 @@
<div class="col-md-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">{{ "date_" | translate }}</span>
<span class="input-group-addon">{{ "invoices.date_" | translate }}</span>
<input type="date" ng-model="searchInvoice.date" class="form-control" ng-change="handleFilterChange()">
</div>
</div>
@ -57,13 +57,13 @@
<table class="table" ng-if="invoices.length > 0">
<thead>
<tr>
<th style="width:15%"><a href="" ng-click="setOrderInvoice('reference')">{{ '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_#' | 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')">{{ 'date' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='date', 'fa fa-sort-numeric-desc': orderInvoice=='-date', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width: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>
<th style="width:10%"><a href="" ng-click="setOrderInvoice('total')"> {{ 'price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderInvoice('total')"> {{ 'invoices.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderInvoice('name')">{{ 'customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderInvoice('name')">{{ 'invoices.customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
<th style="width:30%"></th>
</tr>
@ -78,13 +78,13 @@
<td>
<div class="buttons">
<a class="btn btn-default" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="!invoice.is_avoir">
<i class="fa fa-file-pdf-o"></i> {{ 'download_the_invoice' | translate }}
<i class="fa fa-file-pdf-o"></i> {{ 'invoices.download_the_invoice' | translate }}
</a>
<a class="btn btn-default" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="invoice.is_avoir">
<i class="fa fa-file-pdf-o"></i> {{ 'download_the_credit_note' | translate }}
<i class="fa fa-file-pdf-o"></i> {{ 'invoices.download_the_credit_note' | translate }}
</a>
<a class="btn btn-default" ng-click="generateAvoirForInvoice(invoice)" ng-if="(!invoice.has_avoir || invoice.has_avoir == 'partial') && !invoice.is_avoir && !invoice.prevent_refund">
<i class="fa fa-reply"></i> {{ 'credit_note' | translate }}
<i class="fa fa-reply"></i> {{ 'invoices.credit_note' | translate }}
</a>
</div>
</td>
@ -92,9 +92,9 @@
</tbody>
</table>
<div class="text-center">
<button class="btn btn-warning" ng-click="showNextInvoices()" ng-hide="noMoreResults"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'display_more_invoices' | translate }}</button>
<button class="btn btn-warning" ng-click="showNextInvoices()" ng-hide="noMoreResults"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'invoices.display_more_invoices' | translate }}</button>
</div>
<p ng-if="invoices.length == 0" translate>{{ 'no_invoices_for_now' }}</p>
<p ng-if="invoices.length == 0" translate>{{ 'invoices.no_invoices_for_now' }}</p>
</div>
</div>
@ -103,7 +103,7 @@
<uib-tab heading="{{ 'invoicing_settings' | translate }}">
<uib-tab heading="{{ 'invoices.invoicing_settings' | translate }}">
<form class="invoice-placeholder">
<div class="invoice-logo" style="background-image: url({{invoice.logo}});">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!invoice.logo" class="img-responsive">
@ -111,79 +111,79 @@
<div class="tools-box">
<div class="btn-group">
<div class="btn btn-default btn-file">
<i class="fa fa-edit"></i> {{ 'change_logo' | translate }}
<i class="fa fa-edit"></i> {{ 'invoices.change_logo' | translate }}
<input type="file" accept="image/png,image/jpeg,image/x-png,image/pjpeg" name="invoice[logo][attachment]" ng-model="invoice.logo" base-sixty-four-input>
</div>
</div>
</div>
</div>
<div class="invoice-buyer-infos">
<strong translate>{{ 'john_smith' }}</strong>
<div translate>{{ 'john_smith@example_com' }}</div>
<strong translate>{{ 'invoices.john_smith' }}</strong>
<div translate>{{ 'invoices.john_smith@example_com' }}</div>
</div>
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'invoice_reference_' | translate }} {{mkReference()}}</div>
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'code_' | translate }} {{invoice.code.model}}</div>
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'code_disabled' }}</div>
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'order_#' | translate }} {{mkNumber()}}</div>
<div class="invoice-date">{{ 'invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</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-date">{{ 'invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
<div class="invoice-object">
{{ 'object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
{{ 'invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
</div>
<div class="invoice-data">
{{ 'order_summary' | translate }}
{{ 'invoices.order_summary' | translate }}
<table>
<thead>
<tr>
<th translate>{{ 'details' }}</th>
<th class="right" translate>{{ 'amount' }}</th>
<th translate>{{ 'invoices.details' }}</th>
<th class="right" translate>{{ 'invoices.amount' }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ 'machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
<td>{{ 'invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
<td class="right">{{30.0 | currency}}</td>
</tr>
<tr class="invoice-total" ng-class="{'bold vat-line':invoice.VAT.active}">
<td ng-show="!invoice.VAT.active" translate>{{ 'total_amount' }}</td>
<td ng-show="invoice.VAT.active" translate>{{ 'total_including_all_taxes' }}</td>
<td ng-show="!invoice.VAT.active" translate>{{ 'invoices.total_amount' }}</td>
<td ng-show="invoice.VAT.active" translate>{{ 'invoices.total_including_all_taxes' }}</td>
<td class="right">{{30.0 | currency}}</td>
</tr>
<tr class="invoice-vat invoice-activable" ng-click="openEditVAT()" ng-show="!invoice.VAT.active">
<td translate>{{ 'VAT_disabled' }}</td>
<td translate>{{ 'invoices.VAT_disabled' }}</td>
<td></td>
</tr>
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
<td>{{ 'including_VAT' | translate }} {{invoice.VAT.rate}} %</td>
<td>{{ 'invoices.including_VAT' | translate }} {{invoice.VAT.rate}} %</td>
<td>{{30-(30/(invoice.VAT.rate/100+1)) | currency}}</td>
</tr>
<tr class="invoice-ht vat-line italic" ng-show="invoice.VAT.active">
<td translate>{{ 'including_total_excluding_taxes' }}</td>
<td translate>{{ 'invoices.including_total_excluding_taxes' }}</td>
<td>{{30/(invoice.VAT.rate/100+1) | currency}}</td>
</tr>
<tr class="invoice-payed vat-line bold" ng-show="invoice.VAT.active">
<td translate>{{ 'including_amount_payed_on_ordering' }}</td>
<td translate>{{ 'invoices.including_amount_payed_on_ordering' }}</td>
<td>{{30.0 | currency}}</td>
</tr>
</tbody>
</table>
<p class="invoice-payment" translate translate-values="{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT'), AMOUNT:(30.0 | currency)}">
{{ 'settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
{{ 'invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
</p>
</div>
<div medium-editor class="invoice-text invoice-editable" ng-model="invoice.text.content"
options='{
"placeholder": "{{ "important_notes" | translate }}",
"placeholder": "{{ "invoices.important_notes" | translate }}",
"buttons": ["underline"]
}'
ng-blur="textEditEnd($event)">
</div>
<div medium-editor class="invoice-legals invoice-editable" ng-model="invoice.legals.content"
options='{
"placeholder": "{{ "address_and_legal_information" | translate }}",
"placeholder": "{{ "invoices.address_and_legal_information" | translate }}",
"buttons": ["bold", "underline"]
}'
ng-blur="legalsEditEnd($event)">
@ -199,28 +199,28 @@
<script type="text/ng-template" id="editReference.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'invoice_reference' }}</h3>
<h3 class="modal-title" translate>{{ 'invoices.invoice_reference' }}</h3>
</div>
<div class="modal-body row">
<div class="elements col-md-4">
<h4>Éléments</h4>
<ul>
<li ng-click="invoice.reference.help = 'addYear.html'">{{ 'year' | translate }}</li>
<li ng-click="invoice.reference.help = 'addMonth.html'">{{ 'month' | translate }}</li>
<li ng-click="invoice.reference.help = 'addDay.html'">{{ 'day' | translate }}</li>
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ '#_of_invoice' | translate }}</li>
<li ng-click="invoice.reference.help = 'addOnlineInfo.html'">{{ 'online_sales' | translate }}</li>
<%# <li ng-click="invoice.reference.help = 'addWalletInfo.html'">{{ 'wallet' | translate }}</li> %>
<li ng-click="invoice.reference.help = 'addRefundInfo.html'">{{ 'refund' | translate }}</li>
<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 = '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>
</ul>
</div>
<div class="col-md-8">
<div class="model">
<h4 translate>{{ 'model' }}</h4>
<h4 translate>{{ 'invoices.model' }}</h4>
<input type="text" class="form-control" ng-model="model">
</div>
<div class="help">
<h4 translate>{{ 'documentation' }}</h4>
<h4 translate>{{ 'invoices.documentation' }}</h4>
<ng-include src="invoice.reference.help" autoscroll="true">
</ng-include>
</div>
@ -235,83 +235,83 @@
<script type="text/ng-template" id="addYear.html">
<table class="invoice-element-legend">
<tr><td><strong>YY</strong></td><td translate>{{ '2_digits_year_(eg_70)' }}</td></tr>
<tr><td><strong>YYYY</strong></td><td translate>{{ '4_digits_year_(eg_1970)' }}</td></tr>
<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>
</table>
</script>
<script type="text/ng-template" id="addMonth.html">
<table class="invoice-element-legend">
<tr><td><strong>M</strong></td><td translate>{{ 'month_number_(eg_1)' }}</td></tr>
<tr><td><strong>MM</strong></td><td translate>{{ '2_digits_month_number_(eg_01)' }}</td></tr>
<tr><td><strong>MMM</strong></td><td translate>{{ '3_characters_month_name_(eg_JAN)' }}</td></tr>
<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>
</table>
</script>
<script type="text/ng-template" id="addDay.html">
<table class="invoice-element-legend">
<tr><td><strong>D</strong></td><td translate>{{ 'day_in_the_month_(eg_1)' }}</td></tr>
<tr><td><strong>DD</strong></td><td translate>{{ '2_digits_day_in_the_month_(eg_01)' }}</td></tr>
<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>
</table>
</script>
<script type="text/ng-template" id="addInvoiceNumber.html">
<table class="invoice-element-legend">
<tr><td><strong>dd...dd</strong></td><td translate>{{ '(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>{{ '(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>{{ '(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_(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>
</table>
<span class="bottom-notes" translate>{{ 'beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
<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>{{ '(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order)' }}</td></tr>
<tr><td><strong>dd...dd</strong></td><td translate>{{ '(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>{{ '(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>{{ '(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_(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>
</table>
<span class="bottom-notes" translate>{{ 'beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
<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>{{ 'add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ '(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_X[/VL]_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>{{ 'add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ '(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_W[/PM]_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>{{ 'add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ '(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_R[/A]_will_add_/A_to_the_refund_invoices)' | translate }}</td></tr>
</table>
</script>
<script type="text/ng-template" id="editCode.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'code' }}</h3>
<h3 class="modal-title" translate>{{ 'invoices.code' }}</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="enableCode" class="control-label" translate>{{ 'enable_the_code' }}</label>
<label for="enableCode" class="control-label" translate>{{ 'invoices.enable_the_code' }}</label>
<input bs-switch
ng-model="isSelected"
id="enableCode"
type="checkbox"
class="form-control m-l-sm"
switch-on-text="{{ 'enabled' | translate }}"
switch-off-text="{{ 'disabled' | translate }}"
switch-on-text="{{ 'invoices.enabled' | translate }}"
switch-off-text="{{ 'invoices.disabled' | translate }}"
switch-animate="true"/>
</div>
<div class="form-group" ng-show="isSelected">
<label for="codeModel" class="control-label" translate>{{ 'code' }}</label>
<label for="codeModel" class="control-label" translate>{{ 'invoices.code' }}</label>
<input id="codeModel" type="text" ng-model="codeModel" class="form-control"/>
</div>
</div>
@ -327,25 +327,25 @@
<script type="text/ng-template" id="editNumber.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'order_number' }}</h3>
<h3 class="modal-title" translate>{{ 'invoices.order_number' }}</h3>
</div>
<div class="modal-body row">
<div class="elements col-md-4">
<h4 translate>{{ 'elements' }}</h4>
<h4 translate>{{ 'invoices.elements' }}</h4>
<ul>
<li ng-click="invoice.number.help = 'addYear.html'">{{ 'year' | translate }}</li>
<li ng-click="invoice.number.help = 'addMonth.html'">{{ 'month' | translate }}</li>
<li ng-click="invoice.number.help = 'addDay.html'">{{ 'day' | translate }}</li>
<li ng-click="invoice.number.help = 'addOrderNumber.html'">{{ 'order_#' | translate }}</li>
<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>
</ul>
</div>
<div class="col-md-8">
<div class="model">
<h4 translate>{{ 'model' }}</h4>
<h4 translate>{{ 'invoices.model' }}</h4>
<input type="text" class="form-control" ng-model="model">
</div>
<div class="help">
<h4 translate>{{ 'documentation' }}</h4>
<h4 translate>{{ 'invoices.documentation' }}</h4>
<ng-include src="invoice.number.help" autoscroll="true">
</ng-include>
</div>
@ -362,28 +362,48 @@
<script type="text/ng-template" id="editVAT.html">
<div class="custom-invoice">
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'VAT' }}</h3>
<h3 class="modal-title" translate>{{ 'invoices.VAT' }}</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="enableVAT" class="control-label" translate>{{ 'enable_VAT' }}</label>
<label for="enableVAT" class="control-label" translate>{{ 'invoices.enable_VAT' }}</label>
<input bs-switch
ng-model="isSelected"
id="enableVAT"
type="checkbox"
class="form-control m-l-sm"
switch-on-text="{{ 'enabled' | translate }}"
switch-off-text="{{ 'disabled' | translate }}"
switch-on-text="{{ 'invoices.enabled' | translate }}"
switch-off-text="{{ 'invoices.disabled' | translate }}"
switch-animate="true"/>
</div>
<div class="form-group" ng-show="isSelected">
<label for="vatRate" class="control-label" translate>{{ 'VAT_rate' }}</label>
<label for="vatRate" class="control-label" translate>{{ 'invoices.VAT_rate' }}</label>
<div class="input-group">
<span class="input-group-addon">% </span>
<input id="vatRate" type="number" ng-model="rate" class="form-control" min="0" max="100"/>
</div>
</div>
<div class="m-t-lg">
<h4 translate>{{ 'invoices.VAT_history' }}</h4>
<table class="table">
<head>
<tr>
<th translate>{{ 'invoices.VAT_rate' }}</th>
<th translate>{{ 'invoices.changed_at' }}</th>
<th translate>{{ 'invoices.changed_by' }}</th>
</tr>
</head>
<tbody>
<tr ng-repeat="value in history">
<td>{{value.value}} %</td>
<td>{{value.created_at | amDateFormat:'L LT'}}</td>
<td>{{value.user.name}}<span class="no-user-label" ng-hide="value.user" translate>{{ 'invoices.deleted_user' }}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>

View File

@ -11,26 +11,6 @@
</div>
</div>
<div class="form-group">
<label for="user[invoicing_disabled]" class="control-label col-sm-3" translate>
{{ 'disable_invoices_generation' }}
</label>
<div class="col-sm-2">
<input bs-switch
ng-model="user.invoicing_disabled"
name="user[invoicing_disabled]"
type="checkbox"
class="form-control"
switch-on-text="{{ 'yes' | translate }}"
switch-off-text="{{ 'no' | translate }}"
switch-animate="true"/>
<input type="hidden" name="user[invoicing_disabled]" value="{{user.invoicing_disabled}}">
</div>
<div class="col-sm-7">
<span class="help-block"><i class="fa fa-warning"></i> {{ 'no_more_invoices_will_be_generated_for_' | translate }} <strong translate>{{ '_the_payments_carried_out_at_the_reception_' }}</strong> {{ '_regarding_this_user' | translate }}</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" translate>{{ 'trainings' }}</label>
<div class="col-sm-10">

View File

@ -1,5 +1,5 @@
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'new_partner' }}</h3>
<h3 class="modal-title" translate>{{ 'plan_form.new_partner' }}</h3>
</div>
<div class="modal-body m-lg">
<form name="partnerForm">

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Abuse.
# Typical action is an user reporting an abuse on a project
class API::AbusesController < API::ApiController
before_action :authenticate_user!, except: :create

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type User with role 'admin'.
class API::AdminsController < API::ApiController
before_action :authenticate_user!
@ -31,7 +34,7 @@ class API::AdminsController < API::ApiController
def destroy
@admin = User.admins.find(params[:id])
if current_user.is_admin? and @admin != current_user
if current_user.admin? && @admin != current_user
@admin.destroy
head :no_content
else

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type AgeRange
# AgeRange are used in Events
class API::AgeRangesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_age_range, only: [:show, :update, :destroy]
before_action :set_age_range, only: %i[show update destroy]
def index
@age_ranges = AgeRange.all
end
def show
end
def show; end
def create
authorize AgeRange
@ -19,7 +22,6 @@ class API::AgeRangesController < API::ApiController
end
end
def update
authorize AgeRange
if @age_range.update(age_range_params)
@ -39,6 +41,7 @@ class API::AgeRangesController < API::ApiController
end
private
def set_age_range
@age_range = AgeRange.find(params[:id])
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class API::ApiController < ApplicationController

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
# API Controller for resources of type AuthProvider
# AuthProvider are used to connect users through single-sign on systems
class API::AuthProvidersController < API::ApiController
before_action :set_provider, only: [:show, :update, :destroy]
before_action :set_provider, only: %i[show update destroy]
def index
@providers = policy_scope(AuthProvider)
end
@ -57,33 +61,33 @@ class API::AuthProvidersController < API::ApiController
NotificationCenter.call type: 'notify_user_auth_migration',
receiver: user,
attached_object: user
render json: {status: 'processing'}, status: :ok
render json: { status: 'processing' }, status: :ok
else
render json: {status: 'error', error: I18n.t('members.current_authentication_method_no_code')}, status: :bad_request
render json: { status: 'error', error: I18n.t('members.current_authentication_method_no_code') }, status: :bad_request
end
else
render json: {status: 'error', error: I18n.t('members.requested_account_does_not_exists')}, status: :bad_request
render json: { status: 'error', error: I18n.t('members.requested_account_does_not_exists') }, status: :bad_request
end
end
private
def set_provider
@provider = AuthProvider.find(params[:id])
end
def set_provider
@provider = AuthProvider.find(params[:id])
end
def provider_params
if params['auth_provider']['providable_type'] == DatabaseProvider.name
params.require(:auth_provider).permit(:name, :providable_type)
elsif params['auth_provider']['providable_type'] == OAuth2Provider.name
params.require(:auth_provider).permit(:name, :providable_type, providable_attributes: [
:id, :base_url, :token_endpoint, :authorization_endpoint, :logout_endpoint, :profile_url, :client_id, :client_secret,
o_auth2_mappings_attributes: [
:id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, :_destroy,
transformation: [:type, :format, :true_value, :false_value, mapping: [:from, :to]]
]
])
end
def provider_params
if params['auth_provider']['providable_type'] == DatabaseProvider.name
params.require(:auth_provider).permit(:name, :providable_type)
elsif params['auth_provider']['providable_type'] == OAuth2Provider.name
params.require(:auth_provider)
.permit(:name, :providable_type,
providable_attributes: [:id, :base_url, :token_endpoint, :authorization_endpoint, :logout_endpoint,
:profile_url, :client_id, :client_secret,
o_auth2_mappings_attributes: [:id, :local_model, :local_field, :api_field,
:api_endpoint, :api_data_type, :_destroy,
transformation: [:type, :format, :true_value,
:false_value, mapping: %i[from to]]]])
end
end
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Availability
class API::AvailabilitiesController < API::ApiController
include FablabConfiguration
@ -10,94 +13,30 @@ class API::AvailabilitiesController < API::ApiController
authorize Availability
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces).where.not(available_type: 'event')
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces)
.where.not(available_type: 'event')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
if fablab_spaces_deactivated?
@availabilities = @availabilities.where.not(available_type: 'space')
end
@availabilities = @availabilities.where.not(available_type: 'space') if fablab_spaces_deactivated?
end
def public
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
@reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user)
.where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
@reservations = Reservation.includes(:slots, user: [:profile])
.references(:slots, :user)
.where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
# request for 1 single day
if in_same_day(start_date, end_date)
# trainings, events
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots)
.where(available_type: %w[training event])
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
# machines
@machine_availabilities = Availability.includes(:tags, :machines)
.where(available_type: 'machines')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@machine_slots = []
@machine_availabilities.each do |a|
a.machines.each do |machine|
next unless params[:m]&.include?(machine.id.to_s)
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
machine: machine,
title: machine.name
)
slot = verify_machine_is_reserved(slot, @reservations, current_user, '')
@machine_slots << slot
end
end
end
# spaces
@space_availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@space_availabilities.where(available_id: params[:s]) if params[:s]
@space_slots = []
@space_availabilities.each do |a|
space = a.spaces.first
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: space,
title: space.name
)
slot = verify_space_is_reserved(slot, @reservations, current_user, '')
@space_slots << slot
end
end
@availabilities = [].concat(@training_and_event_availabilities).concat(@machine_slots).concat(@space_slots)
# request for many days (week or month)
else
@availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@availabilities.each do |a|
if a.available_type == 'training' or a.available_type == 'event'
a = verify_training_event_is_reserved(a, @reservations, current_user)
elsif a.available_type == 'space'
a.is_reserved = is_reserved_availability(a, current_user)
end
end
end
machine_ids = params[:m] || []
@title_filter = {machine_ids: machine_ids.map(&:to_i)}
service = Availabilities::PublicAvailabilitiesService.new(current_user)
@availabilities = service.public_availabilities(
start_date,
end_date,
@reservations,
machines: machine_ids, spaces: params[:s]
)
@title_filter = { machine_ids: machine_ids.map(&:to_i) }
@availabilities = filter_availabilites(@availabilities)
end
@ -134,138 +73,22 @@ class API::AvailabilitiesController < API::ApiController
end
def machine
@user = if params[:member_id]
User.find(params[:member_id])
else
current_user
end
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
@machine = Machine.friendly.find(params[:machine_id])
@slots = []
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id)
.includes(:slots, user: [:profile])
.references(:slots, :user)
.where('slots.start_at > ?', Time.now)
if @user.is_admin?
@availabilities = @machine.availabilities.includes(:tags)
.where("end_at > ? AND available_type = 'machines'", Time.now)
.where(lock: false)
else
end_at = @visi_max_other
end_at = @visi_max_year if is_subscription_year(@user)
@availabilities = @machine.availabilities
.includes(:tags)
.where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end
@availabilities.each do |a|
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
@current_user_role = current_user.admin? ? 'admin' : 'user'
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
machine: @machine,
title: ''
)
slot = verify_machine_is_reserved(slot, @reservations, current_user, @current_user_role)
@slots << slot
end
end
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@slots = service.machines(params[:machine_id], user)
end
def trainings
@user = if params[:member_id]
User.find(params[:member_id])
else
current_user
end
@slots = []
# first, we get the already-made reservations
@reservations = @user.reservations.where("reservable_type = 'Training'")
@reservations = @reservations.where('reservable_id = :id', id: params[:training_id].to_i) if params[:training_id].is_number?
@reservations = @reservations.joins(:slots).where('slots.start_at > ?', Time.now)
# what is requested?
# 1) a single training
@availabilities = if params[:training_id].is_number? or (params[:training_id].length > 0 and params[:training_id] != 'all')
Training.friendly.find(params[:training_id]).availabilities
# 2) all trainings
else
Availability.trainings
end
# who made the request?
# 1) an admin (he can see all future availabilities)
if current_user.is_admin?
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines])
.where('availabilities.start_at > ?', Time.now)
.where(lock: false)
# 2) an user (he cannot see availabilities further than 1 (or 3) months)
else
end_at = @visi_max_year
end_at = @visi_max_year if can_show_slot_plus_three_months(@user)
@availabilities = @availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines])
.where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end
# finally, we merge the availabilities with the reservations
@availabilities.each do |a|
a = verify_training_event_is_reserved(a, @reservations, @user)
end
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@availabilities = service.trainings(params[:training_id], user)
end
def spaces
@user = if params[:member_id]
User.find(params[:member_id])
else
current_user
end
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
@space = Space.friendly.find(params[:space_id])
@slots = []
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id)
.includes(:slots, user: [:profile]).references(:slots, :user)
.where('slots.start_at > ?', Time.now)
if current_user.is_admin?
@availabilities = @space.availabilities.includes(:tags)
.where("end_at > ? AND available_type = 'space'", Time.now)
.where(lock: false)
else
end_at = @visi_max_other
end_at = @visi_max_year if is_subscription_year(@user)
@availabilities = @space.availabilities.includes(:tags)
.where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end
@availabilities.each do |a|
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
@current_user_role = current_user.admin? ? 'admin' : 'user'
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: @space,
title: ''
)
slot = verify_space_is_reserved(slot, @reservations, @user, @current_user_role)
@slots << slot
end
end
@slots.each do |s|
if s.is_complete? and not s.is_reserved
s.title = t('availabilities.not_available')
end
end
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@slots = service.spaces(params[:space_id], user)
end
def reservations
@ -281,7 +104,7 @@ class API::AvailabilitiesController < API::ApiController
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new(category: 'availabilities', export_type: 'index', user: current_user)
if @export.save
render json: {export_id: @export.id}, status: :ok
render json: { export_id: @export.id }, status: :ok
else
render json: @export.errors, status: :unprocessable_entity
end
@ -303,6 +126,14 @@ class API::AvailabilitiesController < API::ApiController
private
def user
if params[:member_id]
User.find(params[:member_id])
else
current_user
end
end
def set_availability
@availability = Availability.find(params[:id])
end
@ -317,104 +148,6 @@ class API::AvailabilitiesController < API::ApiController
params.require(:lock)
end
def is_reserved_availability(availability, user)
if user
reserved_slots = []
availability.slots.each do |s|
if s.canceled_at.nil?
reserved_slots << s
end
end
reserved_slots.map(&:reservations).flatten.map(&:user_id).include? user.id
else
false
end
end
def is_reserved(start_at, reservations)
is_reserved = false
reservations.each do |r|
r.slots.each do |s|
is_reserved = true if s.start_at == start_at
end
end
is_reserved
end
def verify_machine_is_reserved(slot, reservations, user, user_role)
show_name = (user_role == 'admin' or Setting.find_by(name: 'display_name_enable').value == 'true')
reservations.each do |r|
r.slots.each do |s|
next unless slot.machine.id == r.reservable_id
if s.start_at == slot.start_at and s.canceled_at == nil
slot.id = s.id
slot.is_reserved = true
slot.title = "#{slot.machine.name} - #{show_name ? r.user.profile.full_name : t('availabilities.not_available')}"
slot.can_modify = true if user_role === 'admin'
slot.reservations.push r
end
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
slot.title = "#{slot.machine.name} - #{t('availabilities.i_ve_reserved')}"
slot.can_modify = true
slot.is_reserved_by_current_user = true
end
end
end
slot
end
def verify_space_is_reserved(slot, reservations, user, user_role)
reservations.each do |r|
r.slots.each do |s|
next unless slot.space.id == r.reservable_id
if s.start_at == slot.start_at and s.canceled_at == nil
slot.can_modify = true if user_role === 'admin'
slot.reservations.push r
end
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
slot.id = s.id
slot.title = t('availabilities.i_ve_reserved')
slot.can_modify = true
slot.is_reserved = true
end
end
end
slot
end
def verify_training_event_is_reserved(availability, reservations, user)
reservations.each do |r|
r.slots.each do |s|
next unless (
(availability.available_type == 'training' && availability.trainings.first.id == r.reservable_id) ||
(availability.available_type == 'event' && availability.event.id == r.reservable_id)
) && s.start_at == availability.start_at && s.canceled_at == nil
availability.slot_id = s.id
if r.user == user
availability.is_reserved = true
availability.can_modify = true
end
end
end
availability
end
def can_show_slot_plus_three_months(user)
# member must have validated at least 1 training and must have a valid yearly subscription.
user.trainings.size > 0 and is_subscription_year(user)
end
def is_subscription_year(user)
user.subscription and user.subscription.plan.interval == 'year' and user.subscription.expired_at >= Time.now
end
def in_same_day(start_date, end_date)
(end_date.to_date - start_date.to_date).to_i == 1
end
def filter_availabilites(availabilities)
availabilities_filtered = []
availabilities.to_ary.each do |a|
@ -422,35 +155,33 @@ class API::AvailabilitiesController < API::ApiController
if !a.try(:available_type)
availabilities_filtered << a
else
# training
if params[:t] and a.available_type == 'training'
if params[:t].include?(a.trainings.first.id.to_s)
availabilities_filtered << a
end
end
# space
if params[:s] and a.available_type == 'space'
if params[:s].include?(a.spaces.first.id.to_s)
availabilities_filtered << a
end
end
# machines
if params[:m] and a.available_type == 'machines'
if (params[:m].map(&:to_i) & a.machine_ids).any?
availabilities_filtered << a
end
end
# event
if params[:evt] and params[:evt] == 'true' and a.available_type == 'event'
availabilities_filtered << a
end
end
end
availabilities_filtered.delete_if do |a|
if params[:dispo] == 'false'
a.is_reserved or (a.try(:completed?) and a.completed?)
availabilities_filtered << a if filter_training?(a)
availabilities_filtered << a if filter_space?(a)
availabilities_filtered << a if filter_machine?(a)
availabilities_filtered << a if filter_event?(a)
end
end
availabilities_filtered.delete_if(&method(:remove_completed?))
end
def filter_training?(availability)
params[:t] && availability.available_type == 'training' && params[:t].include?(availability.trainings.first.id.to_s)
end
def filter_space?(availability)
params[:s] && availability.available_type == 'space' && params[:s].include?(availability.spaces.first.id.to_s)
end
def filter_machine?(availability)
params[:m] && availability.available_type == 'machines' && (params[:m].map(&:to_i) & availability.machine_ids).any?
end
def filter_event?(availability)
params[:evt] && params[:evt] == 'true' && availability.available_type == 'event'
end
def remove_completed?(availability)
params[:dispo] == 'false' && (availability.is_reserved || (availability.try(:completed?) && availability.completed?))
end
def define_max_visibility

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Category
# Categories are used to classify Events
class API::CategoriesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_category, only: [:show, :update, :destroy]
before_action :set_category, only: %i[show update destroy]
def index
@categories = Category.all
end
def show
end
def show; end
def create
authorize Category
@ -39,11 +42,12 @@ class API::CategoriesController < API::ApiController
end
private
def set_category
@category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:name)
end
def set_category
@category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:name)
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Component
# Components are used in Projects
class API::ComponentsController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_component, only: [:show, :update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_component, only: %i[show update destroy]
def index
@components = Component.all
end
def show
end
def show; end
def create
authorize Component
@ -35,11 +38,12 @@ class API::ComponentsController < API::ApiController
end
private
def set_component
@component = Component.find(params[:id])
end
def component_params
params.require(:component).permit(:name)
end
def set_component
@component = Component.find(params[:id])
end
def component_params
params.require(:component).permit(:name)
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Coupon
# Coupons are used in payments
class API::CouponsController < API::ApiController
before_action :authenticate_user!
before_action :set_coupon, only: [:show, :update, :destroy]
before_action :set_coupon, only: %i[show update destroy]
def index
@coupons = Coupon.all
end
def show
end
def show; end
def create
authorize Coupon
@ -22,18 +25,18 @@ class API::CouponsController < API::ApiController
def validate
@coupon = Coupon.find_by(code: params[:code])
if @coupon.nil?
render json: {status: 'rejected'}, status: :not_found
render json: { status: 'rejected' }, status: :not_found
else
if !current_user.is_admin?
_user_id = current_user.id
else
_user_id = params[:user_id]
end
_user_id = if !current_user.admin?
current_user.id
else
params[:user_id]
end
amount = params[:amount].to_f * 100.0
status = @coupon.status(_user_id, amount)
if status != 'active'
render json: {status: status}, status: :unprocessable_entity
render json: { status: status }, status: :unprocessable_entity
else
render :validate, status: :ok, location: @coupon
end
@ -62,18 +65,17 @@ class API::CouponsController < API::ApiController
authorize Coupon
@coupon = Coupon.find_by(code: params[:coupon_code])
if @coupon.nil?
render json: {error: "no coupon with code #{params[:coupon_code]}"}, status: :not_found
else
if @coupon.send_to(params[:user_id])
render :show, status: :ok, location: @coupon
else
render json: @coupon.errors, status: :unprocessable_entity
end
end
if @coupon.nil?
render json: { error: "no coupon with code #{params[:coupon_code]}" }, status: :not_found
elsif @coupon.send_to(params[:user_id])
render :show, status: :ok, location: @coupon
else
render json: @coupon.errors, status: :unprocessable_entity
end
end
private
def set_coupon
@coupon = Coupon.find(params[:id])
end
@ -85,7 +87,8 @@ class API::CouponsController < API::ApiController
@parameters = params
@parameters[:coupon][:amount_off] = @parameters[:coupon][:amount_off].to_f * 100.0 if @parameters[:coupon][:amount_off]
@parameters = @parameters.require(:coupon).permit(:name, :code, :percent_off, :amount_off, :validity_per_user, :valid_until, :max_usages, :active)
@parameters = @parameters.require(:coupon).permit(:name, :code, :percent_off, :amount_off, :validity_per_user, :valid_until,
:max_usages, :active)
end
end

View File

@ -1,14 +1,18 @@
# frozen_string_literal: true
# API Controller for resources of type Credit
# Credits are used to give free reservations to users
class API::CreditsController < API::ApiController
before_action :authenticate_user!
before_action :set_credit, only: [:show, :update, :destroy]
before_action :set_credit, only: %i[show update destroy]
def index
authorize Credit
if params
@credits = Credit.includes(:creditable).where(params.permit(:creditable_type))
else
@credits = Credit.includes(:creditable).all
end
@credits = if params
Credit.includes(:creditable).where(params.permit(:creditable_type))
else
Credit.includes(:creditable).all
end
end
def create
@ -37,11 +41,12 @@ class API::CreditsController < API::ApiController
end
private
def set_credit
@credit = Credit.find(params[:id])
end
def credit_params
params.require(:credit).permit!
end
def set_credit
@credit = Credit.find(params[:id])
end
def credit_params
params.require(:credit).permit!
end
end

View File

@ -1,10 +1,10 @@
class API::CustomAssetsController < API::ApiController
before_action :authenticate_user!, only: [:index, :update, :create, :destroy]
before_action :set_custom_asset, only: [:show, :update, :destroy]
# frozen_string_literal: true
def index
#TODO GET /api/custom_assets/
end
# API Controller for resources of type CustomAsset
# CustomAssets are used in settings
class API::CustomAssetsController < API::ApiController
before_action :authenticate_user!, only: %i[index update create destroy]
before_action :set_custom_asset, only: %i[show update destroy]
# PUT /api/custom_assets/1/
def update
@ -28,14 +28,10 @@ class API::CustomAssetsController < API::ApiController
end
# GET /api/custom_assets/1/
def show
end
def destroy
#TODO DELETE /api/custom_assets/1/
end
def show; end
private
def set_custom_asset
@custom_asset = CustomAsset.find_by(name: params[:id])
end
@ -45,4 +41,4 @@ class API::CustomAssetsController < API::ApiController
params.required(:custom_asset).permit(:name, custom_asset_file_attributes: [:attachment])
end
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type EventTheme
# EventTheme are used to classify Events
class API::EventThemesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_event_theme, only: [:show, :update, :destroy]
before_action :set_event_theme, only: %i[show update destroy]
def index
@event_themes = EventTheme.all
end
def show
end
def show; end
def create
authorize EventTheme
@ -39,6 +42,7 @@ class API::EventThemesController < API::ApiController
end
private
def set_event_theme
@event_theme = EventTheme.find(params[:id])
end

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# API Controller for resources of type Event
class API::EventsController < API::ApiController
before_action :set_event, only: [:show, :update, :destroy]
before_action :set_event, only: %i[show update destroy]
def index
@events = policy_scope(Event)
@ -11,17 +14,17 @@ class API::EventsController < API::ApiController
@events = @events.joins(:event_themes).where('event_themes.id = :theme', theme: params[:theme_id]) if params[:theme_id]
@events = @events.where('age_range_id = :age_range', age_range: params[:age_range_id]) if params[:age_range_id]
if current_user and current_user.is_admin?
case params[:scope]
when 'future'
@events = @events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at DESC')
when 'future_asc'
@events = @events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at ASC')
when 'passed'
@events = @events.where('availabilities.start_at < ?', Time.now).order('availabilities.start_at DESC')
else
@events = @events.order('availabilities.start_at DESC')
end
if current_user&.admin?
@events = case params[:scope]
when 'future'
@events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at DESC')
when 'future_asc'
@events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at ASC')
when 'passed'
@events.where('availabilities.start_at < ?', Time.now).order('availabilities.start_at DESC')
else
@events.order('availabilities.start_at DESC')
end
end
# paginate
@ -38,8 +41,7 @@ class API::EventsController < API::ApiController
.limit(limit)
end
def show
end
def show; end
def create
authorize Event
@ -61,9 +63,10 @@ class API::EventsController < API::ApiController
end
rescue ActiveRecord::RecordNotDestroyed => e
if e.record.class.name == 'EventPriceCategory'
render json: {error: ["#{e.record.price_category.name}: #{t('events.error_deleting_reserved_price')}"]}, status: :unprocessable_entity
render json: { error: ["#{e.record.price_category.name}: #{t('events.error_deleting_reserved_price')}"] },
status: :unprocessable_entity
else
render json: {error: [t('events.other_error')]}, status: :unprocessable_entity
render json: { error: [t('events.other_error')] }, status: :unprocessable_entity
end
end
@ -79,48 +82,22 @@ class API::EventsController < API::ApiController
end
private
# Use callbacks to share common setup or constraints between actions.
def set_event
@event = Event.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def event_params
# handle general properties
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
:amount, :nb_total_places, :availability_id,
:all_day, :recurrence, :recurrence_end_at, :category_id, :event_theme_ids,
:age_range_id, event_theme_ids: [],
event_image_attributes: [:attachment],
event_files_attributes: [:id, :attachment, :_destroy],
event_price_categories_attributes: [:id, :price_category_id, :amount, :_destroy]
)
# handle dates & times (whole-day events or not, maybe during many days)
start_date = Time.zone.parse(event_preparams[:start_date])
end_date = Time.zone.parse(event_preparams[:end_date])
start_time = Time.parse(event_preparams[:start_time]) if event_preparams[:start_time]
end_time = Time.parse(event_preparams[:end_time]) if event_preparams[:end_time]
if event_preparams[:all_day] == 'true'
start_at = DateTime.new(start_date.year, start_date.month, start_date.day, 0, 0, 0, start_date.zone)
end_at = DateTime.new(end_date.year, end_date.month, end_date.day, 23, 59, 59, end_date.zone)
else
start_at = DateTime.new(start_date.year, start_date.month, start_date.day, start_time.hour, start_time.min, start_time.sec, start_date.zone)
end_at = DateTime.new(end_date.year, end_date.month, end_date.day, end_time.hour, end_time.min, end_time.sec, end_date.zone)
end
event_preparams.merge!(availability_attributes: {id: event_preparams[:availability_id], start_at: start_at, end_at: end_at, available_type: 'event'})
.except!(:start_date, :end_date, :start_time, :end_time, :all_day)
# convert main price to centimes
event_preparams.merge!(amount: (event_preparams[:amount].to_f * 100 if event_preparams[:amount].present?))
# delete non-complete "other" prices and convert them to centimes
unless event_preparams[:event_price_categories_attributes].nil?
event_preparams[:event_price_categories_attributes].delete_if { |price_cat| price_cat[:price_category_id].empty? or price_cat[:amount].empty? }
event_preparams[:event_price_categories_attributes].each do |price_cat|
price_cat[:amount] = price_cat[:amount].to_f * 100
end
end
# return the resulting params object
event_preparams
end
# Use callbacks to share common setup or constraints between actions.
def set_event
@event = Event.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def event_params
# handle general properties
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
:amount, :nb_total_places, :availability_id, :all_day, :recurrence,
:recurrence_end_at, :category_id, :event_theme_ids, :age_range_id,
event_theme_ids: [],
event_image_attributes: [:attachment],
event_files_attributes: %i[id attachment_destroy],
event_price_categories_attributes: %i[id price_category_id amount _destroy])
EventService.process_params(event_preparams)
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Export
# Export are used to download data tables in offline files
class API::ExportsController < API::ApiController
before_action :authenticate_user!
before_action :set_export, only: [:download]
@ -6,7 +10,9 @@ class API::ExportsController < API::ApiController
authorize @export
if FileTest.exist?(@export.file)
send_file File.join(Rails.root, @export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
send_file File.join(Rails.root, @export.file),
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
disposition: 'attachment'
else
render text: I18n.t('errors.messages.export_not_found'), status: :not_found
end
@ -15,38 +21,39 @@ class API::ExportsController < API::ApiController
def status
authorize Export
export = Export.where({category: params[:category], export_type: params[:type], query: params[:query], key: params[:key]})
export = Export.where(category: params[:category], export_type: params[:type], query: params[:query], key: params[:key])
if params[:category] === 'users'
if params[:category] == 'users'
case params[:type]
when 'subscriptions'
export = export.where('created_at > ?', Subscription.maximum('updated_at'))
when 'reservations'
export = export.where('created_at > ?', Reservation.maximum('updated_at'))
when 'members'
export = export.where('created_at > ?', User.with_role(:member).maximum('updated_at'))
else
raise ArgumentError, "Unknown export users/#{params[:type]}"
when 'subscriptions'
export = export.where('created_at > ?', Subscription.maximum('updated_at'))
when 'reservations'
export = export.where('created_at > ?', Reservation.maximum('updated_at'))
when 'members'
export = export.where('created_at > ?', User.with_role(:member).maximum('updated_at'))
else
raise ArgumentError, "Unknown export users/#{params[:type]}"
end
elsif params[:category] === 'availabilities'
elsif params[:category] == 'availabilities'
case params[:type]
when 'index'
export = export.where('created_at > ?', Availability.maximum('updated_at'))
else
raise ArgumentError, "Unknown type availabilities/#{params[:type]}"
when 'index'
export = export.where('created_at > ?', Availability.maximum('updated_at'))
else
raise ArgumentError, "Unknown type availabilities/#{params[:type]}"
end
end
export = export.last
if export.nil? || !FileTest.exist?(export.file)
render json: {exists: false, id: nil}, status: :ok
render json: { exists: false, id: nil }, status: :ok
else
render json: {exists: true, id: export.id}, status: :ok
render json: { exists: true, id: export.id }, status: :ok
end
end
private
def set_export
@export = Export.find(params[:id])
end
end
end

View File

@ -1,16 +1,19 @@
# frozen_string_literal: true
# API Controller to wrap social networks public feeds
class API::FeedsController < API::ApiController
respond_to :json
def twitter_timelines
if params
limit = params[:limit]
else
limit = 3
end
limit = if params
params[:limit]
else
3
end
from_account = Setting.find_by(name: 'twitter_name').try(:value) || ENV['TWITTER_NAME']
begin
@tweet_news = Feed.twitter.user_timeline(from_account, {count: limit})
@tweet_news = Feed.twitter.user_timeline(from_account, count: limit)
rescue Twitter::Error::BadRequest => e
STDERR.puts "[WARNING] Unable to retrieve the twitter feed, please check your ENV configuration. Details: #{e.message}"
render status: :no_content

View File

@ -1,12 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Group
# Groups are used for categorizing Users
class API::GroupsController < API::ApiController
before_action :authenticate_user!, except: :index
def index
if current_user and current_user.is_admin?
@groups = Group.all
else
@groups = Group.where.not(slug: 'admins')
end
@groups = if current_user&.admin?
Group.all
else
Group.where.not(slug: 'admins')
end
end
@ -39,7 +43,7 @@ class API::GroupsController < API::ApiController
private
def group_params
params.require(:group).permit(:name, :disabled)
end
def group_params
params.require(:group).permit(:name, :disabled)
end
end

View File

@ -1,15 +1,20 @@
# frozen_string_literal: true
# API Controller for resources of Invoice and Avoir
class API::InvoicesController < API::ApiController
before_action :authenticate_user!
before_action :set_invoice, only: [:show, :download]
before_action :set_invoice, only: %i[show download]
def index
authorize Invoice
@invoices = Invoice.includes(:avoir, :invoiced, invoice_items: [:subscription, :invoice_item], user: [:profile, :trainings]).all.order('reference DESC')
@invoices = Invoice.includes(
:avoir, :invoiced, invoice_items: %i[subscription invoice_item], user: %i[profile trainings]
).all.order('reference DESC')
end
def download
authorize @invoice
send_file File.join(Rails.root, @invoice.file), :type => 'application/pdf', :disposition => 'attachment'
send_file File.join(Rails.root, @invoice.file), type: 'application/pdf', disposition: 'attachment'
end
def list
@ -17,44 +22,18 @@ class API::InvoicesController < API::ApiController
p = params.require(:query).permit(:number, :customer, :date, :order_by, :page, :size)
unless p[:page].is_a? Integer
render json: {error: 'page must be an integer'}, status: :unprocessable_entity
end
render json: { error: 'page must be an integer' }, status: :unprocessable_entity and return unless p[:page].is_a? Integer
unless p[:size].is_a? Integer
render json: {error: 'size must be an integer'}, status: :unprocessable_entity
end
direction = (p[:order_by][0] == '-' ? 'DESC' : 'ASC')
order_key = (p[:order_by][0] == '-' ? p[:order_by][1, p[:order_by].size] : p[:order_by])
case order_key
when 'reference'
order_key = 'invoices.reference'
when 'date'
order_key = 'invoices.created_at'
when 'total'
order_key = 'invoices.total'
when 'name'
order_key = 'profiles.first_name'
else
order_key = 'invoices.id'
end
@invoices = Invoice.includes(:avoir, :invoiced, invoice_items: [:subscription, :invoice_item], user: [:profile, :trainings])
.joins(:user => :profile)
.order("#{order_key} #{direction}")
.page(p[:page])
.per(p[:size])
# ILIKE => PostgreSQL case-insensitive LIKE
@invoices = @invoices.where('invoices.reference LIKE :search', search: "#{p[:number].to_s}%") if p[:number].size > 0
@invoices = @invoices.where('profiles.first_name ILIKE :search OR profiles.last_name ILIKE :search', search: "%#{p[:customer]}%") if p[:customer].size > 0
@invoices = @invoices.where("date_trunc('day', invoices.created_at) = :search", search: "%#{DateTime.iso8601(p[:date]).to_time.to_date.to_s}%") unless p[:date].nil?
@invoices
render json: { error: 'size must be an integer' }, status: :unprocessable_entity and return unless p[:size].is_a? Integer
order = InvoicesService.parse_order(p[:order_by])
@invoices = InvoicesService.list(
order[:order_key],
order[:direction],
p[:page],
p[:size],
number: p[:number], customer: p[:customer], date: p[:date]
)
end
# only for create refund invoices (avoir)
@ -64,9 +43,7 @@ class API::InvoicesController < API::ApiController
@avoir = invoice.build_avoir(avoir_params)
if @avoir.save
# when saved, expire the subscription if needed
if @avoir.subscription_to_expire
@avoir.expire_subscription
end
@avoir.expire_subscription if @avoir.subscription_to_expire
# then answer the API call
render :avoir, status: :created
else
@ -75,11 +52,13 @@ class API::InvoicesController < API::ApiController
end
private
def avoir_params
params.require(:avoir).permit(:invoice_id, :avoir_date, :avoir_mode, :subscription_to_expire, :description, :invoice_items_ids => [])
end
def set_invoice
@invoice = Invoice.find(params[:id])
end
def avoir_params
params.require(:avoir).permit(:invoice_id, :avoir_date, :avoir_mode, :subscription_to_expire, :description,
invoice_items_ids: [])
end
def set_invoice
@invoice = Invoice.find(params[:id])
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Licence
# Licenses are used in Projects
class API::LicencesController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_licence, only: [:show, :update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_licence, only: %i[show update destroy]
def index
@licences = Licence.all
end
def show
end
def show; end
def create
authorize Licence
@ -35,11 +38,12 @@ class API::LicencesController < API::ApiController
end
private
def set_licence
@licence = Licence.find(params[:id])
end
def licence_params
params.require(:licence).permit(:name, :description)
end
def set_licence
@licence = Licence.find(params[:id])
end
def licence_params
params.require(:licence).permit(:name, :description)
end
end

View File

@ -1,15 +1,18 @@
# frozen_string_literal: true
# API Controller for resources of type Machine
class API::MachinesController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_machine, only: [:update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_machine, only: %i[update destroy]
respond_to :json
def index
sort_by = Setting.find_by(name: 'machines_sort_by').value || 'default'
if sort_by === 'default'
@machines = Machine.includes(:machine_image, :plans)
else
@machines = Machine.includes(:machine_image, :plans).order(sort_by)
end
@machines = if sort_by == 'default'
Machine.includes(:machine_image, :plans)
else
Machine.includes(:machine_image, :plans).order(sort_by)
end
end
def show
@ -42,22 +45,14 @@ class API::MachinesController < API::ApiController
end
private
def set_machine
@machine = Machine.find(params[:id])
end
def machine_params
params.require(:machine).permit(:name, :description, :spec, :disabled, :plan_ids, plan_ids: [], machine_image_attributes: [:attachment],
machine_files_attributes: [:id, :attachment, :_destroy])
end
def set_machine
@machine = Machine.find(params[:id])
end
def is_reserved(start_at, reservations)
is_reserved = false
reservations.each do |r|
r.slots.each do |s|
is_reserved = true if s.start_at == start_at
end
end
is_reserved
end
def machine_params
params.require(:machine).permit(:name, :description, :spec, :disabled, :plan_ids,
plan_ids: [], machine_image_attributes: [:attachment],
machine_files_attributes: %i[id attachment _destroy])
end
end

View File

@ -1,15 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type User with role 'member'
class API::MembersController < API::ApiController
before_action :authenticate_user!, except: [:last_subscribed]
before_action :set_member, only: [:update, :destroy, :merge]
before_action :set_member, only: %i[update destroy merge]
respond_to :json
def index
@requested_attributes = params[:requested_attributes]
@query = policy_scope(User)
unless params[:page].nil? and params[:size].nil?
@query = @query.page(params[:page].to_i).per(params[:size].to_i)
end
@query = @query.page(params[:page].to_i).per(params[:size].to_i) unless params[:page].nil? && params[:size].nil?
# remove unmerged profiles from list
@members = @query.to_a
@ -17,7 +18,11 @@ class API::MembersController < API::ApiController
end
def last_subscribed
@query = User.active.with_role(:member).includes(profile: [:user_avatar]).where('is_allow_contact = true AND confirmed_at IS NOT NULL').order('created_at desc').limit(params[:last])
@query = User.active.with_role(:member)
.includes(profile: [:user_avatar])
.where('is_allow_contact = true AND confirmed_at IS NOT NULL')
.order('created_at desc')
.limit(params[:last])
# remove unmerged profiles from list
@members = @query.to_a
@ -34,27 +39,11 @@ class API::MembersController < API::ApiController
def create
authorize User
if !user_params[:password] and !user_params[:password_confirmation]
generated_password = Devise.friendly_token.first(8)
@member = User.new(user_params.merge(password: generated_password).permit!)
else
@member = User.new(user_params.permit!)
end
@member = User.new(user_params.permit!)
members_service = Members::MembersService.new(@member)
# if the user is created by an admin and the authentication is made through an SSO, generate a migration token
if current_user.is_admin? and AuthProvider.active.providable_type != DatabaseProvider.name
@member.generate_auth_migration_token
end
if @member.save
@member.generate_subscription_invoice
@member.send_confirmation_instructions
if !user_params[:password] and !user_params[:password_confirmation]
UsersMailer.delay.notify_user_account_created(@member, generated_password)
else
UsersMailer.delay.notify_user_account_created(@member, user_params[:password])
end
if members_service.create(current_user, user_params)
render :show, status: :created, location: member_path(@member)
else
render json: @member.errors, status: :unprocessable_entity
@ -63,21 +52,14 @@ class API::MembersController < API::ApiController
def update
authorize @member
members_service = MembersService.new(@member)
members_service = Members::MembersService.new(@member)
if user_params[:group_id] && @member.group_id != user_params[:group_id].to_i && !@member.subscribed_plan.nil?
# here a group change is requested but unprocessable, handle the exception
@member.errors[:group_id] = t('members.unable_to_change_the_group_while_a_subscription_is_running')
render json: @member.errors, status: :unprocessable_entity
if members_service.update(user_params)
# Update password without logging out
sign_in(@member, bypass: true) unless current_user.id != params[:id].to_i
render :show, status: :ok, location: member_path(@member)
else
# otherwise, run the user update
if members_service.update(user_params)
# Update password without logging out
sign_in(@member, bypass: true) unless current_user.id != params[:id].to_i
render :show, status: :ok, location: member_path(@member)
else
render json: @member.errors, status: :unprocessable_entity
end
render json: @member.errors, status: :unprocessable_entity
end
end
@ -92,9 +74,11 @@ class API::MembersController < API::ApiController
def export_subscriptions
authorize :export
export = Export.where(category:'users', export_type: 'subscriptions').where('created_at > ?', Subscription.maximum('updated_at')).last
export = Export.where(category: 'users', export_type: 'subscriptions')
.where('created_at > ?', Subscription.maximum('updated_at'))
.last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new(category:'users', export_type: 'subscriptions', user: current_user)
@export = Export.new(category: 'users', export_type: 'subscriptions', user: current_user)
if @export.save
render json: { export_id: @export.id }, status: :ok
else
@ -111,7 +95,7 @@ class API::MembersController < API::ApiController
def export_reservations
authorize :export
export = Export.where(category:'users', export_type: 'reservations')
export = Export.where(category: 'users', export_type: 'reservations')
.where('created_at > ?', Reservation.maximum('updated_at'))
.last
if export.nil? || !FileTest.exist?(export.file)
@ -131,11 +115,11 @@ class API::MembersController < API::ApiController
def export_members
authorize :export
export = Export.where(category:'users', export_type: 'members')
export = Export.where(category: 'users', export_type: 'members')
.where('created_at > ?', User.with_role(:member).maximum('updated_at'))
.last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new(category:'users', export_type: 'members', user: current_user)
@export = Export.new(category: 'users', export_type: 'members', user: current_user)
if @export.save
render json: { export_id: @export.id }, status: :ok
else
@ -148,16 +132,15 @@ class API::MembersController < API::ApiController
end
end
# the user is querying to be mapped to his already existing account
def merge
authorize @member
# here the user query to be mapped to his already existing account
token = params.require(:user).permit(:auth_token)[:auth_token]
token = token_param
@account = User.find_by(auth_token: token)
if @account
members_service = MembersService.new(@account)
members_service = Members::MembersService.new(@account)
begin
if members_service.merge_from_sso(@member)
@member = @account
@ -168,7 +151,8 @@ class API::MembersController < API::ApiController
render json: @member.errors, status: :unprocessable_entity
end
rescue DuplicateIndexError => error
render json: { error: t('members.please_input_the_authentication_code_sent_to_the_address', EMAIL: error.message) }, status: :unprocessable_entity
render json: { error: t('members.please_input_the_authentication_code_sent_to_the_address', EMAIL: error.message) },
status: :unprocessable_entity
end
else
render json: { error: t('members.your_authentication_code_is_not_valid') }, status: :unprocessable_entity
@ -178,67 +162,17 @@ class API::MembersController < API::ApiController
def list
authorize User
p = params.require(:query).permit(:search, :order_by, :page, :size)
render json: { error: 'page must be an integer' }, status: :unprocessable_entity and return unless query_params[:page].is_a? Integer
render json: { error: 'size must be an integer' }, status: :unprocessable_entity and return unless query_params[:size].is_a? Integer
render json: {error: 'page must be an integer'}, status: :unprocessable_entity unless p[:page].is_a? Integer
render json: {error: 'size must be an integer'}, status: :unprocessable_entity unless p[:size].is_a? Integer
direction = (p[:order_by][0] == '-' ? 'DESC' : 'ASC')
order_key = (p[:order_by][0] == '-' ? p[:order_by][1, p[:order_by].size] : p[:order_by])
order_key = case order_key
when 'last_name'
'profiles.last_name'
when 'first_name'
'profiles.first_name'
when 'email'
'users.email'
when 'phone'
'profiles.phone'
when 'group'
'groups.name'
when 'plan'
'plans.base_name'
else
'users.id'
end
@query = User.includes(:profile, :group, :subscriptions)
.joins(:profile, :group, :roles, 'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id" LEFT JOIN "plans" ON "plans"."id" = "subscriptions"."plan_id"')
.where("users.is_active = 'true' AND roles.name = 'member'")
.order("#{order_key} #{direction}")
.page(p[:page])
.per(p[:size])
# ILIKE => PostgreSQL case-insensitive LIKE
@query = @query.where('profiles.first_name ILIKE :search OR profiles.last_name ILIKE :search OR profiles.phone ILIKE :search OR email ILIKE :search OR groups.name ILIKE :search OR plans.base_name ILIKE :search', search: "%#{p[:search]}%") if p[:search].size > 0
@members = @query.to_a
query = Members::ListService.list(query_params)
@max_members = query.except(:offset, :limit, :order).count
@members = query.to_a
end
def search
@members = User.includes(:profile)
.joins(:profile, :roles, 'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id"')
.where("users.is_active = 'true' AND roles.name = 'member'")
.where("lower(f_unaccent(profiles.first_name)) ~ regexp_replace(:search, E'\\\\s+', '|') OR lower(f_unaccent(profiles.last_name)) ~ regexp_replace(:search, E'\\\\s+', '|')", search: params[:query].downcase)
if current_user.is_member?
# non-admin can only retrieve users with "public profiles"
@members = @members.where("users.is_allow_contact = 'true'")
else
# only admins have the ability to filter by subscription
if params[:subscription] === 'true'
@members = @members.where('subscriptions.id IS NOT NULL AND subscriptions.expired_at >= :now', now: Date.today.to_s)
elsif params[:subscription] === 'false'
@members = @members.where('subscriptions.id IS NULL OR subscriptions.expired_at < :now', now: Date.today.to_s)
end
end
@members = @members.to_a
@members = Members::ListService.search(current_user, params[:query], params[:subscription])
end
def mapping
@ -266,8 +200,8 @@ class API::MembersController < API::ApiController
organization_attributes: [:id, :name,
address_attributes: %i[id address]]])
elsif current_user.is_admin?
params.require(:user).permit(:username, :email, :password, :password_confirmation, :invoicing_disabled,
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,
@ -281,4 +215,12 @@ class API::MembersController < API::ApiController
end
end
def token_param
params.require(:user).permit(:auth_token)[:auth_token]
end
def query_params
params.require(:query).permit(:search, :order_by, :page, :size)
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Notification
# Notifications are scoped by user
class API::NotificationsController < API::ApiController
include NotifyWith::NotificationsApi
before_action :authenticate_user!
@ -12,8 +16,8 @@ class API::NotificationsController < API::ApiController
break unless delete_obsoletes(@notifications)
end
@totals = {
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
}
render :index
end
@ -25,17 +29,19 @@ class API::NotificationsController < API::ApiController
break unless delete_obsoletes(@notifications)
end
@totals = {
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
}
render :index
end
def polling
@notifications = current_user.notifications.where('is_read = false AND created_at >= :date', date: params[:last_poll]).order('created_at DESC')
@notifications = current_user.notifications
.where('is_read = false AND created_at >= :date', date: params[:last_poll])
.order('created_at DESC')
@totals = {
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
}
render :index
end
@ -45,7 +51,7 @@ class API::NotificationsController < API::ApiController
def delete_obsoletes(notifications)
cleaned = false
notifications.each do |n|
if !Module.const_get(n.attached_object_type) or !n.attached_object
if !Module.const_get(n.attached_object_type) || !n.attached_object
n.destroy!
cleaned = true
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type OpenAPI::Client
# OpenAPI::Clients are used to allow access to the public API
class API::OpenAPIClientsController < API::ApiController
before_action :authenticate_user!
@ -5,7 +9,7 @@ class API::OpenAPIClientsController < API::ApiController
authorize OpenAPI::Client
@clients = OpenAPI::Client.order(:created_at)
end
# add authorization
def create
@client = OpenAPI::Client.new(client_params)
authorize @client
@ -40,7 +44,8 @@ class API::OpenAPIClientsController < API::ApiController
end
private
def client_params
params.require(:open_api_client).permit(:name)
end
def client_params
params.require(:open_api_client).permit(:name)
end
end

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true
# API Controller for resources of type Openlab::Projects
# Openlab::Projects are Projects shared between different instances
class API::OpenlabProjectsController < API::ApiController
PROJECTS = Openlab::Projects.new
def index
begin
render json: PROJECTS.search(params[:q], page: params[:page], per_page: params[:per_page]).response.body
rescue StandardError
render json: { errors: ['service unavailable'] }
end
render json: PROJECTS.search(params[:q], page: params[:page], per_page: params[:per_page]).response.body
rescue StandardError
render json: { errors: ['service unavailable'] }
end
end

View File

@ -1,4 +1,9 @@
class API::PlansController < API::ApiController
# frozen_string_literal: true
# API Controller for resources of type Plan and PartnerPlan.
# Plan are used to define subscription's characteristics.
# PartnerPlan is a special kind of plan which send notifications to an external user
class API::PlansController < API::ApiController
before_action :authenticate_user!, except: [:index]
def index
@ -13,48 +18,19 @@
def create
authorize Plan
begin
if plan_params[:type] and plan_params[:type] == 'PartnerPlan'
partner = User.find(params[:plan][:partner_id])
unless %w[PartnerPlan Plan].include? plan_params[:type]
render json: { error: 'unhandled plan type' }, status: :unprocessable_entity and return
end
if plan_params[:group_id] == 'all'
plans = PartnerPlan.create_for_all_groups(plan_params)
if plans
plans.each { |plan| partner.add_role :partner, plan }
render json: { plan_ids: plans.map(&:id) }, status: :created
else
render status: :unprocessable_entity
end
type = plan_params[:type]
partner = params[:plan][:partner_id].empty? ? nil : User.find(params[:plan][:partner_id])
else
@plan = PartnerPlan.new(plan_params)
if @plan.save
partner.add_role :partner, @plan
render :show, status: :created
else
render json: @plan.errors, status: :unprocessable_entity
end
end
else
if plan_params[:group_id] == 'all'
plans = Plan.create_for_all_groups(plan_params)
if plans
render json: { plan_ids: plans.map(&:id) }, status: :created
else
render status: :unprocessable_entity
end
else
@plan = Plan.new(plan_params)
if @plan.save
render :show, status: :created, location: @plan
else
render json: @plan.errors, status: :unprocessable_entity
end
end
end
rescue Stripe::InvalidRequestError => e
render json: {error: e.message}, status: :unprocessable_entity
res = PlansService.create(type, partner, plan_params)
if res[:errors]
render res[:errors], status: :unprocessable_entity
else
render json: res, status: :created
end
end
@ -76,21 +52,25 @@
end
private
def plan_params
if @parameters
@parameters
else
@parameters = params
@parameters[:plan][:amount] = @parameters[:plan][:amount].to_f * 100.0 if @parameters[:plan][:amount]
def plan_params
# parameters caching for performance
if @parameters
@parameters
else
@parameters = params
@parameters[:plan][:amount] = @parameters[:plan][:amount].to_f * 100.0 if @parameters[:plan][:amount]
if @parameters[:plan][:prices_attributes]
@parameters[:plan][:prices_attributes] = @parameters[:plan][:prices_attributes].map do |price|
{ amount: price[:amount].to_f * 100.0, id: price[:id] }
end if @parameters[:plan][:prices_attributes]
@parameters = @parameters.require(:plan).permit(:base_name, :type, :group_id, :amount, :interval, :interval_count, :is_rolling,
:training_credit_nb, :ui_weight, :disabled,
plan_file_attributes: [:id, :attachment, :_destroy],
prices_attributes: [:id, :amount]
)
end
end
@parameters = @parameters.require(:plan)
.permit(:base_name, :type, :group_id, :amount, :interval, :interval_count, :is_rolling,
:training_credit_nb, :ui_weight, :disabled,
plan_file_attributes: %i[id attachment _destroy],
prices_attributes: %i[id amount])
end
end
end

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
# API Controller for resources of type PriceCategory
# PriceCategories are used in Events
class API::PriceCategoriesController < API::ApiController
before_action :authenticate_user!, only: [:update, :show, :create, :destroy]
before_action :set_price_category, only: [:show, :update, :destroy]
before_action :authenticate_user!, only: %i[update show create destroy]
before_action :set_price_category, only: %i[show update destroy]
def index
@price_categories = PriceCategory.all
@ -15,8 +19,7 @@ class API::PriceCategoriesController < API::ApiController
end
end
def show
end
def show; end
def create
authorize PriceCategory
@ -38,6 +41,7 @@ class API::PriceCategoriesController < API::ApiController
end
private
def set_price_category
@price_category = PriceCategory.find(params[:id])
end
@ -45,4 +49,4 @@ class API::PriceCategoriesController < API::ApiController
def price_category_params
params.require(:price_category).permit(:name, :conditions)
end
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Price
# Prices are used in reservations (Machine, Space)
class API::PricesController < API::ApiController
before_action :authenticate_user!
@ -6,29 +10,26 @@ class API::PricesController < API::ApiController
@prices = Price.all
if params[:priceable_type]
@prices = @prices.where(priceable_type: params[:priceable_type])
if params[:priceable_id]
@prices = @prices.where(priceable_id: params[:priceable_id])
end
@prices = @prices.where(priceable_id: params[:priceable_id]) if params[:priceable_id]
end
if params[:plan_id]
if params[:plan_id] =~ /no|nil|null|undefined/i
plan_id = nil
else
plan_id = params[:plan_id]
end
plan_id = if params[:plan_id] =~ /no|nil|null|undefined/i
nil
else
params[:plan_id]
end
@prices = @prices.where(plan_id: plan_id)
end
if params[:group_id]
@prices = @prices.where(group_id: params[:group_id])
end
@prices = @prices.where(group_id: params[:group_id]) if params[:group_id]
end
def update
authorize Price
@price = Price.find(params[:id])
_price_params = price_params
_price_params[:amount] = _price_params[:amount] * 100
if @price.update(_price_params)
price_parameters = price_params
price_parameters[:amount] = price_parameters[:amount] * 100
if @price.update(price_parameters)
render status: :ok
else
render status: :unprocessable_entity
@ -36,15 +37,22 @@ class API::PricesController < API::ApiController
end
def compute
_price_params = compute_price_params
price_parameters = compute_price_params
# user
_user = User.find(_price_params[:user_id])
user = User.find(price_parameters[:user_id])
# reservable
if _price_params[:reservable_id].nil?
if price_parameters[:reservable_id].nil?
@amount = {elements: nil, total: 0, before_coupon: 0}
else
_reservable = _price_params[:reservable_type].constantize.find(_price_params[:reservable_id])
@amount = Price.compute(current_user.is_admin?, _user, _reservable, _price_params[:slots_attributes] || [], _price_params[:plan_id], _price_params[:nb_reserve_places], _price_params[:tickets_attributes], coupon_params[:coupon_code])
reservable = price_parameters[:reservable_type].constantize.find(price_parameters[:reservable_id])
@amount = Price.compute(current_user.admin?,
user,
reservable,
price_parameters[:slots_attributes] || [],
price_parameters[:plan_id],
price_parameters[:nb_reserve_places],
price_parameters[:tickets_attributes],
coupon_params[:coupon_code])
end
@ -56,14 +64,15 @@ class API::PricesController < API::ApiController
end
private
def price_params
params.require(:price).permit(:amount)
end
def compute_price_params
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places,
tickets_attributes: [:event_price_category_id, :booked],
slots_attributes: [:id, :start_at, :end_at, :availability_id, :offered])
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered])
end
def coupon_params

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# API Controller for managing Plans prices
class API::PricingController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :authenticate_user!, except: %i[index show]
def index
@group_pricing = Group.includes(:plans, :trainings_pricings)
@ -10,14 +13,14 @@ class API::PricingController < API::ApiController
if params[:training].present?
training = Training.find params[:training]
params[:group_pricing].each do |group_id, amount|
if training
group = Group.includes(:plans).find(group_id)
if group
training_pricing = group.trainings_pricings.find_or_initialize_by(training_id: training.id)
training_pricing.amount = amount * 100
training_pricing.save
end
end
next unless training
group = Group.includes(:plans).find(group_id)
next unless group
training_pricing = group.trainings_pricings.find_or_initialize_by(training_id: training.id)
training_pricing.amount = amount * 100
training_pricing.save
end
end
head 200

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Project
class API::ProjectsController < API::ApiController
before_action :authenticate_user!, except: %i[index show last_published search]
before_action :set_project, only: %i[update destroy]

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Reservation
# Reservations are used for Training, Machine, Space and Event
class API::ReservationsController < API::ApiController
before_action :authenticate_user!
before_action :set_reservation, only: %i[show update]
@ -5,10 +9,10 @@ class API::ReservationsController < API::ApiController
def index
if params[:reservable_id] && params[:reservable_type] && params[:user_id]
params[:user_id] = current_user.id unless current_user.is_admin?
params[:user_id] = current_user.id unless current_user.admin?
@reservations = Reservation.where(params.permit(:reservable_id, :reservable_type, :user_id))
elsif params[:reservable_id] && params[:reservable_type] && current_user.is_admin?
elsif params[:reservable_id] && params[:reservable_type] && current_user.admin?
@reservations = Reservation.where(params.permit(:reservable_id, :reservable_type))
else
@reservations = []
@ -18,8 +22,8 @@ class API::ReservationsController < API::ApiController
def show; end
def create
method = current_user.is_admin? ? :local : :stripe
user_id = current_user.is_admin? ? reservation_params[:user_id] : current_user.id
method = current_user.admin? ? :local : :stripe
user_id = current_user.admin? ? reservation_params[:user_id] : current_user.id
@reservation = Reservation.new(reservation_params)
is_reserve = Reservations::Reserve.new(user_id)
@ -46,6 +50,7 @@ class API::ReservationsController < API::ApiController
end
private
def set_reservation
@reservation = Reservation.find(params[:id])
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Setting
class API::SettingsController < API::ApiController
before_action :authenticate_user!, only: :update
@ -17,6 +20,7 @@ class API::SettingsController < API::ApiController
def show
@setting = Setting.find_or_create_by(name: params[:name])
@show_history = params[:history] == 'true' && current_user.admin?
end
private

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
# API Controller for resources of type Slot
# Slots are used to cut Availabilities into reservable slots of ApplicationHelper::SLOT_DURATION minutes
class API::SlotsController < API::ApiController
before_action :authenticate_user!
before_action :set_slot, only: [:update, :cancel]
before_action :set_slot, only: %i[update cancel]
respond_to :json
def update
@ -15,10 +19,11 @@ class API::SlotsController < API::ApiController
def cancel
authorize @slot
@slot.update_attributes(:canceled_at => DateTime.now)
@slot.update_attributes(canceled_at: DateTime.now)
end
private
def set_slot
@slot = Slot.find(params[:id])
end

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# API Controller for resources of type Space
class API::SpacesController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :authenticate_user!, except: %i[index show]
respond_to :json
def index
@ -38,12 +41,14 @@ class API::SpacesController < API::ApiController
end
private
def get_space
Space.friendly.find(params[:id])
end
def space_params
params.require(:space).permit(:name, :description, :characteristics, :default_places, :disabled, space_image_attributes: [:attachment],
space_files_attributes: [:id, :attachment, :_destroy])
end
def get_space
Space.friendly.find(params[:id])
end
def space_params
params.require(:space).permit(:name, :description, :characteristics, :default_places, :disabled,
space_image_attributes: [:attachment],
space_files_attributes: %i[id attachment _destroy])
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Space
class API::StatisticsController < API::ApiController
before_action :authenticate_user!
@ -6,7 +9,7 @@ class API::StatisticsController < API::ApiController
@statistics = StatisticIndex.all
end
%w(account event machine project subscription training user space).each do |path|
%w[account event machine project subscription training user space].each do |path|
class_eval %{
def #{path}
authorize :statistic, :#{path}?
@ -27,38 +30,50 @@ class API::StatisticsController < API::ApiController
# return result
render json: results
end
}, __FILE__, __LINE__ - 20
end
%w[account event machine project subscription training user space].each do |path|
class_eval %{
def export_#{path}
authorize :statistic, :export_#{path}?
export = Export.where({category:'statistics', export_type: '#{path}', query: params[:body], key: params[:type_key]}).last
export = Export.where(category:'statistics', export_type: '#{path}', query: params[:body], key: params[:type_key]).last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new({category:'statistics', export_type: '#{path}', user: current_user, query: params[:body], key: params[:type_key]})
@export = Export.new(category:'statistics',
export_type: '#{path}',
user: current_user,
query: params[:body],
key: params[:type_key])
if @export.save
render json: {export_id: @export.id}, status: :ok
else
render json: @export.errors, status: :unprocessable_entity
end
else
send_file File.join(Rails.root, export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
send_file File.join(Rails.root, export.file),
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
disposition: 'attachment'
end
end
}
}, __FILE__, __LINE__ - 22
end
def export_global
authorize :statistic, :export_global?
export = Export.where({category:'statistics', export_type: 'global', query: params[:body]}).last
export = Export.where(category: 'statistics', export_type: 'global', query: params[:body]).last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new({category:'statistics', export_type: 'global', user: current_user, query: params[:body]})
@export = Export.new(category: 'statistics', export_type: 'global', user: current_user, query: params[:body])
if @export.save
render json: {export_id: @export.id}, status: :ok
render json: { export_id: @export.id }, status: :ok
else
render json: @export.errors, status: :unprocessable_entity
end
else
send_file File.join(Rails.root, export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
send_file File.join(Rails.root, export.file),
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
disposition: 'attachment'
end
end
@ -68,5 +83,4 @@ class API::StatisticsController < API::ApiController
results = Elasticsearch::Model.client.scroll scroll: params[:scroll], scroll_id: params[:scrollId]
render json: results
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Stylesheet
# Stylesheets are used to customize the appearance of fab-manager
class API::StylesheetsController < API::ApiController
caches_page :show # magic happens here
@ -5,7 +9,7 @@ class API::StylesheetsController < API::ApiController
@stylesheet = Stylesheet.find(params[:id])
respond_to do |format|
format.html # regular ERB template
format.css { render :text => @stylesheet.contents, :content_type => 'text/css' }
format.css { render text: @stylesheet.contents, content_type: 'text/css' }
end
end
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Subscription
class API::SubscriptionsController < API::ApiController
include FablabConfiguration
@ -12,8 +15,8 @@ class API::SubscriptionsController < API::ApiController
if fablab_plans_deactivated?
head 403
else
method = current_user.is_admin? ? :local : :stripe
user_id = current_user.is_admin? ? subscription_params[:user_id] : current_user.id
method = current_user.admin? ? :local : :stripe
user_id = current_user.admin? ? subscription_params[:user_id] : current_user.id
@subscription = Subscription.new(subscription_params)
is_subscribe = Subscriptions::Subscribe.new(user_id)
@ -64,7 +67,7 @@ class API::SubscriptionsController < API::ApiController
params.require(:subscription).permit(:expired_at)
end
# TODO refactor subscriptions logic and move this in model/validator
# TODO, refactor subscriptions logic and move this in model/validator
def valid_card_token?(token)
Stripe::Token.retrieve(token)
rescue Stripe::InvalidRequestError => e

View File

@ -1,15 +1,17 @@
# frozen_string_literal: true
# API Controller for resources of type Tag
# Tags are used to restrict access to Availabilities
class API::TagsController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_tag, only: [:show, :update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_tag, only: %i[show update destroy]
def index
@tags = Tag.all
end
def show
end
def show; end
def create
authorize Tag
@ -37,6 +39,7 @@ class API::TagsController < API::ApiController
end
private
def set_tag
@tag = Tag.find(params[:id])
end
@ -44,4 +47,4 @@ class API::TagsController < API::ApiController
def tag_params
params.require(:tag).permit(:name)
end
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Theme
# Themes are used in Projects
class API::ThemesController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_theme, only: [:show, :update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_theme, only: %i[show update destroy]
def index
@themes = Theme.all
end
def show
end
def show; end
def create
authorize Theme
@ -35,11 +38,12 @@ class API::ThemesController < API::ApiController
end
private
def set_theme
@theme = Theme.find(params[:id])
end
def theme_params
params.require(:theme).permit(:name)
end
def set_theme
@theme = Theme.find(params[:id])
end
def theme_params
params.require(:theme).permit(:name)
end
end

View File

@ -1,19 +1,21 @@
# frozen_string_literal: true
# API Controller for resources of type Training
class API::TrainingsController < API::ApiController
include ApplicationHelper
before_action :authenticate_user!, except: [:index, :show]
before_action :set_training, only: [:update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_training, only: %i[update destroy]
def index
@requested_attributes = params[:requested_attributes]
@trainings = policy_scope(Training)
if params[:public_page]
@trainings = @trainings.where(public_page: true)
end
@trainings = @trainings.where(public_page: true) if params[:public_page]
if attribute_requested?(@requested_attributes, 'availabilities')
@trainings = @trainings.includes(:availabilities => [:slots => [:reservation => [:user => [:profile, :trainings]]]]).order('availabilities.start_at DESC')
end
return unless attribute_requested?(@requested_attributes, 'availabilities')
@trainings = @trainings.includes(availabilities: [slots: [reservation: [user: %i[profile trainings]]]])
.order('availabilities.start_at DESC')
end
def show
@ -39,12 +41,10 @@ class API::TrainingsController < API::ApiController
end
head :no_content
elsif @training.update(training_params)
render :show, status: :ok, location: @training
else
if @training.update(training_params)
render :show, status: :ok, location: @training
else
render json: @training.errors, status: :unprocessable_entity
end
render json: @training.errors, status: :unprocessable_entity
end
end
@ -57,19 +57,24 @@ class API::TrainingsController < API::ApiController
def availabilities
authorize Training
@training = Training.find(params[:id])
@availabilities = @training.availabilities.includes(slots: {reservations: {user: [:profile, :trainings] }}).order('start_at DESC')
@availabilities = @training.availabilities
.includes(slots: { reservations: { user: %i[profile trainings] } })
.order('start_at DESC')
end
private
def set_training
@training = Training.find(params[:id])
end
def valid_training_params
params.require(:training).permit(:id, users: [])
end
def set_training
@training = Training.find(params[:id])
end
def training_params
params.require(:training).permit(:id, :name, :description, :machine_ids, :plan_ids, :nb_total_places, :public_page, :disabled, training_image_attributes: [:attachment], machine_ids: [], plan_ids: [])
end
def valid_training_params
params.require(:training).permit(:id, users: [])
end
def training_params
params.require(:training)
.permit(:id, :name, :description, :machine_ids, :plan_ids, :nb_total_places, :public_page, :disabled,
training_image_attributes: [:attachment], machine_ids: [], plan_ids: [])
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for managing Training prices
class API::TrainingsPricingsController < API::ApiController
before_action :authenticate_user!
@ -6,11 +9,11 @@ class API::TrainingsPricingsController < API::ApiController
end
def update
if current_user.is_admin?
if current_user.admin?
@trainings_pricing = TrainingsPricing.find(params[:id])
_trainings_pricing_params = trainings_pricing_params
_trainings_pricing_params[:amount] = _trainings_pricing_params[:amount] * 100
if @trainings_pricing.update(_trainings_pricing_params)
trainings_pricing_parameters = trainings_pricing_params
trainings_pricing_parameters[:amount] = trainings_pricing_parameters[:amount] * 100
if @trainings_pricing.update(trainings_pricing_parameters)
render status: :ok
else
render status: :unprocessable_entity

View File

@ -1,11 +1,14 @@
# frozen_string_literal: true
# API Controller for managing front-end translations
class API::TranslationsController < API::ApiController
before_action :set_locale
def show
@translations = I18n.t params[:state]
if @translations.class.name == String.name and @translations.start_with?('translation missing')
render json: {error: @translations}, status: :unprocessable_entity
if @translations.class.name == String.name && @translations.start_with?('translation missing')
render json: { error: @translations }, status: :unprocessable_entity
else
render json: @translations, status: :ok
end
@ -15,4 +18,4 @@ class API::TranslationsController < API::ApiController
I18n.locale = params[:locale] || I18n.default_locale
end
end
end

View File

@ -1,8 +1,11 @@
# frozen_string_literal: true
# API Controller for resources of type Users with role :partner
class API::UsersController < API::ApiController
before_action :authenticate_user!
def index
if current_user.is_admin? and params[:role] == 'partner'
if current_user.admin? && params[:role] == 'partner'
@users = User.with_role(:partner).includes(:profile)
else
head 403
@ -10,11 +13,18 @@ class API::UsersController < API::ApiController
end
def create
if current_user.is_admin?
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')
@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')
if @user.save
@user.remove_role :member
@ -29,6 +39,7 @@ class API::UsersController < API::ApiController
end
private
def partner_params
params.require(:user).permit(:email, :first_name, :last_name)
end

View File

@ -1,4 +1,6 @@
# frozen_string_literal: true
# API Controller to get the fab-manager version
class API::VersionController < API::ApiController
before_action :authenticate_user!

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Wallet
class API::WalletController < API::ApiController
before_action :authenticate_user!
@ -19,9 +22,7 @@ class API::WalletController < API::ApiController
service = WalletService.new(user: current_user, wallet: @wallet)
transaction = service.credit(credit_params[:amount].to_f)
if transaction
if credit_params[:avoir]
service.create_avoir(transaction, credit_params[:avoir_date], credit_params[:avoir_description])
end
service.create_avoir(transaction, credit_params[:avoir_date], credit_params[:avoir_description]) if credit_params[:avoir]
render :show
else
head 422
@ -29,6 +30,7 @@ class API::WalletController < API::ApiController
end
private
def credit_params
params.permit(:id, :amount, :avoir, :avoir_date, :avoir_description)
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# Main controller for the backend application. All controllers inherits from it
class ApplicationController < ActionController::Base
include Pundit
# Prevent CSRF attacks by raising an exception.
@ -14,10 +17,10 @@ class ApplicationController < ActionController::Base
# Returning 403 Forbidden if permission is denied
rescue_from Pundit::NotAuthorizedError, with: :permission_denied
def index
end
def index; end
protected
def set_csrf_cookie
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end
@ -28,17 +31,29 @@ class ApplicationController < ActionController::Base
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) <<
{profile_attributes: [:phone, :last_name, :first_name,
:gender, :birthday, :interest, :software_mastered,
organization_attributes: [:name, address_attributes: [:address]]]}
devise_parameter_sanitizer.for(:sign_up).concat [:username, :is_allow_contact, :is_allow_newsletter, :cgu, :group_id]
{ profile_attributes: [:phone, :last_name, :first_name, :gender, :birthday, :interest, :software_mastered,
organization_attributes: [:name, address_attributes: [:address]]] }
devise_parameter_sanitizer.for(:sign_up).concat %i[username is_allow_contact is_allow_newsletter cgu group_id]
end
def default_url_options
{ :host => Rails.application.secrets.default_host, protocol: Rails.application.secrets.default_protocol }
{ host: Rails.application.secrets.default_host, protocol: Rails.application.secrets.default_protocol }
end
def permission_denied
head 403
end
# @return [User]
# This is a placeholder for Devise's current_user.
# As Devise generate the method at runtime, IDEs autocomplete features will complain about 'method not found'
def current_user
super
end
# This is a placeholder for Devise's authenticate_user! method.
def authenticate_user!
super
end
end

View File

@ -1,7 +1,11 @@
# frozen_string_literal: true
# Avoir is a special type of Invoice, which it inherits. It is used to
# refund an user, based on a previous invoice, or to credit an user's wallet.
class Avoir < Invoice
belongs_to :invoice
validates :avoir_mode, inclusion: {in: %w[stripe cheque transfer none cash wallet] }
validates :avoir_mode, inclusion: { in: %w[stripe cheque transfer none cash wallet] }
attr_accessor :invoice_items_ids

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# Coupon is a textual code associated with a discount rate or an amount of discount
class Coupon < ActiveRecord::Base
has_many :invoices
@ -6,7 +9,7 @@ class Coupon < ActiveRecord::Base
validates :name, presence: true
validates :code, presence: true
validates :code, format: { with: /\A[A-Z0-9\-]+\z/, message: 'only caps letters, numbers, and dashes'}
validates :code, format: { with: /\A[A-Z0-9\-]+\z/, message: 'only caps letters, numbers, and dashes' }
validates :code, uniqueness: true
validates :validity_per_user, presence: true
validates :validity_per_user, inclusion: { in: %w[once forever] }
@ -52,9 +55,9 @@ class Coupon < ActiveRecord::Base
end
def type
if amount_off.nil? and !percent_off.nil?
if amount_off.nil? && !percent_off.nil?
'percent_off'
elsif percent_off.nil? and !amount_off.nil?
elsif percent_off.nil? && !amount_off.nil?
'amount_off'
end
end

View File

@ -1,3 +1,5 @@
# Invoice correspond to a single purchase made by an user. This purchase may
# include reservation(s) and/or a subscription
class Invoice < ActiveRecord::Base
include NotifyWith::NotificationAttachedObject
require 'fileutils'
@ -13,21 +15,20 @@ class Invoice < ActiveRecord::Base
has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy
after_create :update_reference
after_commit :generate_and_send_invoice, on: [:create], :if => :persisted?
after_commit :generate_and_send_invoice, on: [:create], if: :persisted?
def file
dir = "invoices/#{user.id}"
# create directories if they doesn't exists (invoice & user_id)
FileUtils::mkdir_p dir
"#{dir}/#{self.filename}"
FileUtils.mkdir_p dir
"#{dir}/#{filename}"
end
def filename
"#{ENV['INVOICE_PREFIX']}-#{self.id}_#{self.created_at.strftime('%d%m%Y')}.pdf"
"#{ENV['INVOICE_PREFIX']}-#{id}_#{created_at.strftime('%d%m%Y')}.pdf"
end
def generate_reference
pattern = Setting.find_by(name: 'invoice_reference').value
@ -62,14 +63,14 @@ class Invoice < ActiveRecord::Base
reference.gsub!(/DD(?![^\[]*\])/, Time.now.strftime('%-d'))
# information about online selling (X[text])
if self.stp_invoice_id
if stp_invoice_id
reference.gsub!(/X\[([^\]]+)\]/, '\1')
else
reference.gsub!(/X\[([^\]]+)\]/, ''.to_s)
end
# information about wallet (W[text])
#reference.gsub!(/W\[([^\]]+)\]/, ''.to_s)
# reference.gsub!(/W\[([^\]]+)\]/, ''.to_s)
# remove information about refunds (R[text])
reference.gsub!(/R\[([^\]]+)\]/, ''.to_s)
@ -83,7 +84,7 @@ class Invoice < ActiveRecord::Base
end
def order_number
pattern = Setting.find_by({name: 'invoice_order-nb'}).value
pattern = Setting.find_by(name: 'invoice_order-nb').value
# global invoice number (nn..nn)
reference = pattern.gsub(/n+(?![^\[]*\])/) do |match|
@ -103,34 +104,35 @@ class Invoice < ActiveRecord::Base
end
# full year (YYYY)
reference.gsub!(/YYYY(?![^\[]*\])/, self.created_at.strftime('%Y'))
reference.gsub!(/YYYY(?![^\[]*\])/, created_at.strftime('%Y'))
# year without century (YY)
reference.gsub!(/YY(?![^\[]*\])/, self.created_at.strftime('%y'))
reference.gsub!(/YY(?![^\[]*\])/, created_at.strftime('%y'))
# abreviated month name (MMM)
reference.gsub!(/MMM(?![^\[]*\])/, self.created_at.strftime('%^b'))
# abbreviated month name (MMM)
reference.gsub!(/MMM(?![^\[]*\])/, created_at.strftime('%^b'))
# month of the year, zero-padded (MM)
reference.gsub!(/MM(?![^\[]*\])/, self.created_at.strftime('%m'))
reference.gsub!(/MM(?![^\[]*\])/, created_at.strftime('%m'))
# month of the year, non zero-padded (M)
reference.gsub!(/M(?![^\[]*\])/, self.created_at.strftime('%-m'))
reference.gsub!(/M(?![^\[]*\])/, created_at.strftime('%-m'))
# day of the month, zero-padded (DD)
reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%d'))
reference.gsub!(/DD(?![^\[]*\])/, created_at.strftime('%d'))
# day of the month, non zero-padded (DD)
reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%-d'))
reference.gsub!(/DD(?![^\[]*\])/, created_at.strftime('%-d'))
reference
end
# for debug & used by rake task "fablab:regenerate_invoices"
def regenerate_invoice_pdf
pdf = ::PDF::Invoice.new(self).render
pdf = ::PDF::Invoice.new(self, nil).render
File.binwrite(file, pdf)
end
def build_avoir(attrs = {})
raise Exception if has_avoir === true or prevent_refund?
avoir = Avoir.new(self.dup.attributes)
raise Exception if refunded? === true || prevent_refund?
avoir = Avoir.new(dup.attributes)
avoir.type = 'Avoir'
avoir.attributes = attrs
avoir.reference = nil
@ -142,15 +144,15 @@ class Invoice < ActiveRecord::Base
paid_items = 0
refund_items = 0
invoice_items.each do |ii|
paid_items += 1 unless ii.amount == 0
if attrs[:invoice_items_ids].include? ii.id # list of items to refund (partial refunds)
raise Exception if ii.invoice_item # cannot refund an item that was already refunded
refund_items += 1 unless ii.amount == 0
avoir_ii = avoir.invoice_items.build(ii.dup.attributes)
avoir_ii.created_at = avoir.avoir_date
avoir_ii.invoice_item_id = ii.id
avoir.total += avoir_ii.amount
end
paid_items += 1 unless ii.amount.zero?
next unless attrs[:invoice_items_ids].include? ii.id # list of items to refund (partial refunds)
raise Exception if ii.invoice_item # cannot refund an item that was already refunded
refund_items += 1 unless ii.amount.zero?
avoir_ii = avoir.invoice_items.build(ii.dup.attributes)
avoir_ii.created_at = avoir.avoir_date
avoir_ii.invoice_item_id = ii.id
avoir.total += avoir_ii.amount
end
# handle coupon
unless avoir.coupon_id.nil?
@ -167,9 +169,9 @@ class Invoice < ActiveRecord::Base
avoir
end
def is_subscription_invoice?
def subscription_invoice?
invoice_items.each do |ii|
return true if ii.subscription and !ii.subscription.expired?
return true if ii.subscription && !ii.subscription.expired?
end
false
end
@ -178,7 +180,7 @@ class Invoice < ActiveRecord::Base
# Test if the current invoice has been refund, totally or partially.
# @return {Boolean|'partial'}, true means fully refund, false means not refunded
##
def has_avoir
def refunded?
if avoir
invoice_items.each do |item|
return 'partial' unless item.invoice_item
@ -195,7 +197,7 @@ class Invoice < ActiveRecord::Base
# @return {Boolean}
##
def prevent_refund?
if invoiced_type == 'Reservation' and invoiced.reservable_type == 'Training'
if invoiced_type == 'Reservation' && invoiced.reservable_type == 'Training'
user.trainings.include?(invoiced.reservable_id)
else
false
@ -204,13 +206,15 @@ class Invoice < ActiveRecord::Base
# get amount total paid
def amount_paid
total - (wallet_amount ? wallet_amount : 0)
total - (wallet_amount || 0)
end
private
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})"
puts "Creating an InvoiceWorker job to generate the following invoice: id(#{id}), invoiced_id(#{invoiced_id}), " \
"invoiced_type(#{invoiced_type}), user_id(#{user_id})"
end
InvoiceWorker.perform_async(id, user&.subscription&.expired_at)
end
@ -233,21 +237,21 @@ class Invoice < ActiveRecord::Base
##
def number_of_invoices(range)
case range.to_s
when 'day'
start = DateTime.current.beginning_of_day
ending = DateTime.current.end_of_day
when 'month'
start = DateTime.current.beginning_of_month
ending = DateTime.current.end_of_month
when 'year'
start = DateTime.current.beginning_of_year
ending = DateTime.current.end_of_year
else
return self.id
end
if defined? start and defined? ending
Invoice.where('created_at >= :start_date AND created_at < :end_date', {start_date: start, end_date: ending}).length
when 'day'
start = DateTime.current.beginning_of_day
ending = DateTime.current.end_of_day
when 'month'
start = DateTime.current.beginning_of_month
ending = DateTime.current.end_of_month
when 'year'
start = DateTime.current.beginning_of_year
ending = DateTime.current.end_of_year
else
return id
end
return Invoice.count unless defined? start && defined? ending
Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: start, end_date: ending).length
end
end

View File

@ -43,4 +43,5 @@ class NotificationType
notify_member_about_coupon
notify_member_reservation_reminder
]
# deprecated: notify_admin_invoicing_changed
end

View File

@ -27,8 +27,8 @@ class Plan < ActiveRecord::Base
validates :amount, :group, :base_name, presence: true
validates :interval_count, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
validates :interval_count, numericality: { less_than: 12 }, if: proc { |plan| plan.interval == 'month' }
validates :interval_count, numericality: { less_than: 52 }, if: proc { |plan| plan.interval == 'week' }
validates :interval_count, numericality: { less_than: 13 }, if: proc { |plan| plan.interval == 'month' }
validates :interval_count, numericality: { less_than: 53 }, if: proc { |plan| plan.interval == 'week' }
validates :interval, inclusion: { in: %w[year month week] }
validates :base_name, :slug, presence: true

View File

@ -190,7 +190,7 @@ class Reservation < ActiveRecord::Base
@coupon = Coupon.find_by(code: coupon_code)
raise InvalidCouponError if @coupon.nil? || @coupon.status(user.id) != 'active'
total = get_cart_total
total = cart_total
discount = if @coupon.type == 'percent_off'
(total * @coupon.percent_off / 100).to_i
@ -210,7 +210,7 @@ class Reservation < ActiveRecord::Base
end
end
@wallet_amount_debit = get_wallet_amount_debit
@wallet_amount_debit = wallet_amount_debit
if @wallet_amount_debit != 0 && !on_site
invoice_items << Stripe::InvoiceItem.create(
customer: user.stp_customer_id,
@ -261,9 +261,8 @@ class Reservation < ActiveRecord::Base
# error handling
invoice_items.each(&:delete)
errors[:card] << subscription.errors[:card].join
if subscription.errors[:payment]
errors[:payment] << subscription.errors[:payment].join
end
errors[:payment] << subscription.errors[:payment].join if subscription.errors[:payment]
return false
end
@ -347,50 +346,31 @@ class Reservation < ActiveRecord::Base
end
def clear_payment_info(card, invoice)
begin
card.delete if card
if invoice
invoice.closed = true
invoice.save
end
rescue Stripe::InvalidRequestError => e
logger.error e
rescue Stripe::AuthenticationError => e
logger.error e
rescue Stripe::APIConnectionError => e
logger.error e
rescue Stripe::StripeError => e
logger.error e
rescue => e
logger.error e
card&.delete
if invoice
invoice.closed = true
invoice.save
end
rescue Stripe::InvalidRequestError => e
logger.error e
rescue Stripe::AuthenticationError => e
logger.error e
rescue Stripe::APIConnectionError => e
logger.error e
rescue Stripe::StripeError => e
logger.error e
rescue StandardError => e
logger.error e
end
def clean_pending_strip_invoice_items
pending_invoice_items = Stripe::InvoiceItem.list(customer: user.stp_customer_id, limit: 100).data.select { |ii| ii.invoice.nil? }
pending_invoice_items.each do |ii|
ii.delete
end
pending_invoice_items.each(&:delete)
end
def save_with_local_payment(coupon_code = nil)
if user.invoicing_disabled?
if valid?
### generate invoice only for calcul price, TODO refactor!!
build_invoice(user: user)
generate_invoice_items(true, coupon_code)
@wallet_amount_debit = get_wallet_amount_debit
self.invoice = nil
###
save!
UsersCredits::Manager.new(reservation: self).update_credits
return true
end
else
build_invoice(user: user)
generate_invoice_items(true, coupon_code)
end
build_invoice(user: user)
generate_invoice_items(true, coupon_code)
return false unless valid?
@ -420,38 +400,38 @@ class Reservation < ActiveRecord::Base
def total_booked_seats
total = nb_reserve_places
if tickets.count > 0
total += tickets.map(&:booked).map(&:to_i).reduce(:+)
end
total += tickets.map(&:booked).map(&:to_i).reduce(:+) if tickets.count.positive?
total
end
private
def machine_not_already_reserved
already_reserved = false
self.slots.each do |slot|
slots.each do |slot|
same_hour_slots = Slot.joins(:reservations).where(
reservations: { reservable_type: self.reservable_type,
reservable_id: self.reservable_id
},
start_at: slot.start_at,
end_at: slot.end_at,
availability_id: slot.availability_id,
canceled_at: nil)
reservations: { reservable_type: reservable_type, reservable_id: reservable_id },
start_at: slot.start_at,
end_at: slot.end_at,
availability_id: slot.availability_id,
canceled_at: nil
)
if same_hour_slots.any?
already_reserved = true
break
end
end
errors.add(:machine, "already reserved") if already_reserved
errors.add(:machine, 'already reserved') if already_reserved
end
def training_not_fully_reserved
slot = self.slots.first
errors.add(:training, "already fully reserved") if Availability.find(slot.availability_id).completed?
slot = slots.first
errors.add(:training, 'already fully reserved') if Availability.find(slot.availability_id).completed?
end
private
def notify_member_create_reservation
NotificationCenter.call type: 'notify_member_create_reservation',
receiver: user,
@ -480,8 +460,8 @@ class Reservation < ActiveRecord::Base
reservable.update_columns(nb_free_places: nb_free_places)
end
def get_cart_total
total = (self.invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) or 0)
def cart_total
total = (invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) or 0)
if plan_id.present?
plan = Plan.find(plan_id)
total += plan.amount
@ -489,30 +469,24 @@ class Reservation < ActiveRecord::Base
total
end
def get_wallet_amount_debit
total = get_cart_total
if @coupon
total = CouponService.new.apply(total, @coupon, user.id)
end
def wallet_amount_debit
total = cart_total
total = CouponService.new.apply(total, @coupon, user.id) if @coupon
wallet_amount = (user.wallet.amount * 100).to_i
wallet_amount >= total ? total : wallet_amount
end
def debit_user_wallet
if @wallet_amount_debit.present? and @wallet_amount_debit != 0
amount = @wallet_amount_debit / 100.0
wallet_transaction = WalletService.new(user: user, wallet: user.wallet).debit(amount, self)
# wallet debit success
if wallet_transaction
# payment by online or (payment by local and invoice isnt disabled)
if stp_invoice_id or !user.invoicing_disabled?
self.invoice.update_columns(wallet_amount: @wallet_amount_debit, wallet_transaction_id: wallet_transaction.id)
end
else
raise DebitWalletError
end
end
return unless @wallet_amount_debit.present? && @wallet_amount_debit != 0
amount = @wallet_amount_debit / 100.0
wallet_transaction = WalletService.new(user: user, wallet: user.wallet).debit(amount, self)
# wallet debit success
raise DebitWalletError unless wallet_transaction
invoice.update_columns(wallet_amount: @wallet_amount_debit, wallet_transaction_id: wallet_transaction.id)
end
# this function only use for compute total of reservation before save
@ -520,13 +494,11 @@ class Reservation < ActiveRecord::Base
total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+)
unless coupon_code.nil?
cp = Coupon.find_by(code: coupon_code)
if not cp.nil? and cp.status(user.id) == 'active'
total = CouponService.new.apply(total, cp, user.id)
else
raise InvalidCouponError
end
raise InvalidCouponError unless !cp.nil? && cp.status(user.id) == 'active'
total = CouponService.new.apply(total, cp, user.id)
end
return total - get_wallet_amount_debit
total - wallet_amount_debit
end
##
@ -539,14 +511,12 @@ class Reservation < ActiveRecord::Base
unless coupon_code.nil?
cp = Coupon.find_by(code: coupon_code)
if not cp.nil? and cp.status(user.id) == 'active'
total = CouponService.new.apply(total, cp, user.id)
self.invoice.coupon_id = cp.id
else
raise InvalidCouponError
end
raise InvalidCouponError unless !cp.nil? && cp.status(user.id) == 'active'
total = CouponService.new.apply(total, cp, user.id)
invoice.coupon_id = cp.id
end
self.invoice.total = total
invoice.total = total
end
end

View File

@ -142,14 +142,12 @@ class Subscription < ActiveRecord::Base
# debit wallet
wallet_transaction = debit_user_wallet
unless user.invoicing_disabled?
invoc = generate_invoice(nil, coupon_code)
if wallet_transaction
invoc.wallet_amount = @wallet_amount_debit
invoc.wallet_transaction_id = wallet_transaction.id
end
invoc.save
invoc = generate_invoice(nil, coupon_code)
if wallet_transaction
invoc.wallet_amount = @wallet_amount_debit
invoc.wallet_transaction_id = wallet_transaction.id
end
invoc.save
end
true
end

View File

@ -1,8 +1,12 @@
# frozen_string_literal: true
# User is a physical or moral person with its authentication parameters
# It is linked to the Profile model with hold informations about this person (like address, name, etc.)
class User < ActiveRecord::Base
include NotifyWith::NotificationReceiver
include NotifyWith::NotificationAttachedObject
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable,
:confirmable, :async
rolify
@ -50,6 +54,8 @@ class User < ActiveRecord::Base
has_many :exports, dependent: :destroy
has_many :history_values, dependent: :nullify
# fix for create admin user
before_save do
email&.downcase!
@ -59,7 +65,6 @@ class User < ActiveRecord::Base
after_create :create_a_wallet
after_commit :create_stripe_customer, on: [:create]
after_commit :notify_admin_when_user_is_created, on: :create
after_update :notify_admin_invoicing_changed, if: :invoicing_disabled_changed?
after_update :notify_group_changed, if: :group_id_changed?
attr_accessor :cgu
@ -74,7 +79,7 @@ class User < ActiveRecord::Base
scope :without_subscription, -> { includes(:subscriptions).where(subscriptions: { user_id: nil }) }
scope :with_subscription, -> { joins(:subscriptions) }
def to_json(options = {})
def to_json(*)
ApplicationController.new.view_context.render(
partial: 'api/members/member',
locals: { member: self },
@ -88,7 +93,7 @@ class User < ActiveRecord::Base
end
def training_machine?(machine)
return true if is_admin?
return true if admin?
trainings.map(&:machines).flatten.uniq.include?(machine)
end
@ -107,11 +112,11 @@ class User < ActiveRecord::Base
subscriptions.order(:created_at).last
end
def is_admin?
def admin?
has_role? :admin
end
def is_member?
def member?
has_role? :member
end
@ -122,7 +127,7 @@ class User < ActiveRecord::Base
def generate_subscription_invoice
return unless subscription
subscription.generate_and_save_invoice unless invoicing_disabled?
subscription.generate_and_save_invoice
end
def stripe_customer
@ -286,7 +291,6 @@ class User < ActiveRecord::Base
false
end
private
def assign_default_role
@ -341,12 +345,4 @@ class User < ActiveRecord::Base
receiver: self,
attached_object: self
end
def notify_admin_invoicing_changed
NotificationCenter.call type: 'notify_admin_invoicing_changed',
receiver: User.admins,
attached_object: self
end
end

View File

@ -1,9 +1,9 @@
class AdminPolicy < ApplicationPolicy
def index?
user.is_admin?
user.admin?
end
def create?
user.is_admin?
user.admin?
end
end

View File

@ -1,7 +1,7 @@
class AgeRangePolicy < ApplicationPolicy
%w(create update destroy show).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -8,7 +8,7 @@ class AuthProviderPolicy < ApplicationPolicy
%w(index? show? create? update? destroy? mapping_fields?).each do |action|
define_method action do
user.is_admin?
user.admin?
end
end

View File

@ -1,7 +1,7 @@
class AvailabilityPolicy < ApplicationPolicy
%w(index? show? create? update? destroy? reservations? export? lock?).each do |action|
define_method action do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,7 +1,7 @@
class CategoryPolicy < ApplicationPolicy
%w(create update destroy show).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,6 +1,6 @@
class ComponentPolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?

View File

@ -1,7 +1,7 @@
class CouponPolicy < ApplicationPolicy
%w(index show create update destroy send_to).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,6 +1,6 @@
class CreditPolicy < ApplicationPolicy
def index?
user.is_admin?
user.admin?
end
def create?

View File

@ -1,11 +1,11 @@
class CustomAssetPolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?
user.is_admin?
user.admin?
end
end

View File

@ -1,7 +1,7 @@
class EventPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.nil? or (user and !user.is_admin?)
if user.nil? or (user and !user.admin?)
scope.includes(:event_image, :event_files, :availability, :category)
.where('availabilities.start_at >= ?', Time.now)
.order('availabilities.start_at ASC')
@ -14,7 +14,7 @@ class EventPolicy < ApplicationPolicy
end
def create?
user.is_admin?
user.admin?
end
def update?

View File

@ -1,7 +1,7 @@
class EventThemePolicy < ApplicationPolicy
%w(create update destroy show).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,7 +1,7 @@
class ExportPolicy < Struct.new(:user, :export)
%w(export_reservations export_members export_subscriptions export_availabilities download status).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,13 +1,13 @@
class GroupPolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?
user.is_admin?
user.admin?
end
def destroy?
user.is_admin? and record.destroyable?
user.admin? and record.destroyable?
end
end

View File

@ -1,17 +1,17 @@
class InvoicePolicy < ApplicationPolicy
def index?
user.is_admin?
user.admin?
end
def download?
user.is_admin? or (record.user_id == user.id)
user.admin? or (record.user_id == user.id)
end
def create?
user.is_admin?
user.admin?
end
def list?
user.is_admin?
user.admin?
end
end

View File

@ -1,6 +1,6 @@
class LicencePolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?

View File

@ -1,13 +1,13 @@
class MachinePolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?
user.is_admin?
user.admin?
end
def destroy?
user.is_admin? and record.destroyable?
user.admin? and record.destroyable?
end
end

View File

@ -1,17 +1,17 @@
class PartnerPlanPolicy < ApplicationPolicy
def index?
user.is_admin?
user.admin?
end
def create?
user.is_admin?
user.admin?
end
def update?
user.is_admin?
user.admin?
end
def destroy?
user.is_admin? and record.destroyable?
user.admin? and record.destroyable?
end
end

View File

@ -1,13 +1,13 @@
class PlanPolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?
user.is_admin?
user.admin?
end
def destroy?
user.is_admin? and record.destroyable?
user.admin? and record.destroyable?
end
end

View File

@ -1,7 +1,7 @@
class PriceCategoryPolicy < ApplicationPolicy
%w(show create update destroy).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,9 +1,9 @@
class PricePolicy < ApplicationPolicy
def index?
user.is_admin?
user.admin?
end
def update?
user.is_admin?
user.admin?
end
end

View File

@ -1,5 +1,5 @@
class PricingPolicy < ApplicationPolicy
def update?
user.is_admin?
user.admin?
end
end

View File

@ -15,10 +15,10 @@ class ProjectPolicy < ApplicationPolicy
end
def update?
user.is_admin? or record.author == user or record.users.include?(user)
user.admin? or record.author == user or record.users.include?(user)
end
def destroy?
user.is_admin? or record.author == user
user.admin? or record.author == user
end
end

View File

@ -1,5 +1,5 @@
class ReservationPolicy < ApplicationPolicy
def update?
user.is_admin? or record.user == user
user.admin? or record.user == user
end
end

View File

@ -1,7 +1,7 @@
class SettingPolicy < ApplicationPolicy
%w(update).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -5,11 +5,11 @@ class SlotPolicy < ApplicationPolicy
enabled = (Setting.find_by( name: 'booking_move_enable').value == 'true')
# these condition does not apply to admins
user.is_admin? or
user.admin? or
(record.reservation.user == user and enabled and ((record.start_at - Time.now).to_i / 3600 >= delay))
end
def cancel?
user.is_admin? or record.reservation.user == user
user.admin? or record.reservation.user == user
end
end

View File

@ -1,13 +1,13 @@
class SpacePolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?
user.is_admin?
user.admin?
end
def destroy?
user.is_admin? and record.destroyable?
user.admin? and record.destroyable?
end
end

View File

@ -2,7 +2,7 @@ class StatisticPolicy < ApplicationPolicy
%w(index account event machine project subscription training user space scroll export_subscription export_machine
export_training export_event export_account export_project export_space export_global).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,9 +1,9 @@
class SubscriptionPolicy < ApplicationPolicy
def show?
user.is_admin? or record.user_id == user.id
user.admin? or record.user_id == user.id
end
def update?
user.is_admin?
user.admin?
end
end

View File

@ -1,6 +1,6 @@
class TagPolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?

View File

@ -1,6 +1,6 @@
class ThemePolicy < ApplicationPolicy
def create?
user.is_admin?
user.admin?
end
def update?

View File

@ -7,15 +7,15 @@ class TrainingPolicy < ApplicationPolicy
%w(create update).each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
def destroy?
user.is_admin? and record.destroyable?
user.admin? and record.destroyable?
end
def availabilities?
user.is_admin?
user.admin?
end
end

View File

@ -1,20 +1,22 @@
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.is_admin?
scope.includes(:group, :training_credits, :machine_credits, :subscriptions => [:plan => [:credits]], :profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc')
if user.admin?
scope.includes(:group, :training_credits, :machine_credits, subscriptions: [plan: [:credits]], profile: [:user_avatar])
.joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc')
else
scope.includes(:profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").where(is_allow_contact: true).order('users.created_at desc')
scope.includes(profile: [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'")
.where(is_allow_contact: true).order('users.created_at desc')
end
end
end
def show?
user.is_admin? or (record.is_allow_contact and record.is_member?) or (user.id == record.id)
user.admin? or (record.is_allow_contact and record.member?) or (user.id == record.id)
end
def update?
user.is_admin? or (user.id == record.id)
user.admin? or (user.id == record.id)
end
def destroy?
@ -25,9 +27,9 @@ class UserPolicy < ApplicationPolicy
user.id == record.id
end
%w(list create mapping).each do |action|
%w[list create mapping].each do |action|
define_method "#{action}?" do
user.is_admin?
user.admin?
end
end
end

View File

@ -1,5 +1,5 @@
class VersionPolicy < ApplicationPolicy
def show?
user.is_admin?
user.admin?
end
end

View File

@ -1,13 +1,13 @@
class WalletPolicy < ApplicationPolicy
def by_user?
user.is_admin? or user == record.user
user.admin? or user == record.user
end
def transactions?
user.is_admin? or user == record.user
user.admin? or user == record.user
end
def credit?
user.is_admin?
user.admin?
end
end

Some files were not shown because too many files have changed in this diff Show More