diff --git a/accountant/frontend/static/css/main.css b/accountant/frontend/static/css/main.css deleted file mode 100644 index b71eb74..0000000 --- a/accountant/frontend/static/css/main.css +++ /dev/null @@ -1,7 +0,0 @@ -.italic { - font-style: italic -} - -.stroke { - text-decoration: line-through -} diff --git a/accountant/frontend/static/index.html b/accountant/frontend/static/index.html deleted file mode 100644 index 7fb6e47..0000000 --- a/accountant/frontend/static/index.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - Accountant - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/accountant/frontend/static/js/accounts.js b/accountant/frontend/static/js/accounts.js deleted file mode 100644 index 515211d..0000000 --- a/accountant/frontend/static/js/accounts.js +++ /dev/null @@ -1,159 +0,0 @@ -/* - This file is part of Accountant. - - Accountant is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Accountant is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Accountant. If not, see . - */ -// vim: set tw=80 ts=2 sw=2 sts=2: -'use strict'; - -angular.module('accountant.accounts', [ - 'ngResource', - 'ui-notification', - 'xeditable', - 'ngBootbox' -]) - -.config(['$resourceProvider', function($resourceProvider) { - // Keep trailing slashes to avoid redirect by flask.. - $resourceProvider.defaults.stripTrailingSlashes = false; -}]) - -.factory('Account', ['$resource', function($resource) { - var Account = $resource( - '/api/account/:id', { - id: '@id' - } - ); - - Account.prototype.getSolds = function() { - var Solds = $resource('/api/account/:id/solds', {id: this.id}); - - this.solds = Solds.get(); - }; - - Account.prototype.getBalance = function(begin, end) { - var Balance = $resource( - '/api/account/:id/balance', { - id: this.id, - begin: begin.format('YYYY-MM-DD'), - end: end.format('YYYY-MM-DD') - }); - - this.balance = Balance.get(); - }; - - return Account; -}]) - -.controller( - 'AccountController', [ - '$scope', '$ngBootbox', 'Account', 'Notification', - function($scope, $ngBootbox, Account, Notification) { - - /* - * Return the class for an account current value compared to authorized - * overdraft. - */ - $scope.rowClass = function(account) { - if(!account || !account.authorized_overdraft || !account.current) { - return; - } - - if(account.current < account.authorized_overdraft) { - return 'danger'; - } else if(account.current < 0) { - return 'warning'; - } - }; - - /* - * Return the class for a value compared to account authorized overdraft. - */ - $scope.valueClass = function(account, value) { - if(!account || !value) { - return; - } - - if(value < account.authorized_overdraft) { - return 'text-danger'; - } else if(value < 0) { - return 'text-warning'; - } - }; - - /* - * Add an empty account. - */ - $scope.add = function() { - var account = new Account({ - authorized_overdraft: 0 - }); - - // Insert account at the begining of the array. - $scope.accounts.splice(0, 0, account); - }; - - /* - * Cancel account edition. Remove it from array if a new one. - */ - $scope.cancelEdit = function(rowform, account, $index) { - if(!account.id) { - // Account not saved, just remove it from array. - $scope.accounts.splice($index, 1); - } else { - rowform.$cancel(); - } - }; - - /* - * Save account. - */ - $scope.save = function(account) { - //var account = $scope.accounts[$index]; - - //account = angular.merge(account, $data); - - return account.$save().then(function(data) { - Notification.success('Account #' + data.id + ' saved.'); - - // TODO Alexis Lahouze 2016-03-08 Update solds - - return data; - }); - }; - - /* - * Delete an account. - */ - $scope.delete = function(account, $index) { - var id = account.id; - - $ngBootbox.confirm( - 'Voulez-vous supprimer le compte \'' + account.name + '\' ?', - function(result) { - if(result) { - account.$delete().then(function() { - Notification.success('Account #' + id + ' deleted.'); - - // Remove account from array. - $scope.accounts.splice($index, 1); - }); - } - } - ); - }; - - // Load accounts. - $scope.accounts = Account.query(); -}]); diff --git a/accountant/frontend/static/js/app.js b/accountant/frontend/static/js/app.js deleted file mode 100644 index 628d9f2..0000000 --- a/accountant/frontend/static/js/app.js +++ /dev/null @@ -1,154 +0,0 @@ -/* - This file is part of Accountant. - - Accountant is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Accountant is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Accountant. If not, see . - */ -// vim: set tw=80 ts=2 sw=2 sts=2: -'use strict'; - -angular.module('accountant', [ - 'accountant.accounts', - 'accountant.operations', - 'accountant.scheduler', - 'ngRoute', - 'ngBootbox', - 'http-auth-interceptor', - 'Storage.Service' -]) - -.factory('sessionInjector', ['$storage', function($storage) { - var sessionInjector = { - request : function(config) { - var token = $storage.get('token'); - - if(token) { - var token_type = $storage.get('token_type'); - var authorization = token_type + ' ' + token; - config.headers.Authorization = authorization; - } - - return config; - } - }; - - return sessionInjector; -}]) - -.config(['$httpProvider', function($httpProvider) { - // Define interceptors. - $httpProvider.interceptors.push('sessionInjector'); -}]) - -.config(['$routeProvider', function($routeProvider) { - // Defining template and controller in function of route. - $routeProvider - .when('/account/:accountId/operations', { - templateUrl: 'views/operations.html', - controller: 'OperationController', - controllerAs: 'operationsCtrl' - }) - .when('/account/:accountId/scheduler', { - templateUrl: 'views/scheduler.html', - controller: 'SchedulerController', - controllerAs: 'schedulerCtrl' - }) - .when('/accounts', { - templateUrl: 'views/accounts.html', - controller: 'AccountController', - controllerAs: 'accountsCtrl' - }) - .otherwise({ - redirectTo: '/accounts' - }); - -}]) - -.config(['$storageProvider', function($storageProvider) { - // Configure storage - // Set global prefix for stored keys - $storageProvider.setPrefix('accountant'); - - // Change the default storage engine - // Defaults to 'local' - $storageProvider.setDefaultStorageEngine('session'); - - // Change the enabled storage engines - // Defaults to ['memory', 'cookie', 'session', 'local'] - $storageProvider.setEnabledStorageEngines(['local', 'session']); -}]) - -.run(function(editableOptions) { - editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs2', 'default' -}) - -.controller('MainController', [ - '$scope', '$rootScope', '$http', 'authService', '$storage', '$ngBootbox', - function($scope, $rootScope, $http, authService, $storage, $ngBootbox) { - $scope.dialogShown = false; - - $scope.showLoginForm = function() { - // First, if there are registered credentials, use them - if($scope.dialogShown) { - return; - } - - $scope.dialogShown = true; - - $storage.clear(); - - $ngBootbox.customDialog({ - title: 'Authentification requise', - templateUrl: 'views/login.tmpl.html', - buttons: { - login: { - label: 'Login', - className: 'btn-primary', - callback: function() { - $scope.dialogShown = false; - - var email = $('#email').val(); - var password = $('#password').val(); - $http.post( - '/api/user/login', - { - 'email': email, - 'password': password - } - ).success(function(result) { - // TODO Alexis Lahouze 2015-08-28 Handle callback. - // Call to /api/login to retrieve the token - $storage.set('token_type', result.token_type); - $storage.set('token', result.token); - $storage.set('expiration_date', result.expiration_date); - - authService.loginConfirmed(); - }); - } - }, - cancel: { - label: 'Annuler', - className: 'btn-default', - callback: function() { - authService.loginCancelled(null, 'Login cancelled by user action.'); - $scope.dialogShown = false; - } - } - } - }); - }; - - $rootScope.$on('event:auth-loginRequired', $scope.showLoginForm); -}]) - -; diff --git a/accountant/frontend/static/js/operations.js b/accountant/frontend/static/js/operations.js deleted file mode 100644 index 72b2f4b..0000000 --- a/accountant/frontend/static/js/operations.js +++ /dev/null @@ -1,466 +0,0 @@ -/* - This file is part of Accountant. - - Accountant is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Accountant is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Accountant. If not, see . - */ -// vim: set tw=80 ts=2 sw=2 sts=2: -'use strict'; - -angular.module('accountant.operations', [ - 'accountant.accounts', - 'ngRoute', - 'ngResource', - 'ngBootbox', - 'ui-notification', - 'mgcrea.ngStrap', - 'highcharts-ng', -]) - -.config(['$resourceProvider', function($resourceProvider) { - // Keep trailing slashes to avoid redirect by flask.. - $resourceProvider.defaults.stripTrailingSlashes = false; -}]) - -.factory('Operation', [ '$resource', function($resource) { - return $resource( - '/api/operation/:id', { - id: '@id' - } - ); -}]) - -.factory('OHLC', [ '$resource', '$routeParams', - function($resource, $routeParams) { - return $resource( - '/api/account/:account_id/ohlc', { - account_id: $routeParams.accountId - } - ); -}]) - -.factory('Category', [ '$resource', '$routeParams', - function($resource, $routeParams) { - return $resource( - '/api/account/:account_id/category', { - account_id: $routeParams.accountId - } - ); -}]) - -.factory('Balance', [ '$resource', '$routeParams', - function($resource, $routeParams) { - return $resource( - '/api/account/:account_id/balance', { - account_id: $routeParams.accountId - } - ); -}]) - -/* - * Controller for category chart. - */ -.controller( - 'CategoryChartController', [ - '$rootScope', '$scope', '$http', 'Category', 'Balance', - function($rootScope, $scope, $http, Category, Balance) { - - var colors = Highcharts.getOptions().colors; - $scope.revenueColor = colors[2]; - $scope.expenseColor = colors[3]; - - // Configure pie chart for categories. - $scope.config = { - options: { - chart: { - type: 'pie', - animation: { - duration: 500 - } - }, - plotOptions: { - pie: { - startAngle: -90 - }, - series: { - allowPointSelect: 0 - } - }, - tooltip: { - valueDecimals: 2, - valueSuffix: '€' - } - }, - yAxis: { - title: { - text: 'Categories' - } - }, - title: { - text: 'Répartition dépenses/recettes' - }, - series: [{ - name: 'Value', - data: [], - innerSize: '33%', - size: '60%', - dataLabels: { - formatter: function() { - return this.point.name; - }, - distance: -40 - } - }, { - name: 'Value', - data: [], - innerSize: '66%', - size: '60%', - dataLabels: { - formatter: function() { - return this.point.name !== null && this.percentage >= 2.5 ? this.point.name : null; - }, - } - }] - }; - - $scope.brightenColor = function(color) { - var brightness = 0.2; - - return Highcharts.Color(color).brighten(brightness).get(); - }; - - // Load categories, mainly to populate the pie chart. - $scope.load = function(begin, end) { - $scope.config.loading = true; - - Category.query({ - begin: begin.format('YYYY-MM-DD'), - end: end.format('YYYY-MM-DD') - }, function(data) { - var expenses = [], revenues = []; - - var expenseColor = $scope.brightenColor($scope.expenseColor); - var revenueColor = $scope.brightenColor($scope.revenueColor); - - angular.forEach(angular.fromJson(data), function(category) { - expenses.push({ - name: category.category, - y: -category.expenses, - color: expenseColor - }); - - revenues.push({ - name: category.category, - y: category.revenues, - color: revenueColor - }); - }); - - // Note: expenses and revenues must be in the same order than in series[0]. - $scope.config.series[1].data = revenues.concat(expenses); - - $scope.config.loading = false; - }); - }; - - /* - * Get account balance. - */ - $scope.getBalance = function(begin, end) { - Balance.get({ - begin: begin.format('YYYY-MM-DD'), - end: end.format('YYYY-MM-DD') - }, function(balance) { - // Update pie chart subtitle with Balance. - $scope.config.subtitle = { - text: 'Balance: ' + balance.balance - }; - - $scope.config.series[0].data = [{ - name: 'Revenues', - y: balance.revenues, - color: $scope.revenueColor - }, { - name: 'Expenses', - y: -balance.expenses, - color: $scope.expenseColor, - }]; - }); - }; - - // Reload categories and account status on range selection. - $rootScope.$on('rangeSelectedEvent', function(e, args) { - $scope.load(args.begin, args.end); - $scope.getBalance(args.begin, args.end); - }); -}]) - -/* - * Controller for the sold chart. - */ -.controller( - 'SoldChartController', [ - '$rootScope', '$scope', '$http', 'OHLC', - function($rootScope, $scope, $http, OHLC) { - // Configure chart for operations. - $scope.config = { - options: { - chart: { - zoomType: 'x' - }, - rangeSelector: { - buttons: [{ - type: 'month', - count: 1, - text: '1m' - }, { - type: 'month', - count: 3, - text: '3m' - }, { - type: 'month', - count: 6, - text: '6m' - }, { - type: 'year', - count: 1, - text: '1y' - }, { - type: 'all', - text: 'All' - }], - selected: 0, - }, - navigator: { - enabled: true - }, - tooltip: { - crosshairs: true, - shared: true, - valueDecimals: 2, - valueSuffix: '€' - }, - scrollbar: { - liveRedraw: false - } - }, - series: [{ - type: 'ohlc', - name: 'Sold', - data: [], - dataGrouping : { - units : [[ - 'week', // unit name - [1] // allowed multiples - ], [ - 'month', - [1, 2, 3, 4, 6] - ]] - } - }], - title: { - text: 'Sold evolution' - }, - xAxis: { - type: 'datetime', - dateTimeLabelFormats: { - month: '%e. %b', - year: '%Y' - }, - minRange: 3600 * 1000 * 24 * 14, // 2 weeks - events: { - afterSetExtremes: function(e) { - $scope.$emit('rangeSelectedEvent', { - begin: moment.utc(e.min), end: moment.utc(e.max) - }); - } - }, - currentMin: moment.utc().startOf('month'), - currentMax: moment.utc().endOf('month') - }, - yAxis: { - plotLines: [{ - color: 'orange', - width: 2, - value: 0.0 - }, { - color: 'red', - width: 2, - value: 0.0 - }] - }, - useHighStocks: true - }; - - $scope.loadSolds = function() { - $scope.config.loading = true; - - OHLC.query({}, function(data) { - $scope.config.series[0].data = []; - - angular.forEach(data, function(operation) { - $scope.config.series[0].data.push([ - moment.utc(operation.operation_date).valueOf(), - operation.open, operation.high, operation.low, operation.close - ]); - }); - - $scope.$emit('rangeSelectedEvent', { - begin: $scope.config.xAxis.currentMin, - end: $scope.config.xAxis.currentMax - }); - - $scope.config.loading = false; - }); - }; - - // Reload solds when an operation is saved. - $rootScope.$on('operationSavedEvent', function() { - $scope.loadSolds(); - }); - - // Reload solds when an operation is deleted. - $rootScope.$on('operationDeletedEvent', function() { - $scope.loadSolds(); - }); - - // Update authorized overdraft on account loading. - $rootScope.$on('accountLoadedEvent', function(e, account) { - $scope.config.yAxis.plotLines[1].value = account.authorized_overdraft; - }); - - // Select beginning and end of month. - $scope.loadSolds(); -}]) - -/* - * Controller for the operations. - */ -.controller( - 'OperationController', [ - '$scope', '$rootScope', '$routeParams', '$ngBootbox', 'Notification', 'Account', 'Operation', - function($scope, $rootScope, $routeParams, $ngBootbox, Notification, Account, Operation) { - // List of operations. - $scope.operations = []; - - /* - * Add an empty operation. - */ - $scope.add = function() { - var operation = new Operation({ - account_id: $routeParams.accountId - }); - - $scope.operations.splice(0, 0, operation); - }; - - /* - * Load operations. - */ - $scope.load = function(begin, end) { - $scope.operations = Operation.query({ - account_id: $routeParams.accountId, - begin: begin.format('YYYY-MM-DD'), - end: end.format('YYYY-MM-DD') - }); - }; - - /* - * Cancel edition. - */ - $scope.cancelEdit = function(operation, rowform, $index) { - if(!operation.id) { - $scope.operations.splice($index, 1); - } else { - rowform.$cancel(); - } - }; - - /* - * Toggle pointed indicator for an operation. - */ - $scope.togglePointed = function(operation, rowform) { - operation.pointed = !operation.pointed; - - // Save operation if not editing it. - if(!rowform.$visible) { - $scope.save(operation); - } - }; - - /* - * Toggle cancel indicator for an operation. - */ - $scope.toggleCanceled = function(operation) { - operation.canceled = !operation.canceled; - - $scope.save(operation); - }; - - /* - * Save an operation and emit operationSavedEvent. - */ - $scope.save = function($data, $index) { - // Check if $data is already a resource. - var operation; - - if($data.$save) { - operation = $data; - } else { - operation = $scope.operations[$index]; - operation = angular.merge(operation, $data); - } - - operation.confirmed = true; - - return operation.$save().then(function(data) { - Notification.success('Operation #' + data.id + ' saved.'); - - $scope.$emit('operationSavedEvent', data); - }); - }; - - /* - * Delete an operation and emit operationDeletedEvent. - */ - $scope.delete = function(operation, $index) { - var id = operation.id; - - $ngBootbox.confirm( - 'Voulez-vous supprimer l\'opération \\\'' + operation.label + '\\\' ?', - function(result) { - if(result) { - operation.$delete().then(function() { - Notification.success('Operation #' + id + ' deleted.'); - - // Remove operation from array. - $scope.operation.splice($index, 1); - - $scope.$emit('operationDeletedEvent', operation); - }); - } - } - ); - }; - - $scope.account = Account.get({ - id: $routeParams.accountId - }); - - /* - * Reload operations on rangeSelectedEvent. - */ - $rootScope.$on('rangeSelectedEvent', function(e, args) { - $scope.load(args.begin, args.end); - }); -}]); diff --git a/accountant/frontend/static/js/scheduler.js b/accountant/frontend/static/js/scheduler.js deleted file mode 100644 index 510dd92..0000000 --- a/accountant/frontend/static/js/scheduler.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - This file is part of Accountant. - - Accountant is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Accountant is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Accountant. If not, see . - */ -// vim: set tw=80 ts=2 sw=2 sts=2: -'use strict'; - -angular.module('accountant.scheduler', [ - 'ngRoute', - 'ngBootbox', - 'ui-notification', - 'mgcrea.ngStrap' -]) - -.config(['$resourceProvider', function($resourceProvider) { - // Keep trailing slashes to avoid redirect by flask.. - $resourceProvider.defaults.stripTrailingSlashes = false; -}]) - -.factory('ScheduledOperation', ['$resource', function($resource) { - return $resource( - '/api/scheduled_operation/:id', { - id: '@id' - } - ); -}]) - -.controller( - 'SchedulerController', [ - '$scope', '$rootScope', '$routeParams', '$ngBootbox', 'Notification', 'ScheduledOperation', - function($scope, $rootScope, $routeParams, $ngBootbox, Notification, ScheduledOperation) { - // Operation store. - $scope.operations = []; - - /* - * Add a new operation at the beginning of th array. - */ - $scope.add = function() { - var operation = new ScheduledOperation({ - account_id: $routeParams.accountId - }); - - // Insert new operation at the beginning of the array. - $scope.operations.splice(0, 0, operation); - }; - - /* - * Load operations. - */ - $scope.load = function() { - $scope.operations = ScheduledOperation.query({ - account_id: $routeParams.accountId - }); - }; - - /* - * Save operation. - */ - $scope.save = function($data, $index) { - var operation; - - if($data.$save) { - operation = $data; - } else { - operation = $scope.operations[$index]; - operation = angular.merge(operation, $data); - } - - return operation.$save().then(function(data) { - Notification.success('Operation #' + data.id + ' saved.'); - }); - }; - - /* - * Cancel operation edition. Delete if new. - */ - $scope.cancelEdit = function(operation, rowform, $index) { - if(!operation.id) { - $scope.operations.splice($index, 1); - } else { - rowform.$cancel(); - } - }; - - /* - * Delete operation. - */ - $scope.delete = function(operation, $index) { - var id = operation.id; - - $ngBootbox.confirm( - 'Voulez-vous supprimer l\'operation planifiée \\\'' + operation.label + '\\\' ?', - function(result) { - if(result) { - operation.$delete().then(function() { - Notification.success('Operation #' + id + ' deleted.'); - - // Remove account from array. - $scope.operations.splice($index, 1); - }); - } - } - ); - }; - - // Load operations on controller initialization. - $scope.load(); -}]); diff --git a/accountant/frontend/static/views/accounts.html b/accountant/frontend/static/views/accounts.html deleted file mode 100644 index 9920458..0000000 --- a/accountant/frontend/static/views/accounts.html +++ /dev/null @@ -1,114 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nom du compteSolde courantSolde pointéDécouvert autoriséActions
- -
- - {{ account.name }} - - - - {{ account.solds.current | currency : "€" }} - - - - {{ account.solds.pointed | currency : "€" }} - - - - {{ account.authorized_overdraft | currency : "€" }} - - -
-
- - - - - - - - - - - - - - - - -
-
-
-
diff --git a/accountant/frontend/static/views/login.tmpl.html b/accountant/frontend/static/views/login.tmpl.html deleted file mode 100644 index a381d06..0000000 --- a/accountant/frontend/static/views/login.tmpl.html +++ /dev/null @@ -1,17 +0,0 @@ - -
-
- -
- -
-
-
- -
- -
-
-
- diff --git a/accountant/frontend/static/views/operations.html b/accountant/frontend/static/views/operations.html deleted file mode 100644 index fdf1fdd..0000000 --- a/accountant/frontend/static/views/operations.html +++ /dev/null @@ -1,152 +0,0 @@ - - -
- -
- -
- -
- - -
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Date d'op.Libellé de l'opérationMontantSoldeCatégorieActions
- -
- - {{ operation.operation_date | date:"yyyy-MM-dd" }} - - - - {{ operation.label }} - - - - {{ operation.value | currency:"€" }} - - - {{ operation.sold | currency:"€" }} - - - {{ operation.category }} - - -
-
- - - - - - - - - - - - - - - - - -
-
-
-
-
diff --git a/accountant/frontend/static/views/scheduler.html b/accountant/frontend/static/views/scheduler.html deleted file mode 100644 index 2552387..0000000 --- a/accountant/frontend/static/views/scheduler.html +++ /dev/null @@ -1,142 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Date de débutDate de finJourFréq.Libellé de l'opérationMontantCatégorieActions
- -
- - {{ operation.start_date | date: "yyyy-MM-dd" }} - - - - {{ operation.stop_date | date: "yyyy-MM-dd" }} - - - - {{ operation.day }} - - - - {{ operation.frequency }} - - - - {{ operation.label }} - - - - {{ operation.value | currency : "€" }} - - - - {{ operation.category }} - - -
-
- - - - - - - - - - - -
-
-
-