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

Merge branch 'us78' into host

This commit is contained in:
Sylvain 2019-01-10 15:15:35 +01:00
commit 79ad9ce8e2
65 changed files with 1671 additions and 745 deletions

2
.gitignore vendored
View File

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

View File

@ -7,10 +7,14 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 9
Metrics/AbcSize:
Max: 42
Max: 45
Style/BracesAroundHashParameters:
EnforcedStyle: context_dependent
Style/RegexpLiteral:
EnforcedStyle: slashes
Style/EmptyElse:
EnforcedStyle: empty
Style/ClassAndModuleChildren:
EnforcedStyle: compact
Style/AndOr:
EnforcedStyle: conditionals

View File

@ -2,6 +2,9 @@
- 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
- Refactored frontend invoices translations
## v2.8.1 2019 January 02

View File

@ -44,6 +44,7 @@ RUN mkdir -p /usr/src/app/exports
RUN mkdir -p /usr/src/app/log
RUN mkdir -p /usr/src/app/public/uploads
RUN mkdir -p /usr/src/app/public/assets
RUN mkdir -p /usr/src/app/accounting
RUN mkdir -p /usr/src/app/tmp/sockets
RUN mkdir -p /usr/src/app/tmp/pids
@ -64,6 +65,7 @@ VOLUME /usr/src/app/exports
VOLUME /usr/src/app/public
VOLUME /usr/src/app/public/uploads
VOLUME /usr/src/app/public/assets
VOLUME /usr/src/app/accounting
VOLUME /var/log/supervisor
# Expose port 3000 to the Docker host, so we can access it

View File

@ -332,6 +332,7 @@ This can be achieved doing the following:
- `db/migrate/20150604131525_add_meta_data_to_notifications.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype.
- `db/migrate/20160915105234_add_transformation_to_o_auth2_mapping.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype.
- `db/migrate/20181217103441_migrate_settings_value_to_history_values.rb` is using `SELECT DISTINCT ON`.
- `db/migrate/20190107111749_protect_accounting_periods.rb` is using `CREATE RULE` and `DROP RULE`.
- If you intend to contribute to the project code, you will need to run the test suite with `rake test`.
This also requires your user to have the _SUPERUSER_ role.
Please see the [known issues](#known-issues) section for more information about this.

View File

@ -17,8 +17,8 @@
/**
* Controller used in the admin invoices listing page
*/
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'invoices', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t',
function ($scope, $state, Invoice, invoices, $uibModal, growl, $filter, Setting, settings, _t) {
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t',
function ($scope, $state, Invoice, AccountingPeriod, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t) {
/* PRIVATE STATIC CONSTANTS */
// number of invoices loaded each time we click on 'load more...'
@ -105,12 +105,13 @@ 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',
resolve: {
invoice () { return invoice; }
invoice () { return invoice; },
closedPeriods() { return AccountingPeriod.query().$promise; }
}
});
@ -119,7 +120,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 +194,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 +232,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 +278,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);
});
});
@ -316,24 +317,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 +347,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 +362,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);
});
};
@ -387,6 +388,37 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
return invoiceSearch(true);
};
/**
* Open a modal allowing the user to close an accounting period and to
* view all periods already closed.
*/
$scope.closeAnAccountingPeriod = function() {
// open modal
$uibModal.open({
templateUrl: '<%= asset_path "admin/invoices/closePeriodModal.html" %>',
controller: 'ClosePeriodModalController',
size: 'lg',
resolve: {
periods() { return AccountingPeriod.query().$promise; },
lastClosingEnd() { return AccountingPeriod.lastClosingEnd().$promise; },
}
});
}
/**
* Test if the given date is within a closed accounting period
* @param date {Date} date to test
* @returns {boolean} true if closed, false otherwise
*/
$scope.isDateClosed = function(date) {
for (const period of closedPeriods) {
if (moment(date).isBetween(moment.utc(period.start_at).startOf('day'), moment.utc(period.end_at).endOf('day'), null, '[]')) {
return true;
}
}
return false;
}
/* PRIVATE SCOPE */
/**
@ -418,9 +450,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);
}
);
@ -496,9 +528,9 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
/**
* Controller used in the invoice refunding modal window
*/
Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModalInstance', 'invoice', 'Invoice', 'growl', '_t',
function ($scope, $uibModalInstance, invoice, Invoice, growl, _t) {
/* PUBLIC SCOPE */
Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModalInstance', 'invoice', 'closedPeriods', 'Invoice', 'growl', '_t',
function ($scope, $uibModalInstance, invoice, closedPeriods, Invoice, growl, _t) {
/* PUBLIC SCOPE */
// invoice linked to the current refund
$scope.invoice = invoice;
@ -515,11 +547,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 +574,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 +589,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,17 +597,31 @@ 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'); };
/**
* Test if the given date is within a closed accounting period
* @param date {Date} date to test
* @returns {boolean} true if closed, false otherwise
*/
$scope.isDateClosed = function(date) {
for (const period of closedPeriods) {
if (moment(date).isBetween(moment.utc(period.start_at).startOf('day'), moment.utc(period.end_at).endOf('day'), null, '[]')) {
return true;
}
}
return false;
}
/* PRIVATE SCOPE */
/**
@ -592,7 +638,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' });
}
};
@ -600,3 +646,90 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
return initialize();
}
]);
/**
* Controller used in the modal window allowing an admin to close an accounting period
*/
Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$uibModalInstance', 'Invoice', 'AccountingPeriod', 'periods', 'lastClosingEnd','dialogs', 'growl', '_t',
function ($scope, $uibModalInstance, Invoice, AccountingPeriod, periods, lastClosingEnd, dialogs, growl, _t) {
const YESTERDAY = moment.utc({ h: 0, m: 0, s: 0, ms: 0 }).subtract(1, 'day').toDate();
const LAST_CLOSING = moment.utc(lastClosingEnd.last_end_date).toDate();
/* PUBLIC SCOPE */
// date pickers values are bound to these variables
$scope.period = {
start_at: LAST_CLOSING,
end_at: YESTERDAY
};
// any form errors will come here
$scope.errors = {};
// existing closed periods, provided by the API
$scope.accountingPeriods = periods;
// AngularUI-Bootstrap datepickers parameters to define the period to close
$scope.datePicker = {
format: Fablab.uibDateFormat,
// default: datePicker are not shown
startOpened: false,
endOpened: false,
minDate: LAST_CLOSING,
maxDate: YESTERDAY,
options: {
startingDay: Fablab.weekStartingDay
}
};
/**
* Callback to open the datepicker
*/
$scope.openDatePicker = function ($event, pickerId) {
$event.preventDefault();
$event.stopPropagation();
$scope.datePicker[`${pickerId}Opened`] = true;
};
/**
* Validate the close period creation
*/
$scope.ok = function () {
dialogs.confirm(
{
resolve: {
object () {
return {
title: _t('invoices.confirmation_required'),
msg: _t(
'invoices.confirm_close_START_END',
{ START: moment($scope.period.start_at).format('LL'), END: moment($scope.period.end_at).format('LL') }
)
};
}
}
},
function () { // creation confirmed
AccountingPeriod.save({ accounting_period: $scope.period }, function (resp) {
growl.success(_t(
'invoices.period_START_END_closed_success',
{ START: moment(resp.start_at).format('LL'), END: moment(resp.end_at).format('LL') }
));
$uibModalInstance.close(resp);
}
, function(error) {
growl.error(_t('invoices.failed_to_close_period'));
$scope.errors = error.data;
});
}
);
};
/**
* Cancel the refund, dismiss the modal window
*/
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
}
]);

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

@ -885,6 +885,7 @@ angular.module('application.router', ['ui.router'])
query: { number: '', customer: '', date: null, order_by: '-reference', page: 1, size: 20 }
}).$promise;
}],
closedPeriods: [ 'AccountingPeriod', function(AccountingPeriod) { return AccountingPeriod.query().$promise; }],
translations: ['Translations', function (Translations) { return Translations.query('app.admin.invoices').$promise; }]
}
})

View File

@ -0,0 +1,12 @@
'use strict';
Application.Services.factory('AccountingPeriod', ['$resource', function ($resource) {
return $resource('/api/accounting_periods/:id',
{ id: '@id' }, {
lastClosingEnd: {
method: 'GET',
url: '/api/accounting_periods/last_closing_end'
}
}
);
}]);

View File

@ -616,4 +616,8 @@ padding: 10px;
& > i.fileinput-exists {
margin-right: 5px;
}
}
}
.help-block.error {
color: #ff565d;
}

View File

@ -178,4 +178,42 @@
border-radius: 5px;
font-size: small;
}
}
}
table.closings-table {
width: 100%;
border-spacing: 0;
thead, tbody, tr, th, td { display: block; }
thead tr {
/* fallback */
width: 97%;
/* minus scroll bar width */
width: -webkit-calc(100% - 16px);
width: -moz-calc(100% - 16px);
width: calc(100% - 16px);
}
thead tr th {
border-bottom: 0;
}
tr:after { /* clearing float */
content: ' ';
display: block;
visibility: hidden;
clear: both;
}
tbody {
height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
tbody td, thead th {
width: 24%; /* 24% is less than (100% / 4 cols) = 25% */
float: left;
}
}

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"
@ -14,28 +14,29 @@
uib-datepicker-popup="{{datePicker.format}}"
datepicker-options="datePicker.options"
is-open="datePicker.opened"
date-disabled="isDateClosed(date, mode)"
placeholder="{{datePicker.format}}"
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 +49,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 +58,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

@ -0,0 +1,75 @@
<div class="modal-header">
<h3 class="text-center red" translate>{{ 'invoices.close_accounting_period' }}</h3>
</div>
<div class="modal-body">
<form name="closePeriodForm" novalidate="novalidate" class="row">
<div class="form-group col-md-6" ng-class="{'has-error': closePeriodForm.start_at.$dirty && closePeriodForm.start_at.$invalid }">
<label translate>{{ 'invoices.close_from_date' }}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text"
class="form-control"
name="start_at"
ng-model="period.start_at"
uib-datepicker-popup="{{datePicker.format}}"
datepicker-options="datePicker.options"
is-open="datePicker.startOpened"
min-date="datePicker.minDate"
max-date="datePicker.maxDate"
init-date="period.start_at"
placeholder="{{datePicker.format}}"
ng-click="openDatePicker($event, 'start')"
required/>
</div>
<span class="help-block" ng-show="closePeriodForm.start_at.$dirty && closePeriodForm.start_at.$error.required" translate>{{ 'invoices.start_date_is_required' }}</span>
<span class="help-block error" ng-show="errors.start_at">{{ errors.start_at[0] }}</span>
</div>
<div class="form-group col-md-6" ng-class="{'has-error': closePeriodForm.end_at.$dirty && closePeriodForm.end_at.$invalid }">
<label translate>{{ 'invoices.close_until_date' }}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text"
class="form-control"
name="end_at"
ng-model="period.end_at"
uib-datepicker-popup="{{datePicker.format}}"
datepicker-options="datePicker.options"
is-open="datePicker.endOpened"
min-date="datePicker.minDate"
max-date="datePicker.maxDate"
init-date="period.end_at"
placeholder="{{datePicker.format}}"
ng-click="openDatePicker($event, 'end')"
required/>
</div>
<span class="help-block" ng-show="closePeriodForm.end_at.$dirty && closePeriodForm.end_at.$error.required" translate>{{ 'invoices.end_date_is_required' }}</span>
<span class="help-block error" ng-show="errors.end_at">{{ errors.end_at[0] }}</span>
</div>
</form>
<div>
<h4 translate>{{ 'invoices.previous_closings' }}</h4>
<table class="table closings-table" ng-show="accountingPeriods.length > 0">
<thead>
<tr>
<th translate>{{ 'invoices.start_date' }}</th>
<th translate>{{ 'invoices.end_date' }}</th>
<th translate>{{ 'invoices.closed_at' }}</th>
<th translate>{{ 'invoices.closed_by' }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="period in accountingPeriods">
<td>{{period.start_at | amDateFormat:'L'}}</td>
<td>{{period.end_at | amDateFormat:'L'}}</td>
<td>{{period.closed_at | amDateFormat:'L'}}</td>
<td>{{period.user_name}}</td>
</tr>
</tbody>
</table>
<div ng-show="accountingPeriods.length === 0" translate>{{ 'invoices.no_periods'}}</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" ng-disabled="closePeriodForm.$invalid" translate>{{ 'confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
</div>

View File

@ -7,10 +7,14 @@
</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>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-default rounded m-t-sm text-sm" ng-click="closeAnAccountingPeriod()"><i class="fa fa-calendar-check-o"></i></a>
</section>
</div>
</div>
</section>
@ -18,14 +22,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 +38,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 +47,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 +61,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 +82,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 }}
<a class="btn btn-default" ng-click="generateAvoirForInvoice(invoice)" ng-if="(!invoice.has_avoir || invoice.has_avoir == 'partial') && !invoice.is_avoir && !invoice.prevent_refund && !isDateClosed(invoice.created_at)">
<i class="fa fa-reply"></i> {{ 'invoices.credit_note' | translate }}
</a>
</div>
</td>
@ -92,9 +96,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 +107,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 +115,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 +203,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 +239,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 +331,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,23 +366,23 @@
<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"/>

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

