var transactionControllers = angular.module('transactionControllers', ['ngResource']); function amountToNumber(amount) { var number = amount.toString().replace('.', '').replace(',', '.'); if (number.charAt(0) !== '+' && number.charAt(0) !== '-') { number = '-' + number; } return number; } function numberToAmount(number) { var amount = number.toFixed(2).toString().replace('.', ','); if (amount.charAt(0) !== '-' && amount !== '0,00') { amount = "+" + amount; } return amount; } transactionControllers.controller('TransactionListCtrl', [ '$scope', '$routeParams', '$resource', '$q', '$uibModal', '$log', 'Transaction', function($scope, $routeParams, $resource, $q, $uibModal, $log, Transaction) { function refreshExpenses() { var accountUsers = $resource($scope.account._links.users.href).get(); accountUsers.$promise.then(function(data) { $scope.accountUsers = accountUsers._embedded.users; // get outstandings (expenses and withdrawals) for all users of the account var promises = new Array($scope.accountUsers.length); for (i = 0; i < $scope.accountUsers.length; i++) { var resource = $resource('/api/transactions/search/outstandings', { accountId: $scope.account.id, userId: $scope.accountUsers[i].id, size: 500 }).get(); promises[i] = resource.$promise; } $q.all(promises).then(function(data) { for (i = 0; i < data.length; i++) { $scope.accountUsers[i].outstandings = new Array(data[i]._embedded.transactions.length); $scope.accountUsers[i].outstandingSum = 0.0; for (j = 0; j < data[i]._embedded.transactions.length; j++) { $scope.accountUsers[i].outstandings[j] = new Object(); $scope.accountUsers[i].outstandings[j].date = data[i]._embedded.transactions[j].date; $scope.accountUsers[i].outstandings[j].description = data[i]._embedded.transactions[j].description; $scope.accountUsers[i].outstandings[j].href = data[i]._embedded.transactions[j]._links.self.href; $scope.accountUsers[i].outstandings[j].category = data[i]._embedded.transactions[j].category.name; if ($scope.accountUsers[i].outstandings[j].category === 'Private Entnahme') { // magic strings ftw $scope.accountUsers[i].outstandingSum += data[i]._embedded.transactions[j].amount; $scope.accountUsers[i].outstandings[j].amount = numberToAmount(data[i]._embedded.transactions[j].amount); } else { $scope.accountUsers[i].outstandingSum -= data[i]._embedded.transactions[j].amount; $scope.accountUsers[i].outstandings[j].amount = numberToAmount(-data[i]._embedded.transactions[j].amount); } } $scope.accountUsers[i].outstandingAmount = numberToAmount($scope.accountUsers[i].outstandingSum); } }); }); } $scope.refreshList = function() { // reset form if ($scope.form) { $scope.form.$setPristine(); $scope.form.$setUntouched(); } $scope.transaction = { amount: undefined, date: new Date(), creditor: "" }; if (typeof $scope.categories !== 'undefined') { $scope.transaction.category = $scope.categories[0]; // XXX dirty hack - one day we will have to move that into the database... if ($scope.account.name == 'Gemeinschaftskonto') { $scope.transaction.category = $scope.categories[3]; } } $scope.checkCategory(); // get recent transactions var transactions = $resource('/api/transactions/search/forAccount', { accountId: $scope.account.id, size: $scope.pageSize, sort: 'date,desc' }).get(); transactions.$promise.then(function(data) { $scope.transactions = transactions._embedded.transactions; $scope.nextPageAvailable = (transactions.page.totalPages !== transactions.page.number + 1); $scope.prevPageAvailable = (transactions.page.number > 0); $scope.pageNumber = transactions.page.number; for (i = 0; i < $scope.transactions.length; i++) { $scope.transactions[i].amount = numberToAmount($scope.transactions[i].amount); } }); refreshExpenses(); } $scope.checkCategory = function() { // if this is selected to be a withdrawal, preselect the current user and disable "no creditor" radio button if ($scope.transaction.category.name === 'Private Entnahme') { $scope.transaction.creditor = $scope.userinfo._links.self.href; document.getElementById('noCreditorRadioButton').disabled = true; } else { document.getElementById('noCreditorRadioButton').disabled = false; document.getElementById('noCreditorRadioButton').checked = true; } } $scope.initList = function() { $scope.pageSize = 20; var userinfo = $resource('/user').get(); userinfo.$promise.then(function(data) { $scope.userid = userinfo.name; userinfo = $resource('/api/users/search/findByUsername', { username: $scope.userid }).get(); userinfo.$promise.then(function(data) { $scope.userinfo = userinfo; }); var accountInfo = $resource('/api/accounts/search/findByUserName', { username: $scope.userid }).get(); accountInfo.$promise.then(function(data) { $scope.accounts = accountInfo._embedded.accounts; if ($scope.account == undefined) { $scope.account = $scope.accounts[0]; if ($routeParams.account != undefined) { for (i = 0; i < $scope.accounts.length; i++) { if ($scope.accounts[i].name == $routeParams.account) $scope.account = $scope.accounts[i]; } } } var categories = $resource('/api/categories/search/listForAccount', { accountId: $scope.account.id }).get(); categories.$promise.then(function(data) { $scope.categories = categories._embedded.categories; // remove retired categories for (i = 0; i < $scope.categories.length; i++) { if ($scope.categories[i].retired) { $scope.categories.splice(i, 1); i--; } } $scope.refreshList(); }); }); }); } $scope.submitTransaction = function() { if (document.getElementById('amountInput').value) { document.getElementById('transactionSubmit').disabled = true; $scope.waitingForSubmission = true; var newTransaction = new Transaction({ "account" : $scope.account._links.self.href, "amount" : amountToNumber($scope.transaction.amount), "date" : $scope.transaction.date, "description" : $scope.transaction.description != undefined ? $scope.transaction.description : '', "category" : $scope.transaction.category._links.self.href, "creditor" : $scope.transaction.creditor }); newTransaction.$save(function(transaction) { console.log(transaction); document.getElementById('transactionSubmit').disabled = false; $scope.waitingForSubmission = false; $scope.refreshList(); $scope.lastSubmission = newTransaction; $scope.lastSubmission.amount = numberToAmount(newTransaction.amount); }); } } $scope.deleteLastSubmission = function() { var testTrns = $resource($scope.lastSubmission._links.self.href).delete(function() { $scope.lastSubmission = undefined; $scope.refreshList(); }); }; $scope.moreTransactions = function() { if ($scope.nextPageAvailable) { $scope.pageNumber++; var transactions = $resource('/api/transactions/search/forAccount', { accountId: $scope.account.id, size: $scope.pageSize, sort: 'date,desc', page: $scope.pageNumber }).get(); transactions.$promise.then(function(data) { for (i = 0; i < transactions._embedded.transactions.length; i++) { transactions._embedded.transactions[i].amount = numberToAmount(transactions._embedded.transactions[i].amount); } $scope.transactions = $scope.transactions.concat(transactions._embedded.transactions); $scope.nextPageAvailable = (transactions.page.totalPages !== transactions.page.number + 1); $scope.prevPageAvailable = (transactions.page.number > 0); $scope.pageNumber = transactions.page.number; }); } }; $scope.updateSuggestions = function(val) { var suggestions = $resource('/api/transactions/search/descriptions', { accountId: $scope.account.id, prefix: val.toLowerCase() }).get(); return suggestions.$promise.then(function(data) { var newSuggestions = new Array(); var numSuggestions = 0; var idx = 0; while (numSuggestions < 6 && idx < suggestions._embedded.transactions.length) { if (newSuggestions.indexOf(suggestions._embedded.transactions[idx].description) < 0) { newSuggestions[numSuggestions] = suggestions._embedded.transactions[idx].description; numSuggestions++; } idx++; } return newSuggestions; }); }; $scope.openDetails = function(transaction) { var modalInstance = $uibModal.open({ templateUrl: 'detailsTemplate.html', controller: 'DetailsModalCtrl', resolve: { transactionId: transaction.id, categories: function () { return $scope.categories; }, accountUsers: function () { return $scope.accountUsers; } } }); modalInstance.result.then(function(editedTransaction) { $scope.lastSubmission = undefined; if (editedTransaction.amount != undefined) { transaction.amount = editedTransaction.amount; transaction.date = editedTransaction.date; transaction.description = editedTransaction.description; transaction.creditor = $resource(editedTransaction.creditor).get(); transaction.category = $resource(editedTransaction.category).get(); } else { // if the transaction has been deleted, only the ID is returned for (i = 0; i < $scope.transactions.length; i++) { if ($scope.transactions[i].id == editedTransaction) { $scope.transactions.splice(i, 1); } } } refreshExpenses(); }, function () { $log.info('DetailsModal dismissed ' + new Date()); }); }; $scope.openOutstandings = function(user) { var modalInstance = $uibModal.open({ templateUrl: 'outstandingsTemplate.html', controller: 'OutstandingsModalCtrl', resolve: { accountUser: user } }); modalInstance.result.then(function() { $scope.refreshList(); }, function () { $log.info('OutstandingsModal dismissed ' + new Date()); }); }; // initialize (http://stackoverflow.com/a/25662066/3761783) $scope.initList(); } ]); transactionControllers.controller('DetailsModalCtrl', [ '$scope', '$resource', '$uibModalInstance', 'Transaction', 'transactionId', 'categories', 'accountUsers', function($scope, $resource, $uibModalInstance, Transaction, transactionId, categories, accountUsers) { $scope.accountUsers = accountUsers; var transaction = Transaction.get({ id: transactionId }); transaction.$promise.then(function(data) { transaction.amount = numberToAmount(transaction.amount); transaction.date = new Date(transaction.date); transaction.creditor = $resource(transaction._links.creditor.href).get(); transaction.category = $resource(transaction._links.category.href).get(); transaction.category.$promise.then(function(data) { $scope.transaction = transaction; $scope.categories = categories; for (i = 0; i < $scope.categories.length; i++) { if ($scope.categories[i].name == $scope.transaction.category.name) { $scope.transaction.category = $scope.categories[i]; break; } } transaction.creditor.$promise.then(function(data) { $scope.transaction.creditor = $scope.transaction.creditor._links.user.href; }, function(error) { // transactions without a creditor return a HTTP 404 error $scope.transaction.creditor = ""; }); }); }); $scope.ok = function () { $scope.transaction.category = $scope.transaction.category._links.self.href; $scope.transaction.amount = amountToNumber($scope.transaction.amount); $scope.transaction.$update(function(data) {}).then( $uibModalInstance.close($scope.transaction) ); }; $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; $scope.deleteTransaction = function() { var deletedTransactionId = $scope.transaction.id; $resource($scope.transaction._links.self.href).remove().$promise.then(function() { $uibModalInstance.close(deletedTransactionId); }); } } ]); transactionControllers.controller('OutstandingsModalCtrl', [ '$scope', '$uibModalInstance', '$http', '$resource', '$q', 'accountUser', function($scope, $uibModalInstance, $http, $resource, $q, accountUser) { $scope.accountUser = accountUser; $scope.ok = function () { $uibModalInstance.close(); }; $scope.close = function () { $uibModalInstance.dismiss('close'); }; $scope.clearOutstandings = function() { var promises = new Array($scope.accountUser.outstandings.length); for (i = 0; i < $scope.accountUser.outstandings.length; i++) { if ($scope.accountUser.outstandings[i].category === 'Private Entnahme') { // delete withdrawals var resource = $resource($scope.accountUser.outstandings[i].href).remove(); promises[i] = resource.$promise; } else { // remove creditor from expenses promises[i] = $http.patch($scope.accountUser.outstandings[i].href, '{ "creditor" : "" }'); } } $q.all(promises).then(function(data) { $uibModalInstance.close('cleared outstandings'); }); } } ]); transactionControllers.controller('ImportCtrl', [ '$scope', '$resource', '$location', 'ImportService', function($scope, $resource, $location, ImportService) { $scope.importCSV = function () { ImportService.setAccount($scope.account); ImportService.setCategories($scope.categories); ImportService.setCSV($scope.csvInput); $location.path('importList'); } $scope.initPage = function () { var userinfo = $resource('/user').get(); userinfo.$promise.then(function(data) { $scope.userid = userinfo.name; userinfo = $resource('/api/users/search/findByUsername', { username: $scope.userid }).get(); userinfo.$promise.then(function(data) { $scope.userinfo = userinfo; }); var accountInfo = $resource('/api/accounts/search/findByUserName', { username: $scope.userid }).get(); accountInfo.$promise.then(function(data) { $scope.accounts = accountInfo._embedded.accounts; $scope.account = $scope.accounts[0]; var categories = $resource('/api/categories/search/listForAccount', { accountId: $scope.account.id }).get(); categories.$promise.then(function(data) { $scope.categories = categories._embedded.categories; if ($scope.importForm) { $scope.importForm.$setPristine(); $scope.importForm.$setUntouched(); } }); }); }); } $scope.initPage(); } ]); transactionControllers.controller('ImportListCtrl', [ '$scope', '$resource', '$location', 'ImportService', 'Transaction', function($scope, $resource, $location, ImportService, Transaction) { $scope.processCSV = function () { $scope.account = ImportService.getAccount(); $scope.categories = ImportService.getCategories(); $scope.csvInput = ImportService.getCSV(); $scope.csv = Papa.parse(ImportService.getCSV()).data; var matcherData = $resource('/api/matchers/search/listForAccount', { projection: 'full', accountId: $scope.account.id }).get(); matcherData.$promise.then(function(data) { matcherData = matcherData._embedded.matchers; $scope.matchers = new Array(matcherData.length); for (i = 0; i < $scope.matchers.length; i++) { $scope.matchers[i] = {}; $scope.matchers[i].id = matcherData[i].id; $scope.matchers[i].position = matcherData[i].position; if (matcherData[i].dateMatcherCode != null) { $scope.matchers[i].dateMatcher = new Function('x', matcherData[i].dateMatcherCode); $scope.matchers[i].dateMatcherCode = matcherData[i].dateMatcherCode; } if (matcherData[i].typeMatcherCode != null) { $scope.matchers[i].typeMatcher = new Function('x', matcherData[i].typeMatcherCode); $scope.matchers[i].typeMatcherCode = matcherData[i].typeMatcherCode; } if (matcherData[i].textMatcherCode != null) { $scope.matchers[i].textMatcher = new Function('x', matcherData[i].textMatcherCode); $scope.matchers[i].textMatcherCode = matcherData[i].textMatcherCode; } if (matcherData[i].amountMatcherCode != null) { $scope.matchers[i].amountMatcher = new Function('x', matcherData[i].amountMatcherCode); $scope.matchers[i].amountMatcherCode = matcherData[i].amountMatcherCode; } $scope.matchers[i].targetDescription = matcherData[i].targetDescription; $scope.matchers[i].targetCategory = matcherData[i].targetCategory; } for (i = 0; i < $scope.csv.length; i++) { var j = 0; while (j < $scope.matchers.length) { var match = true; if ($scope.matchers[j].dateMatcher) match = $scope.matchers[j].dateMatcher($scope.csv[i][1]); if (match && $scope.matchers[j].typeMatcher) match = $scope.matchers[j].typeMatcher($scope.csv[i][2]); if (match && $scope.matchers[j].textMatcher) match = $scope.matchers[j].textMatcher($scope.csv[i][3]); if (match && $scope.matchers[j].amountMatcher) match = $scope.matchers[j].amountMatcher($scope.csv[i][4].toString().replace('.', '').replace(',', '.')); if (match) { $scope.csv[i].targetDescription = $scope.matchers[j].targetDescription; $scope.csv[i].matcherPosition = $scope.matchers[j].position; for (k = 0; k < $scope.categories.length; k++) { if ($scope.categories[k].name == $scope.matchers[j].targetCategory.name) $scope.csv[i].targetCategory = $scope.categories[k]; } break; } j++; } } }); } $scope.importTransaction = function(line) { if (line.targetCategory) { var newTransaction = new Transaction({ "account" : $scope.account._links.self.href, "amount" : line[4].replace(',', '.'), "date" : new Date(line[1].substr(6,4) + "-" + line[1].substr(3,2) + "-" + line[1].substr(0,2)), "description" : line.targetDescription != undefined ? line.targetDescription : '', "category" : line.targetCategory._links.self.href, "creditor" : "" }); newTransaction.$save(function(transaction) { line.submitted = true; console.log(transaction); line.submittedTransactionLink = transaction._links.self.href; line.submittedTransactionId = transaction.id; // delete from CSV var descriptionPos = $scope.csvInput.search(line[3]); var lineStart = $scope.csvInput.lastIndexOf('\n', descriptionPos); var lineEnd = $scope.csvInput.indexOf('\n', descriptionPos); if (lineEnd < 0) lineEnd = $scope.csvInput.length; $scope.csvInput = $scope.csvInput.substring(0, lineStart) + $scope.csvInput.substring(lineEnd, $scope.csvInput.length); }); } } } ]);