@ -0,0 +1,45 @@
# frozen_string_literal: true
# API Controller for resources of AccountingPeriod
class API::AccountingPeriodsController < API::ApiController
before_action :authenticate_user!
before_action :set_period, only: %i[show]
def index
@accounting_periods = AccountingPeriodService.all_periods_with_users
end
def show; end
def create
authorize AccountingPeriod
@accounting_period = AccountingPeriod.new(period_params.merge(closed_at: DateTime.now, closed_by: current_user.id))
if @accounting_period.save
render :show, status: :created, location: @accounting_period
else
render json: @accounting_period.errors, status: :unprocessable_entity
end
end
def last_closing_end
authorize AccountingPeriod
last_period = AccountingPeriodService.find_last_period
if last_period.nil?
invoice = Invoice.order(:created_at).first
@last_end = invoice.created_at if invoice
else
@last_end = last_period.end_at + 1.day
end
end
private
def set_period
@tag = AccountingPeriod.find(params[:id])
end
def period_params
params.require(:accounting_period).permit(:start_at, :end_at)
end
end

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!

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

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,15 +1,13 @@
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
@ -44,4 +42,4 @@ class API::TagsController < API::ApiController
def tag_params
params.require(:tag).permit(:name)
end
end
end

View File

@ -1,13 +1,12 @@
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 +34,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,8 +1,8 @@
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]
@ -12,7 +12,8 @@ class API::TrainingsController < API::ApiController
end
if attribute_requested?(@requested_attributes, 'availabilities')
@trainings = @trainings.includes(:availabilities => [:slots => [:reservation => [:user => [:profile, :trainings]]]]).order('availabilities.start_at DESC')
@trainings = @trainings.includes(availabilities: [slots: [reservation: [user: %i[profile trainings]]]])
.order('availabilities.start_at DESC')
end
end
@ -39,12 +40,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 +56,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

@ -4,8 +4,8 @@ class API::TranslationsController < API::ApiController
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 +15,4 @@ class API::TranslationsController < API::ApiController
I18n.locale = params[:locale] || I18n.default_locale
end
end
end

View File

@ -2,7 +2,7 @@ class API::UsersController < API::ApiController
before_action :authenticate_user!
def index
if current_user.is_admin? and params[:role] == 'partner'
if current_user.is_admin? && params[:role] == 'partner'
@users = User.with_role(:partner).includes(:profile)
else
head 403
@ -12,9 +12,16 @@ class API::UsersController < API::ApiController
def create
if current_user.is_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 +36,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,3 @@
class API::VersionController < API::ApiController
before_action :authenticate_user!

View File

@ -29,6 +29,7 @@ class API::WalletController < API::ApiController
end
private
def credit_params
params.permit(:id, :amount, :avoir, :avoir_date, :avoir_description)
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
# AccountingPeriod is a period of N days (N > 0) which as been closed by an admin
# to prevent writing new accounting lines (invoices & refunds) during this period of time.
class AccountingPeriod < ActiveRecord::Base
before_destroy { false }
before_update { false }
after_create :archive_closed_data
validates :start_at, :end_at, :closed_at, :closed_by, presence: true
validates_with DateRangeValidator
validates_with PeriodOverlapValidator
def delete
false
end
def archive_file
dir = 'accounting'
# create directory if it doesn't exists (accounting)
FileUtils.mkdir_p dir
"#{dir}/#{start_at.iso8601}_#{end_at.iso8601}.json"
end
private
def to_json_archive(invoices)
ApplicationController.new.view_context.render(
partial: 'archive/accounting',
locals: { invoices: invoices },
formats: [:json],
handlers: [:jbuilder]
)
end
def archive_closed_data
data = Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: start_at, end_date: end_at)
.includes(:invoice_items)
File.write(archive_file, to_json_archive(data))
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,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,22 @@ 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?
validates_with ClosedPeriodValidator
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 +65,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 +86,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 +106,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 +146,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 +171,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 +182,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 +199,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 +208,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 +239,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

@ -0,0 +1,10 @@
# frozen_string_literal: true
# Check the access policies for API::AccountingPeriodsController
class AccountingPeriodPolicy < ApplicationPolicy
%w[index show create last_closing_end].each do |action|
define_method "#{action}?" do
user.is_admin?
end
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
# Provides methods for accessing AccountingPeriods properties
class AccountingPeriodService
def self.find_last_period
AccountingPeriod.where(end_at: AccountingPeriod.select('max(end_at)')).first
end
def self.all_periods_with_users
AccountingPeriod.joins("INNER JOIN #{User.arel_table.name} ON users.id = accounting_periods.closed_by
INNER JOIN #{Profile.arel_table.name} ON profiles.user_id = users.id")
.select("#{AccountingPeriod.arel_table.name}.*,
#{Profile.arel_table.name}.first_name,
#{Profile.arel_table.name}.last_name")
.order('start_at DESC')
end
end

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
# Provides methods for accessing Invoices resources and properties
class InvoicesService
# return a paginated list of invoices, ordered by the given criterion and optionally filtered
# @param order_key {string} any column from invoices or joined a table
# @param direction {string} 'ASC' or 'DESC', linked to order_key
# @param page {number} page number, used to paginate results
# @param size {number} number of items per page
# @param filters {Hash} allowed filters: number, customer, date.
def self.list(order_key, direction, page, size, filters = {})
invoices = Invoice.includes(:avoir, :invoiced, invoice_items: %i[subscription invoice_item], user: %i[profile trainings])
.joins(user: :profile)
.order("#{order_key} #{direction}")
.page(page)
.per(size)
if filters[:number].size.positive?
invoices = invoices.where(
'invoices.reference LIKE :search',
search: "#{filters[:number]}%"
)
end
if filters[:customer].size.positive?
# ILIKE => PostgreSQL case-insensitive LIKE
invoices = invoices.where(
'profiles.first_name ILIKE :search OR profiles.last_name ILIKE :search',
search: "%#{filters[:customer]}%"
)
end
unless filters[:date].nil?
invoices = invoices.where(
"date_trunc('day', invoices.created_at) = :search",
search: "%#{DateTime.iso8601(filters[:date]).to_time.to_date}%"
)
end
invoices
end
# Parse the order_by clause provided by JS client from '-column' form to SQL compatible form
# @param order_by {string} expected form: 'column' or '-column'
def self.parse_order(order_by)
direction = (order_by[0] == '-' ? 'DESC' : 'ASC')
key = (order_by[0] == '-' ? order_by[1, order_by.size] : order_by)
order_key = case key
when 'reference'
'invoices.reference'
when 'date'
'invoices.created_at'
when 'total'
'invoices.total'
when 'name'
'profiles.first_name'
else
'invoices.id'
end
{ direction: direction, order_key: order_key }
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
# Validates the current invoice is not generated within a closed accounting period
class ClosedPeriodValidator < ActiveModel::Validator
def validate(record)
date = if record.is_a?(Avoir)
record.avoir_date
else
DateTime.now
end
AccountingPeriod.all.each do |period|
record.errors[:date] << I18n.t('errors.messages.in_closed_period') if date >= period.start_at && date <= period.end_at
end
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
# Validates that start_at is same or before end_at in the given record
class DateRangeValidator < ActiveModel::Validator
def validate(record)
the_end = record.start_at
the_start = record.end_at
return unless the_end.present? && the_end >= the_start
record.errors[:end_at] << "The end date can't be before the start date. Pick a date after #{the_start}"
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
# Validates the current accounting period does not overlap an existing one
class PeriodOverlapValidator < ActiveModel::Validator
def validate(record)
the_end = record.end_at
the_start = record.start_at
AccountingPeriod.all.each do |period|
if the_start >= period.start_at && the_start <= period.end_at
record.errors[:start_at] << I18n.t('errors.messages.cannot_overlap')
end
if the_end >= period.start_at && the_end <= period.end_at
record.errors[:end_at] << I18n.t('errors.messages.cannot_overlap')
end
if period.start_at >= the_start && period.end_at <= the_end
record.errors[:end_at] << I18n.t('errors.messages.cannot_encompass')
end
end
end
end

View File

@ -0,0 +1,6 @@
# frozen_string_literal: true
json.array!(@accounting_periods) do |ap|
json.extract! ap, :id, :start_at, :end_at, :closed_at, :closed_by, :created_at
json.user_name "#{ap.first_name} #{ap.last_name}"
end

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.last_end_date @last_end

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.extract! @accounting_period, :id, :start_at, :end_at, :closed_at, :closed_by, :created_at

View File

@ -3,9 +3,9 @@ json.array!(@invoices) do |invoice|
json.total (invoice.total / 100.00)
json.url invoice_url(invoice, format: :json)
json.name invoice.user.profile.full_name
json.has_avoir invoice.has_avoir
json.has_avoir invoice.refunded?
json.is_avoir invoice.is_a?(Avoir)
json.is_subscription_invoice invoice.is_subscription_invoice?
json.is_subscription_invoice invoice.subscription_invoice?
json.stripe invoice.stp_invoice_id?
json.date invoice.is_a?(Avoir) ? invoice.avoir_date : invoice.created_at
json.prevent_refund invoice.prevent_refund?

View File

@ -6,10 +6,10 @@ json.array!(@invoices) do |invoice|
json.total (invoice.total / 100.00)
json.url invoice_url(invoice, format: :json)
json.name invoice.user.profile.full_name
json.has_avoir invoice.has_avoir
json.has_avoir invoice.refunded?
json.is_avoir invoice.is_a?(Avoir)
json.is_subscription_invoice invoice.is_subscription_invoice?
json.is_subscription_invoice invoice.subscription_invoice?
json.stripe invoice.stp_invoice_id?
json.date invoice.is_a?(Avoir) ? invoice.avoir_date : invoice.created_at
json.prevent_refund invoice.prevent_refund?
end
end

View File

@ -1,9 +1,9 @@
json.extract! @invoice, :id, :created_at, :reference, :invoiced_type, :user_id, :avoir_date, :description
json.total (@invoice.total / 100.00)
json.name @invoice.user.profile.full_name
json.has_avoir @invoice.has_avoir
json.has_avoir @invoice.refunded?
json.is_avoir @invoice.is_a?(Avoir)
json.is_subscription_invoice @invoice.is_subscription_invoice?
json.is_subscription_invoice @invoice.subscription_invoice?
json.stripe @invoice.stp_invoice_id?
json.date @invoice.is_a?(Avoir) ? @invoice.avoir_date : @invoice.created_at
json.items @invoice.invoice_items do |item|

View File

@ -0,0 +1,49 @@
json.array!(invoices) do |invoice|
json.extract! invoice, :id, :stp_invoice_id, :created_at, :reference
json.total number_to_currency(invoice.total / 100.0)
json.invoiced do
json.type invoice.invoiced_type
json.id invoice.invoiced_id
if invoice.invoiced_type == Subscription.name
json.extract! invoice.invoiced, :stp_subscription_id, :created_at, :expiration_date, :canceled_at
json.plan do
json.extract! invoice.invoiced.plan, :id, :base_name, :interval, :interval_count, :stp_plan_id, :is_rolling
json.group do
json.extract! invoice.invoiced.plan.group, :id, :name
end
end
elsif invoice.invoiced_type == Reservation.name
json.extract! invoice.invoiced, :created_at, :stp_invoice_id
json.reservable do
json.type invoice.invoiced.reservable_type
json.id invoice.invoiced.reservable_id
if [Training.name, Machine.name, Space.name].include?(invoice.invoiced.reservable_type) &&
!invoice.invoiced.reservable.nil?
json.extract! invoice.invoiced.reservable, :name, :created_at
elsif invoice.invoiced.reservable_type == Event.name && !invoice.invoiced.reservable.nil?
json.extract! invoice.invoiced.reservable, :title, :created_at
json.prices do
json.standard_price number_to_currency(invoice.invoiced.reservable.amount / 100.0)
json.other_prices invoice.invoiced.reservable.event_price_categories do |price|
json.amount number_to_currency(price.amount / 100.0)
json.price_category do
json.extract! price.price_category, :id, :name, :created_at
end
end
end
end
end
end
end
json.user do
json.extract! invoice.user, :id, :email, :created_at
json.profile do
json.extract! invoice.user.profile, :id, :first_name, :last_name, :birthday, :phone
json.gender invoice.user.profile.gender ? 'male' : 'female'
end
end
json.invoice_items invoice.invoice_items do |item|
json.extract! item, :id, :stp_invoice_item_id, :created_at, :description
json.amount number_to_currency(item.amount / 100.0)
end
end

View File

@ -282,115 +282,141 @@ en:
subscription_successfully_changed: "Subscription successfully changed."
invoices:
# list of all invoices & invoicing parameters
invoices: "Invoices"
invoices_list: "Invoices list"
filter_invoices: "Filter invoices"
invoice_#_: "Invoice #:"
customer_: "Customer:"
date_: "Date:"
invoice_#: "Invoice #"
customer: "Customer"
credit_note: "Credit note"
display_more_invoices: "Display more invoices..."
invoicing_settings: "Invoicing settings"
change_logo: "Change logo"
john_smith: "John Smith"
john_smith@example_com: "jean.smith@example.com"
invoice_reference_: "Invoice reference:"
code_: "Code:"
code_disabled: "Code disabled"
order_#: "Order #:"
invoice_issued_on_DATE_at_TIME: "Invoice issued on {{DATE}} at {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Object: Reservation of John Smith on {{DATE}} at {{TIME}}" # angular interpolation
order_summary: "Order summary:"
details: "Details"
amount: "Amount"
machine_booking-3D_printer: "Machine booking - 3D printer"
total_amount: "Total amount"
total_including_all_taxes: "Total incl. all taxes"
VAT_disabled: "VAT disabled"
including_VAT: "Including VAT"
including_total_excluding_taxes: "Including Total excl. taxes"
including_amount_payed_on_ordering: "Including Amount payed on ordering"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Settlement by debit card on {{DATE}} at {{TIME}}, for an amount of {{AMOUNT}}"
important_notes: "Important notes"
address_and_legal_information: "Address and legal information"
invoice_reference: "Invoice reference"
day: "Day"
"#_of_invoice": "# of invoice"
online_sales: "Online sales"
wallet: "Wallet"
refund: "Refund"
documentation: "Documentation"
2_digits_year_(eg_70): "2 digits year (eg. 70)"
4_digits_year_(eg_1970): "4 digits year (eg. 1970)"
month_number_(eg_1): "Month number (eg. 1)"
2_digits_month_number_(eg_01): "2 digits month number (eg. 01)"
3_characters_month_name_(eg_JAN): "3 characters month name (eg. JAN)"
day_in_the_month_(eg_1): "Day in the month (eg. 1)"
2_digits_day_in_the_month_(eg_01): "2 digits in the month (eg. 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "(n) digits, daily count of invoices (eg. ddd => 002 : 2nd invoice of the day)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "(n) digits, monthly count of invoices (eg. mmmm => 0012 : 12th invoice of the month)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "(n) digits, annual count of invoices (ex. yyyyyy => 000008 : 8th invoice of this year)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Beware: if the number exceed the specified length, it will be truncated by the left."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "(n) digits, count of invoices (eg. nnnn => 0327 : 327th order)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "(n) digits, daily count of orders (eg. ddd => 002 : 2nd order of the day)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "(n) digits, monthly count of orders (eg. mmmm => 0012 : 12th order of the month)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "(n) digits, annual count of orders (ex. yyyyyy => 000008 : 8th order of this year)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Add a notice regarding the online sales, only if the invoice is concerned."
this_will_never_be_added_when_a_refund_notice_is_present: "This will never be added when a refund notice is present."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(eg. X[/VL] will add "/VL" to the invoices settled with stripe)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Add a notice regarding refunds, only if the invoice is concerned."
this_will_never_be_added_when_an_online_sales_notice_is_present: "This will never be added when an online sales notice is present."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ed. R[/A] will add "/A" to the refund invoices)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Add a notice regarding the wallet, only if the invoice is concerned."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(eg. W[/PM] will add "/PM" to the invoices settled with wallet)'
code: "Code"
enable_the_code: "Enable the code"
enabled: "Enabled"
disabled: "Disabled"
order_number: "Order number"
elements: "Elements"
VAT: "VAT"
enable_VAT: "Enable VAT"
VAT_rate: "VAT rate"
refund_invoice_successfully_created: "Refund invoice successfully created."
create_a_refund_on_this_invoice: "Create a refund on this invoice"
creation_date_for_the_refund: "Creation date for the refund"
creation_date_is_required: "Creation date is required."
refund_mode: "Refund mode:"
do_you_want_to_disable_the_user_s_subscription: "Do you want to disabled the user's subscription:"
elements_to_refund: "Elements to refund"
description_(optional): "Description (optional):"
will_appear_on_the_refund_invoice: "Will appear on the refund invoice."
none: "None" # grammar note: concordance with "payment mean"
by_cash: "By cash"
by_cheque: "By cheque"
by_transfer: "By transfer"
by_wallet: "By wallet"
you_must_select_at_least_one_element_to_create_a_refund: "You must select at least one element, to create a refund."
unable_to_create_the_refund: "Unable to create the refund"
invoice_reference_successfully_saved: "Invoice reference successfully saved."
an_error_occurred_while_saving_invoice_reference: "An error occurred while saving invoice reference."
invoicing_code_succesfully_saved: "Invoicing code successfully saved."
an_error_occurred_while_saving_the_invoicing_code: "An error occurred while saving the invoicing code."
code_successfully_activated: "Code successfully activated."
code_successfully_disabled: "Code successfully disabled."
an_error_occurred_while_activating_the_invoicing_code: "An error occurred while activating the invoicing code."
order_number_successfully_saved: "Order number successfully saved."
an_error_occurred_while_saving_the_order_number: "An error occurred while saving the order number."
VAT_rate_successfully_saved: "VAT rate successfully saved."
an_error_occurred_while_saving_the_VAT_rate: "An error occurred while saving the VAT rate."
VAT_successfully_activated: "VAT successfully activated."
VAT_successfully_disabled: "VAT successfully disabled."
an_error_occurred_while_activating_the_VAT: "An error occurred while activating the VAT."
text_successfully_saved: "Text successfully saved."
an_error_occurred_while_saving_the_text: "An error occurred while saving the text."
address_and_legal_information_successfully_saved: "Address and legal information successfully saved."
an_error_occurred_while_saving_the_address_and_the_legal_information: "An error occurred while saving the address and the legal information."
logo_successfully_saved: "Logo successfully saved."
an_error_occurred_while_saving_the_logo: "An error occurred while saving the logo."
invoices:
# list of all invoices & invoicing parameters
invoices: "Invoices"
invoices_list: "Invoices list"
filter_invoices: "Filter invoices"
invoice_#_: "Invoice #:"
customer_: "Customer:"
date_: "Date:"
invoice_#: "Invoice #"
date: "Date"
price: "Price"
customer: "Customer"
download_the_invoice: "Download the invoice"
download_the_credit_note: "Download the credit note"
credit_note: "Credit note"
display_more_invoices: "Display more invoices..."
no_invoices_for_now: "No invoices for now."
invoicing_settings: "Invoicing settings"
change_logo: "Change logo"
john_smith: "John Smith"
john_smith@example_com: "jean.smith@example.com"
invoice_reference_: "Invoice reference:"
code_: "Code:"
code_disabled: "Code disabled"
order_#: "Order #:"
invoice_issued_on_DATE_at_TIME: "Invoice issued on {{DATE}} at {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Object: Reservation of John Smith on {{DATE}} at {{TIME}}" # angular interpolation
order_summary: "Order summary:"
details: "Details"
amount: "Amount"
machine_booking-3D_printer: "Machine booking - 3D printer"
total_amount: "Total amount"
total_including_all_taxes: "Total incl. all taxes"
VAT_disabled: "VAT disabled"
including_VAT: "Including VAT"
including_total_excluding_taxes: "Including Total excl. taxes"
including_amount_payed_on_ordering: "Including Amount payed on ordering"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Settlement by debit card on {{DATE}} at {{TIME}}, for an amount of {{AMOUNT}}"
important_notes: "Important notes"
address_and_legal_information: "Address and legal information"
invoice_reference: "Invoice reference"
year: "Year"
month: "Month"
day: "Day"
"#_of_invoice": "# of invoice"
online_sales: "Online sales"
wallet: "Wallet"
refund: "Refund"
model: "Model"
documentation: "Documentation"
2_digits_year_(eg_70): "2 digits year (eg. 70)"
4_digits_year_(eg_1970): "4 digits year (eg. 1970)"
month_number_(eg_1): "Month number (eg. 1)"
2_digits_month_number_(eg_01): "2 digits month number (eg. 01)"
3_characters_month_name_(eg_JAN): "3 characters month name (eg. JAN)"
day_in_the_month_(eg_1): "Day in the month (eg. 1)"
2_digits_day_in_the_month_(eg_01): "2 digits in the month (eg. 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "(n) digits, daily count of invoices (eg. ddd => 002 : 2nd invoice of the day)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "(n) digits, monthly count of invoices (eg. mmmm => 0012 : 12th invoice of the month)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "(n) digits, annual count of invoices (ex. yyyyyy => 000008 : 8th invoice of this year)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Beware: if the number exceed the specified length, it will be truncated by the left."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "(n) digits, count of invoices (eg. nnnn => 0327 : 327th order)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "(n) digits, daily count of orders (eg. ddd => 002 : 2nd order of the day)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "(n) digits, monthly count of orders (eg. mmmm => 0012 : 12th order of the month)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "(n) digits, annual count of orders (ex. yyyyyy => 000008 : 8th order of this year)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Add a notice regarding the online sales, only if the invoice is concerned."
this_will_never_be_added_when_a_refund_notice_is_present: "This will never be added when a refund notice is present."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(eg. X[/VL] will add "/VL" to the invoices settled with stripe)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Add a notice regarding refunds, only if the invoice is concerned."
this_will_never_be_added_when_an_online_sales_notice_is_present: "This will never be added when an online sales notice is present."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ed. R[/A] will add "/A" to the refund invoices)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Add a notice regarding the wallet, only if the invoice is concerned."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(eg. W[/PM] will add "/PM" to the invoices settled with wallet)'
code: "Code"
enable_the_code: "Enable the code"
enabled: "Enabled"
disabled: "Disabled"
order_number: "Order number"
elements: "Elements"
VAT: "VAT"
enable_VAT: "Enable VAT"
VAT_rate: "VAT rate"
refund_invoice_successfully_created: "Refund invoice successfully created."
create_a_refund_on_this_invoice: "Create a refund on this invoice"
creation_date_for_the_refund: "Creation date for the refund"
creation_date_is_required: "Creation date is required."
refund_mode: "Refund mode:"
do_you_want_to_disable_the_user_s_subscription: "Do you want to disabled the user's subscription:"
elements_to_refund: "Elements to refund"
description: "Description"
description_(optional): "Description (optional):"
will_appear_on_the_refund_invoice: "Will appear on the refund invoice."
none: "None" # grammar note: concordance with "payment mean"
by_cash: "By cash"
by_cheque: "By cheque"
by_transfer: "By transfer"
by_wallet: "By wallet"
you_must_select_at_least_one_element_to_create_a_refund: "You must select at least one element, to create a refund."
unable_to_create_the_refund: "Unable to create the refund"
invoice_reference_successfully_saved: "Invoice reference successfully saved."
an_error_occurred_while_saving_invoice_reference: "An error occurred while saving invoice reference."
invoicing_code_succesfully_saved: "Invoicing code successfully saved."
an_error_occurred_while_saving_the_invoicing_code: "An error occurred while saving the invoicing code."
code_successfully_activated: "Code successfully activated."
code_successfully_disabled: "Code successfully disabled."
an_error_occurred_while_activating_the_invoicing_code: "An error occurred while activating the invoicing code."
order_number_successfully_saved: "Order number successfully saved."
an_error_occurred_while_saving_the_order_number: "An error occurred while saving the order number."
VAT_rate_successfully_saved: "VAT rate successfully saved."
an_error_occurred_while_saving_the_VAT_rate: "An error occurred while saving the VAT rate."
VAT_successfully_activated: "VAT successfully activated."
VAT_successfully_disabled: "VAT successfully disabled."
an_error_occurred_while_activating_the_VAT: "An error occurred while activating the VAT."
text_successfully_saved: "Text successfully saved."
an_error_occurred_while_saving_the_text: "An error occurred while saving the text."
address_and_legal_information_successfully_saved: "Address and legal information successfully saved."
an_error_occurred_while_saving_the_address_and_the_legal_information: "An error occurred while saving the address and the legal information."
logo_successfully_saved: "Logo successfully saved."
an_error_occurred_while_saving_the_logo: "An error occurred while saving the logo."
online_payment: "Online payment"
close_accounting_period: "Close an accounting period"
close_from_date: "Close from"
start_date_is_required: "Start date is required"
close_until_date: "Close until"
end_date_is_required: "End date is required"
previous_closings: "Previous closings"
start_date: "From"
end_date: "To"
closed_at: "Closed at"
closed_by: "By"
confirmation_required: "Confirmation required"
confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible."
period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed"
failed_to_close_period: "An error occurred, unable to close the accounting period"
no_periods: "No closings for now"
members:
# management of users, labels, groups, and so on

View File

@ -282,115 +282,141 @@ es:
subscription_successfully_changed: "Suscripción cambiada correctamente."
invoices:
# list of all invoices & invoicing parameters
invoices: "Facturas"
invoices_list: "Lista de facturas"
filter_invoices: "Filtrar facturas"
invoice_#_: "Factura #:"
customer_: "Cliente:"
date_: "Fecha:"
invoice_#: "Factura #"
customer: "Cliente"
credit_note: "Nota de crédito"
display_more_invoices: "Mostrar más facturas..."
invoicing_settings: "Configuración de facturación"
change_logo: "Cambio de logotipo"
john_smith: "John Smith"
john_smith@example_com: "jean.smith@example.com"
invoice_reference_: "Referencia de factura:"
code_: "Código:"
code_disabled: "Código inhabilitado"
order_#: "Orden #:"
invoice_issued_on_DATE_at_TIME: "Factura emitida el {{DATE}} a las {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Objeto: Reserva de John Smith el {{DATE}} a las {{TIME}}" # angular interpolation
order_summary: "Resumen del pedido:"
details: "Detalles"
amount: "Cantidad"
machine_booking-3D_printer: "Reserva de la máquina- Impresora 3D"
total_amount: "Cantidad total"
total_including_all_taxes: "Total incl. todos los impuestos"
VAT_disabled: "VAT disabled"
including_VAT: "IVA desactivado"
including_total_excluding_taxes: "Incluido Total excl. impuestos"
including_amount_payed_on_ordering: "Incluido el monto pagado en el pedido"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Liquidación por tarjeta de débito el {{DATE}} a las {{TIME}}, por una cantidad de {{AMOUNT}}"
important_notes: "Notas importantes"
address_and_legal_information: "Dirección e información legal"
invoice_reference: "Referencia de factura"
day: "Día"
"#_of_invoice": "# de factura"
online_sales: "Ventas en línea"
wallet: "Cartera"
refund: "Reembolso"
documentation: "Documentación"
2_digits_year_(eg_70): "2 dígitos del año (por ejemplo, 70)"
4_digits_year_(eg_1970): "4 dígitos del año (por ejemplo, 70)"
month_number_(eg_1): "Número del mes (por ejemplo, 1)"
2_digits_month_number_(eg_01): "Número de mes de 2 dígitos (por ejemplo, 01)"
3_characters_month_name_(eg_JAN): "3 caracteres nombre del mes (por ejemplo, ENE)"
day_in_the_month_(eg_1): "Día del mes (por ejemplo, 1)"
2_digits_day_in_the_month_(eg_01): "2 dígitos en el mes (por ejemplo, 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "(n) dígitos, cuenta diaria de facturas (por ejemplo, ddd => 002: 2ª factura del día)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "(n) dígitos, recuento mensual de facturas (por ejemplo, mmmm => 0012: 12ª factura del mes)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "(n) dígitos, recuento anual de facturas (ej. aaaaa => 000008: 8ª factura de este año)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Cuidado: si el número excede la longitud especificada, será aproximado por la izquierda."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "(n) dígitos, cuenta diaria de órdenes (eg ddd => 002: segunda orden del día)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "((n) dígitos, cuenta diaria de órdenes (eg ddd => 002: segunda orden del día)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "(n) dígitos, recuento mensual de pedidos (por ejemplo, mmmm => 0012: 12º orden del mes)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "(n) dígitos, recuento anual de órdenes (ej: aaaaa => 000008: octava orden de este año)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Añadir un aviso con respecto a las ventas en línea, sólo si la factura es de interés."
this_will_never_be_added_when_a_refund_notice_is_present: "Esto nunca se agregará cuando se presente un aviso de reembolso."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(por ejemplo, X [/ VL] agregará "/ VL" a las facturas liquidadas con la raya)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Añada un aviso con respecto a los reembolsos, sólo si la factura es de interés."
this_will_never_be_added_when_an_online_sales_notice_is_present: "Esto nunca se agregará cuando un aviso de venta en línea está presente."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ed. R[/A] añadirá "/A" a las facturas de reembolso)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Añadir un aviso con respecto a la cartera, sólo si la factura es de interés."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(eg. W[/PM] añadirá "/PM" a las facturas liquidadas con cartera)'
code: "Código"
enable_the_code: "Habilitar el código"
enabled: "Habilitado"
disabled: "Desactivado"
order_number: "Número de orden"
elements: "Elementos"
VAT: "IVA"
enable_VAT: "Habilitar IVA"
VAT_rate: "Ratio IVA"
refund_invoice_successfully_created: "Factura de reembolso creada correctamente."
create_a_refund_on_this_invoice: "Crear un reembolso en esta factura"
creation_date_for_the_refund: "Fecha de creación del reembolso"
creation_date_is_required: "Se requiere la fecha de creación."
refund_mode: "Modo de reembolso:"
do_you_want_to_disable_the_user_s_subscription: "¿Quieres inhabilitar la suscripción del usuario?:"
elements_to_refund: "Elementos a reembolsar"
description_(optional): "Descripción (opcional):"
will_appear_on_the_refund_invoice: "Aparecerá en la factura de reembolso."
none: "Nada" # grammar note: concordancia con "medio de pago""
by_cash: "En efectivo"
by_cheque: "Mediante cheque"
by_transfer: "Por transferencia"
by_wallet: "Por cartera"
you_must_select_at_least_one_element_to_create_a_refund: "Debe seleccionar al menos un elemento, para crear un reembolso."
unable_to_create_the_refund: "No se puede crear el reembolso"
invoice_reference_successfully_saved: "Referencia de factura guardada correctamente."
an_error_occurred_while_saving_invoice_reference: "Se ha producido un error al guardar la referencia de la factura."
invoicing_code_succesfully_saved: "Código de facturación guardado correctamente."
an_error_occurred_while_saving_the_invoicing_code: "Se ha producido un error al guardar el código de facturación.."
code_successfully_activated: "Código activado correctamente."
code_successfully_disabled: "Código deshabilitado correctamente."
an_error_occurred_while_activating_the_invoicing_code: "Se ha producido un error al activar el código de facturación."
order_number_successfully_saved: "Número de pedido guardado correctamente."
an_error_occurred_while_saving_the_order_number: "Se ha producido un error al guardar el número de orden."
VAT_rate_successfully_saved: "VAT rate successfully saved." # translation_missing
an_error_occurred_while_saving_the_VAT_rate: "La tasa de IVA se ha guardado correctamente."
VAT_successfully_activated: "IVA activado correctamente."
VAT_successfully_disabled: "IVA desactivado correctamente."
an_error_occurred_while_activating_the_VAT: "Se ha producido un error al activar el IVA."
text_successfully_saved: "Texto guardado correctamente."
an_error_occurred_while_saving_the_text: "Se ha producido un error al guardar el texto."
address_and_legal_information_successfully_saved: "Dirección e información legal guardada correctamente."
an_error_occurred_while_saving_the_address_and_the_legal_information: "Se ha producido un error al guardar la dirección y la información legal."
logo_successfully_saved: "Logo guardado correctamente."
an_error_occurred_while_saving_the_logo: "Se ha producido un error al guardar el logotipo.."
invoices:
# list of all invoices & invoicing parameters
invoices: "Facturas"
invoices_list: "Lista de facturas"
filter_invoices: "Filtrar facturas"
invoice_#_: "Factura #:"
customer_: "Cliente:"
date_: "Fecha:"
invoice_#: "Factura #"
date: "Día"
price: "Precio"
customer: "Cliente"
download_the_invoice: "Descargar factura"
download_the_credit_note: "Descargar nota de crédito"
credit_note: "Nota de crédito"
display_more_invoices: "Mostrar más facturas..."
no_invoices_for_now: "Sin facturas por ahora."
invoicing_settings: "Configuración de facturación"
change_logo: "Cambio de logotipo"
john_smith: "John Smith"
john_smith@example_com: "jean.smith@example.com"
invoice_reference_: "Referencia de factura:"
code_: "Código:"
code_disabled: "Código inhabilitado"
order_#: "Orden #:"
invoice_issued_on_DATE_at_TIME: "Factura emitida el {{DATE}} a las {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Objeto: Reserva de John Smith el {{DATE}} a las {{TIME}}" # angular interpolation
order_summary: "Resumen del pedido:"
details: "Detalles"
amount: "Cantidad"
machine_booking-3D_printer: "Reserva de la máquina- Impresora 3D"
total_amount: "Cantidad total"
total_including_all_taxes: "Total incl. todos los impuestos"
VAT_disabled: "VAT disabled"
including_VAT: "IVA desactivado"
including_total_excluding_taxes: "Incluido Total excl. impuestos"
including_amount_payed_on_ordering: "Incluido el monto pagado en el pedido"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Liquidación por tarjeta de débito el {{DATE}} a las {{TIME}}, por una cantidad de {{AMOUNT}}"
important_notes: "Notas importantes"
address_and_legal_information: "Dirección e información legal"
invoice_reference: "Referencia de factura"
year: "Año"
month: "Mes"
day: "Día"
"#_of_invoice": "# de factura"
online_sales: "Ventas en línea"
wallet: "Cartera"
refund: "Reembolso"
model: "Modelo"
documentation: "Documentación"
2_digits_year_(eg_70): "2 dígitos del año (por ejemplo, 70)"
4_digits_year_(eg_1970): "4 dígitos del año (por ejemplo, 70)"
month_number_(eg_1): "Número del mes (por ejemplo, 1)"
2_digits_month_number_(eg_01): "Número de mes de 2 dígitos (por ejemplo, 01)"
3_characters_month_name_(eg_JAN): "3 caracteres nombre del mes (por ejemplo, ENE)"
day_in_the_month_(eg_1): "Día del mes (por ejemplo, 1)"
2_digits_day_in_the_month_(eg_01): "2 dígitos en el mes (por ejemplo, 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "(n) dígitos, cuenta diaria de facturas (por ejemplo, ddd => 002: 2ª factura del día)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "(n) dígitos, recuento mensual de facturas (por ejemplo, mmmm => 0012: 12ª factura del mes)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "(n) dígitos, recuento anual de facturas (ej. aaaaa => 000008: 8ª factura de este año)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Cuidado: si el número excede la longitud especificada, será aproximado por la izquierda."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "(n) dígitos, cuenta diaria de órdenes (eg ddd => 002: segunda orden del día)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "((n) dígitos, cuenta diaria de órdenes (eg ddd => 002: segunda orden del día)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "(n) dígitos, recuento mensual de pedidos (por ejemplo, mmmm => 0012: 12º orden del mes)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "(n) dígitos, recuento anual de órdenes (ej: aaaaa => 000008: octava orden de este año)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Añadir un aviso con respecto a las ventas en línea, sólo si la factura es de interés."
this_will_never_be_added_when_a_refund_notice_is_present: "Esto nunca se agregará cuando se presente un aviso de reembolso."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(por ejemplo, X [/ VL] agregará "/ VL" a las facturas liquidadas con la raya)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Añada un aviso con respecto a los reembolsos, sólo si la factura es de interés."
this_will_never_be_added_when_an_online_sales_notice_is_present: "Esto nunca se agregará cuando un aviso de venta en línea está presente."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ed. R[/A] añadirá "/A" a las facturas de reembolso)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Añadir un aviso con respecto a la cartera, sólo si la factura es de interés."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(eg. W[/PM] añadirá "/PM" a las facturas liquidadas con cartera)'
code: "Código"
enable_the_code: "Habilitar el código"
enabled: "Habilitado"
disabled: "Desactivado"
order_number: "Número de orden"
elements: "Elementos"
VAT: "IVA"
enable_VAT: "Habilitar IVA"
VAT_rate: "Ratio IVA"
refund_invoice_successfully_created: "Factura de reembolso creada correctamente."
create_a_refund_on_this_invoice: "Crear un reembolso en esta factura"
creation_date_for_the_refund: "Fecha de creación del reembolso"
creation_date_is_required: "Se requiere la fecha de creación."
refund_mode: "Modo de reembolso:"
do_you_want_to_disable_the_user_s_subscription: "¿Quieres inhabilitar la suscripción del usuario?:"
elements_to_refund: "Elementos a reembolsar"
description: "Descripción"
description_(optional): "Descripción (opcional):"
will_appear_on_the_refund_invoice: "Aparecerá en la factura de reembolso."
none: "Nada" # grammar note: concordancia con "medio de pago""
by_cash: "En efectivo"
by_cheque: "Mediante cheque"
by_transfer: "Por transferencia"
by_wallet: "Por cartera"
you_must_select_at_least_one_element_to_create_a_refund: "Debe seleccionar al menos un elemento, para crear un reembolso."
unable_to_create_the_refund: "No se puede crear el reembolso"
invoice_reference_successfully_saved: "Referencia de factura guardada correctamente."
an_error_occurred_while_saving_invoice_reference: "Se ha producido un error al guardar la referencia de la factura."
invoicing_code_succesfully_saved: "Código de facturación guardado correctamente."
an_error_occurred_while_saving_the_invoicing_code: "Se ha producido un error al guardar el código de facturación.."
code_successfully_activated: "Código activado correctamente."
code_successfully_disabled: "Código deshabilitado correctamente."
an_error_occurred_while_activating_the_invoicing_code: "Se ha producido un error al activar el código de facturación."
order_number_successfully_saved: "Número de pedido guardado correctamente."
an_error_occurred_while_saving_the_order_number: "Se ha producido un error al guardar el número de orden."
VAT_rate_successfully_saved: "VAT rate successfully saved." # translation_missing
an_error_occurred_while_saving_the_VAT_rate: "La tasa de IVA se ha guardado correctamente."
VAT_successfully_activated: "IVA activado correctamente."
VAT_successfully_disabled: "IVA desactivado correctamente."
an_error_occurred_while_activating_the_VAT: "Se ha producido un error al activar el IVA."
text_successfully_saved: "Texto guardado correctamente."
an_error_occurred_while_saving_the_text: "Se ha producido un error al guardar el texto."
address_and_legal_information_successfully_saved: "Dirección e información legal guardada correctamente."
an_error_occurred_while_saving_the_address_and_the_legal_information: "Se ha producido un error al guardar la dirección y la información legal."
logo_successfully_saved: "Logo guardado correctamente."
an_error_occurred_while_saving_the_logo: "Se ha producido un error al guardar el logotipo.."
online_payment: "Pago online"
close_accounting_period: "Close an accounting period" # translation_missing
close_from_date: "Close from" # translation_missing
start_date_is_required: "Start date is required" # translation_missing
close_until_date: "Close until" # translation_missing
end_date_is_required: "End date is required" # translation_missing
previous_closings: "Previous closings" # translation_missing
start_date: "From" # translation_missing
end_date: "To" # translation_missing
closed_at: "Closed at" # translation_missing
closed_by: "By" # translation_missing
confirmation_required: "Confirmation required" # translation_missing
confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible." # translation_missing
period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed" # translation_missing
failed_to_close_period: "An error occurred, unable to close the accounting period" # translation_missing
no_periods: "No closings for now" # translation_missing
members:
# management of users, labels, groups, and so on
@ -591,7 +617,7 @@ es:
terms_of_service_(TOS): "Términos de servicio (TOS)"
customize_the_graphics: "Personalizar los gráficos"
for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height: "Para una representación óptima, la imagen del logotipo debe estar en el formato PNG con un fondo transparente y una relación de aspecto 3,5 más ancha que la altura."
concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "En cuanto al favicon, debe estar en formato ICO con un tamaño de 16x16 píxeles."
concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "En cuanto al favicon, debe estar en formato ICO con un tamaño de 16x16 píxeles."
remember_to_refresh_the_page_for_the_changes_to_take_effect: "Recuerde actualizar la página para que los cambios surtan efecto."
logo_(white_background): "Logo (fondo blanco)"
change_the_logo: "Cambiar el logotipo"

View File

@ -282,115 +282,141 @@ fr:
subscription_successfully_changed: "Modification de l'abonnement réussie."
invoices:
# liste de toutes les factures & paramètres de facturation
invoices: "Factures"
invoices_list: "Liste des factures"
filter_invoices: "Filtrer les factures"
invoice_#_: "Facture n° :"
customer_: "Client :"
date_: "Date :"
invoice_#: "Facture n°"
customer: "Client"
credit_note: "Avoir"
display_more_invoices: "Afficher plus de factures ..."
invoicing_settings: "Paramètres de facturation"
change_logo: "Changer le logo"
john_smith: "Jean Dupont"
john_smith@example_com: "jean.dupont@example.com"
invoice_reference_: "Référence facture :"
code_: "Code :"
code_disabled: "Code désactivé"
order_#: "N° Commande :"
invoice_issued_on_DATE_at_TIME: "Facture éditée le {{DATE}} à {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Objet : Réservation de Jean Dupont le {{DATE}} à {{TIME}}" # angular interpolation
order_summary: "Récapitulatif de la commande :"
details: "Détails"
amount: "Montant"
machine_booking-3D_printer: "Réservation Machine - Imprimante 3D"
total_amount: "Montant total"
total_including_all_taxes: "Total TTC"
VAT_disabled: "TVA désactivée"
including_VAT: "Dont TVA"
including_total_excluding_taxes: "Dont total HT"
including_amount_payed_on_ordering: "Dont montant payé à la commande"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Règlement effectué par carte bancaire le {{DATE}} à {{TIME}}, pour un montant de {{AMOUNT}}"
important_notes: "Informations importantes"
address_and_legal_information: "Adresse et informations légales"
invoice_reference: "Référence facture"
day: "Jour"
"#_of_invoice": "N° de facture"
online_sales: "Vente en ligne"
wallet: "Porte-monnaie"
refund: "Remboursement"
documentation: "Documentation"
2_digits_year_(eg_70): "Année sur 2 chiffres (ex. 70)"
4_digits_year_(eg_1970): "Année sur 4 chiffres (ex. 1970)"
month_number_(eg_1): "Numéro du mois (ex. 1)"
2_digits_month_number_(eg_01): "Numéro du mois sur 2 chiffres (ex. 01)"
3_characters_month_name_(eg_JAN): "Nom du mois sur 3 lettres (ex. JAN)"
day_in_the_month_(eg_1): "Jour dans le mois (ex. 1)"
2_digits_day_in_the_month_(eg_01): "Jour dans le mois sur 2 chiffres (ex. 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "Nombre de facture dans le jour, sur (n) chiffres (ex. ddd => 002 : 2ème facture du jour)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "Nombre de facture dans le mois, sur (n) chiffres (ex. mmmm => 0012 : 12ème facture ce mois)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "Nombre de facture dans l'année, sur (n) chiffres (ex. yyyyyy => 000008 : 8ème facture cette année)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Attention : si le nombre dépasse la longueur demandée, il sera tronqué par la gauche."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "Nombre de commandes, sur (n) chiffres (ex. nnnn => 0327 : 327ème commande)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "Nombre de commandes dans le jour, sur (n) chiffres (ex. ddd => 002 : 2ème commande du jour)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "Nombre de commandes dans le mois, sur (n) chiffres (ex. mmmm => 0012 : 12ème commande ce mois)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "Nombre de commandes dans l'année, sur (n) chiffres (ex. yyyyyy => 000008 : 8ème commande cette année)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Ajoute une information relative à la vente en ligne, uniquement si cela concerne la facture."
this_will_never_be_added_when_a_refund_notice_is_present: "Ceci ne sera jamais cumulé avec une information de remboursement."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(ex. X[/VL] ajoutera "/VL" aux factures réglées avec stripe)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Ajoute une information relative aux remboursements, uniquement si cela concerne la facture. "
this_will_never_be_added_when_an_online_sales_notice_is_present: "Ceci ne sera jamais cumulé avec une information de vente en ligne."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ex. R[/A] ajoutera "/A" aux factures de remboursement)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Ajoute une information relative au paiement par le porte-monnaie, uniquement si cela concerne la facture."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(ex. W[/PM] ajoutera "/PM" aux factures réglées avec porte-monnaie)'
code: "Code"
enable_the_code: "Activer le code"
enabled: "Activé"
disabled: "Désactivé"
order_number: "Numéro de Commande"
elements: "Éléments"
VAT: "TVA"
enable_VAT: "Activer la TVA"
VAT_rate: "Taux de TVA"
refund_invoice_successfully_created: "La facture d'avoir a bien été créée."
create_a_refund_on_this_invoice: "Générer un avoir sur cette facture"
creation_date_for_the_refund: "Date d'émission de l'avoir"
creation_date_is_required: "La date d'émission est requise."
refund_mode: "Mode de remboursement :"
do_you_want_to_disable_the_user_s_subscription: "Souhaitez-vous désactiver l'abonnement de l'utilisateur :"
elements_to_refund: "Éléments à rembourser"
description_(optional): "Description (optionnelle) :"
will_appear_on_the_refund_invoice: "Apparaîtra sur la facture de remboursement."
none: "Aucun" # grammar note: concordance with "payment mean"
by_cash: "En espèces"
by_cheque: "Par chèque"
by_transfer: "Par virement"
by_wallet: "Par porte-monnaie"
you_must_select_at_least_one_element_to_create_a_refund: "Vous devez sélectionner au moins un élément sur lequel créer un avoir."
unable_to_create_the_refund: "Impossible de créer l'avoir"
invoice_reference_successfully_saved: "La référence facture a bien été enregistrée."
an_error_occurred_while_saving_invoice_reference: "Une erreur est survenue lors de l'enregistrement de la référence facture."
invoicing_code_succesfully_saved: "Le code de facturation a bien été enregistré."
an_error_occurred_while_saving_the_invoicing_code: "Une erreur est survenue lors de l'enregistrement du code de facturation."
code_successfully_activated: "Le code a bien été activé."
code_successfully_disabled: "Le code a bien été désactivé."
an_error_occurred_while_activating_the_invoicing_code: "Une erreur est survenue lors de l'activation du code de facturation."
order_number_successfully_saved: "Le numéro de commande a bien été enregistré."
an_error_occurred_while_saving_the_order_number: "Une erreur est survenue lors de l'enregistrement du numéro de commande."
VAT_rate_successfully_saved: "Le taux de TVA a bien été enregistré."
an_error_occurred_while_saving_the_VAT_rate: "Une erreur est survenue lors de l'enregistrement du taux de TVA."
VAT_successfully_activated: "La TVA a bien été activé."
VAT_successfully_disabled: "La TVA a bien été désactivé."
an_error_occurred_while_activating_the_VAT: "Une erreur est survenue lors de l'activation de la TVA."
text_successfully_saved: "Le texte a bien été enregistré."
an_error_occurred_while_saving_the_text: "Une erreur est survenue lors de l'enregistrement du texte."
address_and_legal_information_successfully_saved: "L'adresse et les informations légales ont bien été enregistrées."
an_error_occurred_while_saving_the_address_and_the_legal_information: "Une erreur est survenue lors de l'enregistrement de l'adresse et des informations légales."
logo_successfully_saved: "Le logo bien été enregistré."
an_error_occurred_while_saving_the_logo: "Une erreur est survenue lors de l'enregistrement du logo."
invoices:
# liste de toutes les factures & paramètres de facturation
invoices: "Factures"
invoices_list: "Liste des factures"
filter_invoices: "Filtrer les factures"
invoice_#_: "Facture n° :"
customer_: "Client :"
date_: "Date :"
invoice_#: "Facture n°"
date: "Date"
price: "Prix"
customer: "Client"
download_the_invoice: "Télécharger la facture"
download_the_credit_note: "Télécharger l'avoir"
credit_note: "Avoir"
display_more_invoices: "Afficher plus de factures ..."
no_invoices_for_now: "Aucune facture pour le moment."
invoicing_settings: "Paramètres de facturation"
change_logo: "Changer le logo"
john_smith: "Jean Dupont"
john_smith@example_com: "jean.dupont@example.com"
invoice_reference_: "Référence facture :"
code_: "Code :"
code_disabled: "Code désactivé"
order_#: "N° Commande :"
invoice_issued_on_DATE_at_TIME: "Facture éditée le {{DATE}} à {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Objet : Réservation de Jean Dupont le {{DATE}} à {{TIME}}" # angular interpolation
order_summary: "Récapitulatif de la commande :"
details: "Détails"
amount: "Montant"
machine_booking-3D_printer: "Réservation Machine - Imprimante 3D"
total_amount: "Montant total"
total_including_all_taxes: "Total TTC"
VAT_disabled: "TVA désactivée"
including_VAT: "Dont TVA"
including_total_excluding_taxes: "Dont total HT"
including_amount_payed_on_ordering: "Dont montant payé à la commande"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Règlement effectué par carte bancaire le {{DATE}} à {{TIME}}, pour un montant de {{AMOUNT}}"
important_notes: "Informations importantes"
address_and_legal_information: "Adresse et informations légales"
invoice_reference: "Référence facture"
year: "Année"
month: "Mois"
day: "Jour"
"#_of_invoice": "N° de facture"
online_sales: "Vente en ligne"
wallet: "Porte-monnaie"
refund: "Remboursement"
model: "Modèle"
documentation: "Documentation"
2_digits_year_(eg_70): "Année sur 2 chiffres (ex. 70)"
4_digits_year_(eg_1970): "Année sur 4 chiffres (ex. 1970)"
month_number_(eg_1): "Numéro du mois (ex. 1)"
2_digits_month_number_(eg_01): "Numéro du mois sur 2 chiffres (ex. 01)"
3_characters_month_name_(eg_JAN): "Nom du mois sur 3 lettres (ex. JAN)"
day_in_the_month_(eg_1): "Jour dans le mois (ex. 1)"
2_digits_day_in_the_month_(eg_01): "Jour dans le mois sur 2 chiffres (ex. 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "Nombre de facture dans le jour, sur (n) chiffres (ex. ddd => 002 : 2ème facture du jour)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "Nombre de facture dans le mois, sur (n) chiffres (ex. mmmm => 0012 : 12ème facture ce mois)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "Nombre de facture dans l'année, sur (n) chiffres (ex. yyyyyy => 000008 : 8ème facture cette année)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Attention : si le nombre dépasse la longueur demandée, il sera tronqué par la gauche."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "Nombre de commandes, sur (n) chiffres (ex. nnnn => 0327 : 327ème commande)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "Nombre de commandes dans le jour, sur (n) chiffres (ex. ddd => 002 : 2ème commande du jour)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "Nombre de commandes dans le mois, sur (n) chiffres (ex. mmmm => 0012 : 12ème commande ce mois)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "Nombre de commandes dans l'année, sur (n) chiffres (ex. yyyyyy => 000008 : 8ème commande cette année)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Ajoute une information relative à la vente en ligne, uniquement si cela concerne la facture."
this_will_never_be_added_when_a_refund_notice_is_present: "Ceci ne sera jamais cumulé avec une information de remboursement."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(ex. X[/VL] ajoutera "/VL" aux factures réglées avec stripe)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Ajoute une information relative aux remboursements, uniquement si cela concerne la facture. "
this_will_never_be_added_when_an_online_sales_notice_is_present: "Ceci ne sera jamais cumulé avec une information de vente en ligne."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ex. R[/A] ajoutera "/A" aux factures de remboursement)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Ajoute une information relative au paiement par le porte-monnaie, uniquement si cela concerne la facture."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(ex. W[/PM] ajoutera "/PM" aux factures réglées avec porte-monnaie)'
code: "Code"
enable_the_code: "Activer le code"
enabled: "Activé"
disabled: "Désactivé"
order_number: "Numéro de Commande"
elements: "Éléments"
VAT: "TVA"
enable_VAT: "Activer la TVA"
VAT_rate: "Taux de TVA"
refund_invoice_successfully_created: "La facture d'avoir a bien été créée."
create_a_refund_on_this_invoice: "Générer un avoir sur cette facture"
creation_date_for_the_refund: "Date d'émission de l'avoir"
creation_date_is_required: "La date d'émission est requise."
refund_mode: "Mode de remboursement :"
do_you_want_to_disable_the_user_s_subscription: "Souhaitez-vous désactiver l'abonnement de l'utilisateur :"
elements_to_refund: "Éléments à rembourser"
description: "Description"
description_(optional): "Description (optionnelle) :"
will_appear_on_the_refund_invoice: "Apparaîtra sur la facture de remboursement."
none: "Aucun" # grammar note: concordance with "payment mean"
by_cash: "En espèces"
by_cheque: "Par chèque"
by_transfer: "Par virement"
by_wallet: "Par porte-monnaie"
you_must_select_at_least_one_element_to_create_a_refund: "Vous devez sélectionner au moins un élément sur lequel créer un avoir."
unable_to_create_the_refund: "Impossible de créer l'avoir"
invoice_reference_successfully_saved: "La référence facture a bien été enregistrée."
an_error_occurred_while_saving_invoice_reference: "Une erreur est survenue lors de l'enregistrement de la référence facture."
invoicing_code_succesfully_saved: "Le code de facturation a bien été enregistré."
an_error_occurred_while_saving_the_invoicing_code: "Une erreur est survenue lors de l'enregistrement du code de facturation."
code_successfully_activated: "Le code a bien été activé."
code_successfully_disabled: "Le code a bien été désactivé."
an_error_occurred_while_activating_the_invoicing_code: "Une erreur est survenue lors de l'activation du code de facturation."
order_number_successfully_saved: "Le numéro de commande a bien été enregistré."
an_error_occurred_while_saving_the_order_number: "Une erreur est survenue lors de l'enregistrement du numéro de commande."
VAT_rate_successfully_saved: "Le taux de TVA a bien été enregistré."
an_error_occurred_while_saving_the_VAT_rate: "Une erreur est survenue lors de l'enregistrement du taux de TVA."
VAT_successfully_activated: "La TVA a bien été activé."
VAT_successfully_disabled: "La TVA a bien été désactivé."
an_error_occurred_while_activating_the_VAT: "Une erreur est survenue lors de l'activation de la TVA."
text_successfully_saved: "Le texte a bien été enregistré."
an_error_occurred_while_saving_the_text: "Une erreur est survenue lors de l'enregistrement du texte."
address_and_legal_information_successfully_saved: "L'adresse et les informations légales ont bien été enregistrées."
an_error_occurred_while_saving_the_address_and_the_legal_information: "Une erreur est survenue lors de l'enregistrement de l'adresse et des informations légales."
logo_successfully_saved: "Le logo bien été enregistré."
an_error_occurred_while_saving_the_logo: "Une erreur est survenue lors de l'enregistrement du logo."
online_payment: "Paiement en ligne"
close_accounting_period: "Clôturer une période comptable"
close_from_date: "Clôturer depuis"
start_date_is_required: "La date de début est requise"
close_until_date: "Clôturer jusqu'au"
end_date_is_required: "La date de fin est requise"
previous_closings: "Fermetures précédentes"
start_date: "Du"
end_date: "Au"
closed_at: "Clôturé le"
closed_by: "Par"
confirmation_required: "Confirmation requise"
confirm_close_START_END: "Êtes-vous sur de vouloir clôturer la période comptable du {{START}} au {{END}} ? Toute modification ultérieure sera impossible."
period_START_END_closed_success: "La période comptable du {{START}} au {{END}} a bien été clôturée"
failed_to_close_period: "Une erreur est survenue, impossible de clôturer la période comptable"
no_periods: "Aucune clôture pour le moment"
members:
# gestion des utilisateurs, des groupes, des étiquettes, etc.

View File

@ -113,8 +113,8 @@ pt:
events_to_come: "Eventos futuros"
events_to_come_asc: "Eventos futuros | ordem cronológica"
on_DATE: "No {{DATE}}" # angular interpolation
from_DATE: "Em {{DATE}}" # angular interpolation
from_TIME: "Ás {{TIME}}" # angular interpolation
from_DATE: "Em {{DATE}}" # angular interpolation
from_TIME: "Ás {{TIME}}" # angular interpolation
booking: "Reserva"
sold_out: "Esgotado"
cancelled: "Cancelado"
@ -282,115 +282,141 @@ pt:
subscription_successfully_changed: "Assinatura alterada com sucesso."
invoices:
# list of all invoices & invoicing parameters
invoices: "Faturas"
invoices_list: "Lista de faturas"
filter_invoices: "Filtrar faturas"
invoice_#_: "Fatura #:"
customer_: "Cliente:"
date_: "Data:"
invoice_#: "Fatura #"
customer: "Cliente"
credit_note: "Nota de crédito"
display_more_invoices: "Mostrar mais faturas..."
invoicing_settings: "Configurações do faturamento"
change_logo: "Mudar o logo"
john_smith: "John Smith"
john_smith@example_com: "jean.smith@example.com"
invoice_reference_: "Referencia de fatura:"
code_: "Código:"
code_disabled: "Código desabilitado"
order_#: "Ordem #:"
invoice_issued_on_DATE_at_TIME: "Fatura emitida em {{DATE}} ás {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Objeto: Reserva do John Smith em {{DATE}} ás {{TIME}}" # angular interpolation
order_summary: "Sumário de ordem:"
details: "Detalhes"
amount: "Montante"
machine_booking-3D_printer: "Reserva de máquina - 3D printer"
total_amount: "Montante total"
total_including_all_taxes: "Total incluindo todas as taxas"
VAT_disabled: "VAT desativado"
including_VAT: "Incluindo VAT"
including_total_excluding_taxes: "Incluindo o total de taxas excluidas"
including_amount_payed_on_ordering: "Incluindo o valor pago na compra"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Pagamento por cartão de débito em {{DATE}} ás {{TIME}}, no valor de {{AMOUNT}}"
important_notes: "Notas importantes"
address_and_legal_information: "Endereço e informações legais"
invoice_reference: "Referencia de fatura"
day: "Dia"
"#_of_invoice": "# da fatura"
online_sales: "Vendas online"
wallet: "Carteira"
refund: "Restituição"
documentation: "Documentação"
2_digits_year_(eg_70): "2 dígitos ano (ex 70)"
4_digits_year_(eg_1970): "4 dígitos ano (ex. 1970)"
month_number_(eg_1): "Número do mês (eg. 1)"
2_digits_month_number_(eg_01): "2 digits month number (eg. 01)"
3_characters_month_name_(eg_JAN): "3 characters month name (eg. JAN)"
day_in_the_month_(eg_1): "Day in the month (eg. 1)"
2_digits_day_in_the_month_(eg_01): "2 digits in the month (eg. 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "(n) digits, daily count of invoices (eg. ddd => 002 : 2nd invoice of the day)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "(n) digits, monthly count of invoices (eg. mmmm => 0012 : 12th invoice of the month)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "(n) digits, annual count of invoices (ex. yyyyyy => 000008 : 8th invoice of this year)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Beware: if the number exceed the specified length, it will be truncated by the left."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "(n) digits, count of invoices (eg. nnnn => 0327 : 327th order)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "(n) digits, daily count of orders (eg. ddd => 002 : 2nd order of the day)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "(n) digits, monthly count of orders (eg. mmmm => 0012 : 12th order of the month)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "(n) digits, annual count of orders (ex. yyyyyy => 000008 : 8th order of this year)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Add a notice regarding the online sales, only if the invoice is concerned."
this_will_never_be_added_when_a_refund_notice_is_present: "This will never be added when a refund notice is present."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(eg. X[/VL] will add "/VL" to the invoices settled with stripe)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Add a notice regarding refunds, only if the invoice is concerned."
this_will_never_be_added_when_an_online_sales_notice_is_present: "This will never be added when an online sales notice is present."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ed. R[/A] will add "/A" to the refund invoices)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Add a notice regarding the wallet, only if the invoice is concerned."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(eg. W[/PM] will add "/PM" to the invoices settled with wallet)'
code: "Código"
enable_the_code: "Ativar código"
enabled: "Ativar"
disabled: "Desativar"
order_number: "Número de ordem"
elements: "Elementos"
VAT: "VAT"
enable_VAT: "Ativar VAT"
VAT_rate: "VAT taxa"
refund_invoice_successfully_created: "Restituição de fatura criada com sucesso."
create_a_refund_on_this_invoice: "Criar restituição de fatura"
creation_date_for_the_refund: "Criação de data de restituição"
creation_date_is_required: "Data de criação é obrigatório."
refund_mode: "Modo de restituição:"
do_you_want_to_disable_the_user_s_subscription: "Você deseja desativar a inscrição de usuários:"
elements_to_refund: "Elementos para restituição"
description_(optional): "Descrição (optional):"
will_appear_on_the_refund_invoice: "Aparecerá na fatura de reembolso."
none: "Vazio" # grammar note: concordance with "payment mean"
by_cash: "Em dinheiro"
by_cheque: "Em cheque"
by_transfer: "Por transferência"
by_wallet: "Pela carteira"
you_must_select_at_least_one_element_to_create_a_refund: "Você deve selecionar pelo menos um elemento, para criar um reembolso."
unable_to_create_the_refund: "Não foi possível criar reembolso"
invoice_reference_successfully_saved: "Referência de fatura salva com sucesso."
an_error_occurred_while_saving_invoice_reference: "Um erro ocorreu enquanto era salvo a fatura de referência."
invoicing_code_succesfully_saved: "Invoicing code successfully saved."
an_error_occurred_while_saving_the_invoicing_code: "An error occurred while saving the invoicing code."
code_successfully_activated: "Código ativado com sucesso."
code_successfully_disabled: "Código desativado com êxito."
an_error_occurred_while_activating_the_invoicing_code: "Ocorreu um erro ao ativar o código de faturamento."
order_number_successfully_saved: "Número de ordem salvo com sucesso."
an_error_occurred_while_saving_the_order_number: "Ocorreu um erro ao salvar o número da ordem."
VAT_rate_successfully_saved: "Taxa VAT salva com sucesso."
an_error_occurred_while_saving_the_VAT_rate: "Um erro ocorreu ao salvar a taxa VAT."
VAT_successfully_activated: "VAT ativado com sucesso."
VAT_successfully_disabled: "VAT desativada com sucesso."
an_error_occurred_while_activating_the_VAT: "Um erro ocorreu ao ativar VAT."
text_successfully_saved: "Texto salvo com sucesso."
an_error_occurred_while_saving_the_text: "Um erro ocorreu ao salvar texto."
address_and_legal_information_successfully_saved: "Endereço e informações legais salvos com sucesso."
an_error_occurred_while_saving_the_address_and_the_legal_information: "Um erro ocorreu ao salvar o endereço e informações legais."
logo_successfully_saved: "Logo salvo com sucesso."
an_error_occurred_while_saving_the_logo: "Um erro ocorreu ao salvar o logo."
invoices:
# list of all invoices & invoicing parameters
invoices: "Faturas"
invoices_list: "Lista de faturas"
filter_invoices: "Filtrar faturas"
invoice_#_: "Fatura #:"
customer_: "Cliente:"
date_: "Data:"
invoice_#: "Fatura #"
date: "Data"
price: "Preço"
customer: "Cliente"
download_the_invoice: "Baixar a fatura"
download_the_credit_note: "Baixar a nota de crédito"
credit_note: "Nota de crédito"
display_more_invoices: "Mostrar mais faturas..."
no_invoices_for_now: "Nenhuma fatura."
invoicing_settings: "Configurações do faturamento"
change_logo: "Mudar o logo"
john_smith: "John Smith"
john_smith@example_com: "jean.smith@example.com"
invoice_reference_: "Referencia de fatura:"
code_: "Código:"
code_disabled: "Código desabilitado"
order_#: "Ordem #:"
invoice_issued_on_DATE_at_TIME: "Fatura emitida em {{DATE}} ás {{TIME}}" # angular interpolation
object_reservation_of_john_smith_on_DATE_at_TIME: "Objeto: Reserva do John Smith em {{DATE}} ás {{TIME}}" # angular interpolation
order_summary: "Sumário de ordem:"
details: "Detalhes"
amount: "Montante"
machine_booking-3D_printer: "Reserva de máquina - 3D printer"
total_amount: "Montante total"
total_including_all_taxes: "Total incluindo todas as taxas"
VAT_disabled: "VAT desativado"
including_VAT: "Incluindo VAT"
including_total_excluding_taxes: "Incluindo o total de taxas excluidas"
including_amount_payed_on_ordering: "Incluindo o valor pago na compra"
settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Pagamento por cartão de débito em {{DATE}} ás {{TIME}}, no valor de {{AMOUNT}}"
important_notes: "Notas importantes"
address_and_legal_information: "Endereço e informações legais"
invoice_reference: "Referencia de fatura"
year: "Ano"
month: "Mês"
day: "Dia"
"#_of_invoice": "# da fatura"
online_sales: "Vendas online"
wallet: "Carteira"
refund: "Restituição"
model: "Modelo"
documentation: "Documentação"
2_digits_year_(eg_70): "2 dígitos ano (ex 70)"
4_digits_year_(eg_1970): "4 dígitos ano (ex. 1970)"
month_number_(eg_1): "Número do mês (eg. 1)"
2_digits_month_number_(eg_01): "2 digits month number (eg. 01)"
3_characters_month_name_(eg_JAN): "3 characters month name (eg. JAN)"
day_in_the_month_(eg_1): "Day in the month (eg. 1)"
2_digits_day_in_the_month_(eg_01): "2 digits in the month (eg. 01)"
(n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "(n) digits, daily count of invoices (eg. ddd => 002 : 2nd invoice of the day)"
(n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "(n) digits, monthly count of invoices (eg. mmmm => 0012 : 12th invoice of the month)"
(n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "(n) digits, annual count of invoices (ex. yyyyyy => 000008 : 8th invoice of this year)"
beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Beware: if the number exceed the specified length, it will be truncated by the left."
(n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "(n) digits, count of invoices (eg. nnnn => 0327 : 327th order)"
(n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "(n) digits, daily count of orders (eg. ddd => 002 : 2nd order of the day)"
(n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "(n) digits, monthly count of orders (eg. mmmm => 0012 : 12th order of the month)"
(n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "(n) digits, annual count of orders (ex. yyyyyy => 000008 : 8th order of this year)"
add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Add a notice regarding the online sales, only if the invoice is concerned."
this_will_never_be_added_when_a_refund_notice_is_present: "This will never be added when a refund notice is present."
(eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(eg. X[/VL] will add "/VL" to the invoices settled with stripe)'
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Add a notice regarding refunds, only if the invoice is concerned."
this_will_never_be_added_when_an_online_sales_notice_is_present: "This will never be added when an online sales notice is present."
(eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ed. R[/A] will add "/A" to the refund invoices)'
add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned: "Add a notice regarding the wallet, only if the invoice is concerned."
(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet): '(eg. W[/PM] will add "/PM" to the invoices settled with wallet)'
code: "Código"
enable_the_code: "Ativar código"
enabled: "Ativar"
disabled: "Desativar"
order_number: "Número de ordem"
elements: "Elementos"
VAT: "VAT"
enable_VAT: "Ativar VAT"
VAT_rate: "VAT taxa"
refund_invoice_successfully_created: "Restituição de fatura criada com sucesso."
create_a_refund_on_this_invoice: "Criar restituição de fatura"
creation_date_for_the_refund: "Criação de data de restituição"
creation_date_is_required: "Data de criação é obrigatório."
refund_mode: "Modo de restituição:"
do_you_want_to_disable_the_user_s_subscription: "Você deseja desativar a inscrição de usuários:"
elements_to_refund: "Elementos para restituição"
description: "Descrição"
description_(optional): "Descrição (optional):"
will_appear_on_the_refund_invoice: "Aparecerá na fatura de reembolso."
none: "Vazio" # grammar note: concordance with "payment mean"
by_cash: "Em dinheiro"
by_cheque: "Em cheque"
by_transfer: "Por transferência"
by_wallet: "Pela carteira"
you_must_select_at_least_one_element_to_create_a_refund: "Você deve selecionar pelo menos um elemento, para criar um reembolso."
unable_to_create_the_refund: "Não foi possível criar reembolso"
invoice_reference_successfully_saved: "Referência de fatura salva com sucesso."
an_error_occurred_while_saving_invoice_reference: "Um erro ocorreu enquanto era salvo a fatura de referência."
invoicing_code_succesfully_saved: "Invoicing code successfully saved."
an_error_occurred_while_saving_the_invoicing_code: "An error occurred while saving the invoicing code."
code_successfully_activated: "Código ativado com sucesso."
code_successfully_disabled: "Código desativado com êxito."
an_error_occurred_while_activating_the_invoicing_code: "Ocorreu um erro ao ativar o código de faturamento."
order_number_successfully_saved: "Número de ordem salvo com sucesso."
an_error_occurred_while_saving_the_order_number: "Ocorreu um erro ao salvar o número da ordem."
VAT_rate_successfully_saved: "Taxa VAT salva com sucesso."
an_error_occurred_while_saving_the_VAT_rate: "Um erro ocorreu ao salvar a taxa VAT."
VAT_successfully_activated: "VAT ativado com sucesso."
VAT_successfully_disabled: "VAT desativada com sucesso."
an_error_occurred_while_activating_the_VAT: "Um erro ocorreu ao ativar VAT."
text_successfully_saved: "Texto salvo com sucesso."
an_error_occurred_while_saving_the_text: "Um erro ocorreu ao salvar texto."
address_and_legal_information_successfully_saved: "Endereço e informações legais salvos com sucesso."
an_error_occurred_while_saving_the_address_and_the_legal_information: "Um erro ocorreu ao salvar o endereço e informações legais."
logo_successfully_saved: "Logo salvo com sucesso."
an_error_occurred_while_saving_the_logo: "Um erro ocorreu ao salvar o logo."
online_payment: "Pagamento Online"
close_accounting_period: "Close an accounting period" # translation_missing
close_from_date: "Close from" # translation_missing
start_date_is_required: "Start date is required" # translation_missing
close_until_date: "Close until" # translation_missing
end_date_is_required: "End date is required" # translation_missing
previous_closings: "Previous closings" # translation_missing
start_date: "From" # translation_missing
end_date: "To" # translation_missing
closed_at: "Closed at" # translation_missing
closed_by: "By" # translation_missing
confirmation_required: "Confirmation required" # translation_missing
confirm_close_START_END: "Do you really want to close the accounting period between {{START}} and {{END}}? Any subsequent changes will be impossible." # translation_missing
period_START_END_closed_success: "The accounting period from {{START}} to {{END}} has been successfully closed" # translation_missing
failed_to_close_period: "An error occurred, unable to close the accounting period" # translation_missing
no_periods: "No closings for now" # translation_missing
members:
# management of users, labels, groups, and so on
@ -444,8 +470,8 @@ pt:
an_error_occurred_when_saving_the_new_group: "Um erro ocorreu ao salvar novo grupo."
group_successfully_deleted: "Grupo excluido com sucesso."
unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it: "Não é possível excluir o grupo porque alguns usuários e / ou grupos ainda estão vinculados a ele."
group_successfully_enabled_disabled: "Grupo {STATUS, select, true{desativado} other{ativado}} com sucesso."
unable_to_enable_disable_group: "Não foi possível {STATUS, select, true{desativar} other{ativar}} grupo."
group_successfully_enabled_disabled: "Grupo {STATUS, select, true{desativado} other{ativado}} com sucesso."
unable_to_enable_disable_group: "Não foi possível {STATUS, select, true{desativar} other{ativar}} grupo."
unable_to_disable_group_with_users: "Não é possível desabilitar grupo porque {USERS, plural, =1{existe} other{existem}} {USERS} {USERS, plural, =1{usuário} other{usuários}} {USERS, plural, =1{ativo} other{ativos}}."
status_enabled: "Ativos"
status_disabled: "Desabilitados"
@ -563,7 +589,7 @@ pt:
settings:
# global application parameters and customization
settings:
tittle: "Title"
tittle: "Title"
customize_the_application: "Customizar a aplicação"
general: "Geral"
fablab_title: "Título do FabLab"
@ -687,7 +713,7 @@ pt:
client_successfully_updated: "Cliente alterado com sucesso."
client_successfully_deleted: "Cliente excluído com sucesso."
access_successfully_revoked: "Acesso revogado com sucesso."
space_new:
# create a new space
space_new:
@ -700,4 +726,4 @@ pt:
# modify an exiting space
space_edit:
edit_the_space_NAME: "Editar o espaço: {{NAME}}" # angular interpolation
validate_the_changes: "Validar mudanças"
validate_the_changes: "Validar mudanças"

View File

@ -36,6 +36,9 @@ en:
cannot_be_blank_at_same_time: "cannot be blank when %{field} is blank too"
cannot_be_in_the_past: "cannot be in the past"
cannot_be_before_previous_value: "cannot be before the previous value"
cannot_overlap: "can't overlap an existing accounting period"
cannot_encompass: "can't encompass an existing accounting period"
in_closed_period: "can't be within a closed accounting period"
activemodel:
errors:

View File

@ -36,6 +36,9 @@ es:
cannot_be_blank_at_same_time: "no puede estar vacío cuando %{field} también está vacío"
cannot_be_in_the_past: "no puede estar en el pasado"
cannot_be_before_previous_value: "No puede estar antes del valor anterior."
cannot_overlap: "can't overlap an existing accounting period" # missing translation
cannot_encompass: "can't encompass an existing accounting period" # missing translation
in_closed_period: "can't be within a closed accounting period" # missing translation
activemodel:
errors:

View File

@ -36,6 +36,9 @@ fr:
cannot_be_blank_at_same_time: "ou %{field} doit être rempli(e)"
cannot_be_in_the_past: "ne peut pas être dans le passé"
cannot_be_before_previous_value: "ne peut pas être antérieur(e) à la valeur précédente"
cannot_overlap: "ne peut pas chevaucher une période comptable existante"
cannot_encompass: "ne peut pas englober une période comptable existante"
in_closed_period: "ne peut pas être dans une période comptable fermée"
activemodel:
errors:
@ -360,4 +363,4 @@ fr:
group:
# nom du groupe utilisateur pour les administrateurs
admins: 'Administrateurs'
admins: 'Administrateurs'

View File

@ -36,6 +36,9 @@ pt:
cannot_be_blank_at_same_time: "Não pode ficar em branco quando %{field} estiver em branco também"
cannot_be_in_the_past: "não pode ser no passado"
cannot_be_before_previous_value: "não pode ser antes do valor anterior"
cannot_overlap: "can't overlap an existing accounting period" # missing translation
cannot_encompass: "can't encompass an existing accounting period" # missing translation
in_closed_period: "can't be within a closed accounting period" # missing translation
activemodel:
errors:

View File

@ -1,3 +1,4 @@
dsb:
date:
abbr_day_names:
- Nj
@ -8,7 +9,7 @@
-
- So
abbr_month_names:
-
-
- jan
- feb
- měr
@ -34,7 +35,7 @@
long: ! '%d. %B %Y'
short: ! '%d %b'
month_names:
-
-
- Januar
- Februar
- Měrc

View File

@ -127,6 +127,9 @@ Rails.application.routes.draw do
end
resources :price_categories
resources :spaces
resources :accounting_periods do
get 'last_closing_end', on: :collection
end
# i18n
# regex allows using dots in URL for 'state'

View File

@ -0,0 +1,15 @@
class CreateAccountingPeriods < ActiveRecord::Migration
def change
create_table :accounting_periods do |t|
t.date :start_at
t.date :end_at
t.datetime :closed_at
t.integer :closed_by
t.timestamps null: false
end
add_foreign_key :accounting_periods, :users, column: :closed_by, primary_key: :id
end
end

View File

@ -0,0 +1,13 @@
class ProtectAccountingPeriods < ActiveRecord::Migration
# PostgreSQL only
def up
execute("CREATE RULE accounting_periods_del_protect AS ON DELETE TO #{AccountingPeriod.arel_table.name} DO INSTEAD NOTHING;")
execute("CREATE RULE accounting_periods_upd_protect AS ON UPDATE TO #{AccountingPeriod.arel_table.name} DO INSTEAD NOTHING;")
end
def down
execute("DROP RULE IF EXISTS accounting_periods_del_protect ON #{AccountingPeriod.arel_table.name};")
execute("DROP RULE IF EXISTS accounting_periods_upd_protect ON #{AccountingPeriod.arel_table.name};")
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20181217110454) do
ActiveRecord::Schema.define(version: 20190107111749) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -31,6 +31,15 @@ ActiveRecord::Schema.define(version: 20181217110454) do
add_index "abuses", ["signaled_type", "signaled_id"], name: "index_abuses_on_signaled_type_and_signaled_id", using: :btree
create_table "accounting_periods", force: :cascade do |t|
t.date "start_at"
t.date "end_at"
t.datetime "closed_at"
t.integer "closed_by"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "addresses", force: :cascade do |t|
t.string "address", limit: 255
t.string "street_number", limit: 255
@ -856,6 +865,7 @@ ActiveRecord::Schema.define(version: 20181217110454) do
add_index "wallets", ["user_id"], name: "index_wallets_on_user_id", using: :btree
add_foreign_key "accounting_periods", "users", column: "closed_by"
add_foreign_key "availability_tags", "availabilities"
add_foreign_key "availability_tags", "tags"
add_foreign_key "event_price_categories", "events"

View File

@ -14,6 +14,7 @@ services:
- ${PWD}/exports:/usr/src/app/exports
- ${PWD}/log:/var/log/supervisor
- ${PWD}/plugins:/usr/src/app/plugins
- ${PWD}/accounting:/usr/src/app/accounting
depends_on:
- postgres
- redis

5
test/fixtures/accounting_periods.yml vendored Normal file
View File

@ -0,0 +1,5 @@
period2015:
start_at: 2015-01-01
end_at: 2015-12-31
closed_at: 2016-01-04 18:12:07
closed_by: 1

View File

@ -33,7 +33,6 @@ invoice_item_3:
subscription_id: 3
invoice_item_id:
invoice_item_4:
id: 4
invoice_id: 4
@ -45,3 +44,14 @@ invoice_item_4:
subscription_id:
invoice_item_id:
invoice_item_5:
id: 5
invoice_id: 5
stp_invoice_item_id:
amount: 1500
created_at: 2015-06-10 11:20:01.341130000 Z
updated_at: 2015-06-10 11:20:01.341130000 Z
description: Imprimante 3D June 15, 2015 12:00 - 01:00 PM
subscription_id:
invoice_item_id:

View File

@ -66,4 +66,21 @@ invoice_4:
invoice_id:
type:
subscription_to_expire:
description:
description:
invoice_5:
id: 5
invoiced_id: 2
invoiced_type: Reservation
stp_invoice_id:
total: 1500
created_at: 2015-06-10 11:20:01.341130000 Z
updated_at: 2015-06-10 11:20:01.341130000 Z
user_id: 3
reference: '1506031'
avoir_mode:
avoir_date:
invoice_id:
type:
subscription_to_expire:
description:

View File

@ -8,4 +8,15 @@ reservation_1:
reservable_id: 2
reservable_type: Training
stp_invoice_id:
nb_reserve_places:
nb_reserve_places:
reservation_2:
id: 2
user_id: 3
message:
created_at: 2015-06-10 11:20:01.341130000 Z
updated_at: 2015-06-10 11:20:01.341130000 Z
reservable_id: 4
reservable_type: Machine
stp_invoice_id:
nb_reserve_places:

View File

@ -9,4 +9,16 @@ slot_1:
ex_start_at:
canceled_at:
ex_end_at:
offered:
offered:
slot_2:
id: 2
start_at: 2015-06-15 12:00:28.000000000 Z
end_at: 2015-06-15 13:00:28.000000000 Z
created_at: 2015-06-10 11:20:01.341130000 Z
updated_at: 2015-06-10 11:20:01.341130000 Z
availability_id: 13
ex_start_at:
canceled_at:
ex_end_at:
offered:

View File

@ -2,4 +2,8 @@
one:
slot_id: 1
reservation_id: 1
reservation_id: 1
two:
slot_id: 2
reservation_id: 2

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
class AccountingPeriodTest < ActionDispatch::IntegrationTest
def setup
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
end
test 'admin closes an accounting period' do
start_at = '2012-01-01T00:00:00.000Z'
end_at = '2012-12-31T00:00:00.000Z'
post '/api/accounting_periods',
{
accounting_period: {
start_at: start_at,
end_at: end_at
}
}.to_json, default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the correct period was closed successfully
period = json_response(response.body)
accounting_period = AccountingPeriod.find(period[:id])
assert_dates_equal start_at.to_date, period[:start_at]
assert_dates_equal end_at.to_date, period[:end_at]
# Check archive file was created
assert FileTest.exists? accounting_period.archive_file
# Check archive matches
archive = File.read(accounting_period.archive_file)
archive_json = JSON.parse(archive)
invoices = Invoice.where(
'created_at >= :start_date AND created_at <= :end_date',
start_date: start_at.to_datetime, end_date: end_at.to_datetime
)
assert_equal invoices.count, archive_json.count
end
end

View File

@ -0,0 +1,86 @@
# frozen_string_literal: true
class InvoicesTest < ActionDispatch::IntegrationTest
# Called before every test method runs. Can be used
# to set up fixture information.
def setup
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
end
test 'admin list invoices' do
post '/api/invoices/list', { query: {
number: '',
customer: '',
date: nil,
order_by: '-reference',
page: 1,
size: 20 # test db may have < 20 invoices
} }.to_json, default_headers
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check that we have all invoices
invoices = json_response(response.body)
assert_equal Invoice.count, invoices.size, 'some invoices are missing'
# Check that invoices are ordered by reference
assert_equal '1604002', invoices.first[:reference]
assert_equal '1203001', invoices.last[:reference]
end
test 'admin generates a refund' do
date = DateTime.now.iso8601
post '/api/invoices', { avoir: {
avoir_date: date,
avoir_mode: 'cash',
description: 'Lorem ipsum',
invoice_id: 4,
invoice_items_ids: [4],
subscription_to_expire: false
} }.to_json, default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check that the refund match
refund = json_response(response.body)
avoir = Avoir.find(refund[:id])
assert_dates_equal date, refund[:avoir_date]
assert_dates_equal date, refund[:date]
assert_equal 'cash', refund[:avoir_mode]
assert_equal false, refund[:has_avoir]
assert_equal 4, refund[:invoice_id]
assert_equal 4, refund[:items][0][:invoice_item_id]
assert_match %r{^[0-9]+/A$}, refund[:reference]
assert_equal 'Lorem ipsum', avoir.description
end
test 'admin fails generates a refund in closed period' do
date = '2015-10-01T13:09:55+01:00'.to_datetime
post '/api/invoices', { avoir: {
avoir_date: date,
avoir_mode: 'cash',
description: 'Unable to refund',
invoice_id: 5,
invoice_items_ids: [5],
subscription_to_expire: false
} }.to_json, default_headers
# Check response format & status
assert_equal 422, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the error was handled
assert_match(/#{I18n.t('errors.messages.in_closed_period')}/, response.body)
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
class MemebersTest < ActionDispatch::IntegrationTest
class MembersTest < ActionDispatch::IntegrationTest
# Called before every test method runs. Can be used
# to set up fixture information.