diff --git a/accountant/api/views/entries.py b/accountant/api/views/entries.py index d29b696..4c04c31 100644 --- a/accountant/api/views/entries.py +++ b/accountant/api/views/entries.py @@ -98,7 +98,7 @@ class EntryResource(Resource): @session_aware @marshal_with_field(Object(resource_fields)) - def put(self, entry_id, session): + def post(self, entry_id, session): kwargs = parser.parse_args() assert (id not in kwargs or kwargs.id is None diff --git a/accountant/frontend/static/js/entries.js b/accountant/frontend/static/js/entries.js index 99eb6b4..40cc316 100644 --- a/accountant/frontend/static/js/entries.js +++ b/accountant/frontend/static/js/entries.js @@ -14,13 +14,29 @@ You should have received a copy of the GNU Affero General Public License along with Accountant. If not, see . */ -accountantApp.controller( - "EntryController", function($scope, $http, $rootScope, $filter) { +accountantApp + +.factory("Entries", [ "$resource", function($resource) { + return $resource( + "/api/entries/:id", { + id: "@id" + } + ); +}]) + +.controller( + "EntryController", [ + "$scope", "$http", "$rootScope", "$filter", "Entries", + function($scope, $http, $rootScope, $filter, Entries) { + // Range for entries. + $scope.begin = moment.utc().startOf('month'); + $scope.end = moment.utc().endOf('month'); + // Entry store and selection $scope.entries = []; + $scope.categories = []; - $scope.chartValues = []; - $scope.pieChartValues = []; + $scope.selectedItem = null; $scope.account = null; @@ -28,9 +44,11 @@ accountantApp.controller( // Placeholder for saved value to cancel entry edition $scope.savedItem = null; - // Range for entries. - $scope.begin = moment.utc().startOf('month'); - $scope.end = moment.utc().endOf('month'); + // Function to reset the new entry. + $scope.resetNewEntry = function() { + // The new entry. + $scope.newEntry = new Entries({}); + }; $scope.setExtremes = function(e) { begin = moment.utc(e.min); @@ -58,6 +76,9 @@ accountantApp.controller( plotOptions: { pie: { startAngle: -90 + }, + series: { + allowPointSelect: 0 } }, tooltip: { @@ -183,6 +204,8 @@ accountantApp.controller( // Load categories, mainly to populate the pie chart. $scope.loadCategories = function() { + $scope.categoriesChartConfig.loading = true; + $http.get("/api/categories", { params: { account: $scope.account.id, @@ -214,14 +237,14 @@ accountantApp.controller( // Note: expenses and revenues must be in the same order than in series[0]. config.series[1].data = revenues.concat(expenses); - $scope.categoriesChartConfig.loaded = true; + $scope.categoriesChartConfig.loading = false; $scope.loadSolds(); }); }; $scope.loadSolds = function() { - $scope.soldChartConfig.loaded = true; + $scope.soldChartConfig.loading = true; $http.get("/api/solds", { params: { @@ -239,10 +262,15 @@ accountantApp.controller( ]); }); + $scope.soldChartConfig.loading = false; + $scope.loadEntries(); }); }; + /* + * Hook on account selected event to display account status. + */ $rootScope.$on("accountSelectedEvent", function(event, args) { $scope.account = args.account; @@ -253,93 +281,59 @@ accountantApp.controller( $scope.getAccountStatus(); }); - $rootScope.$on("entriesLoadedEvent", function(event, args) { - //$scope.loadCategories(); - }); - $scope.getAccountStatus = function() { - $scope.categoriesChartConfig.loaded = true; + $scope.categoriesChartConfig.loading = true; $http.get("/api/accounts/" + $scope.account.id, { params: { begin: $scope.begin.format('YYYY-MM-DD'), end: $scope.end.format('YYYY-MM-DD') } - }).success(function(status) { - var colors = Highcharts.getOptions().colors; + }).success(function(account) { + $scope.categoriesChartConfig.loading = false; - var config = $scope.categoriesChartConfig; - - // Update pie chart subtitle with Balance. - config.subtitle = { - text: "Balance: " + status.balance - }; - - config.series[0].data = [{ - name: "Revenues", - y: status.revenues, - color: colors[2] - }, { - name: "Expenses", - y: -status.expenses, - color: colors[3], - }]; - - $scope.soldChartConfig.yAxis.plotLines[1].value = status.authorized_overdraft; - - $scope.loadCategories(); + $scope.$emit("accountLoadedEvent", account); }); }; + $rootScope.$on("accountLoadedEvent", $scope.loadCategories); + + $rootScope.$on("accountLoadedEvent", function(e, account) { + var colors = Highcharts.getOptions().colors; + + var config = $scope.categoriesChartConfig; + + // Update pie chart subtitle with Balance. + config.subtitle = { + text: "Balance: " + account.balance + }; + + config.series[0].data = [{ + name: "Revenues", + y: account.revenues, + color: colors[2] + }, { + name: "Expenses", + y: -account.expenses, + color: colors[3], + }]; + + $scope.soldChartConfig.yAxis.plotLines[1].value = account.authorized_overdraft; + }); + // Function to load entries from server for a specific account and month. $scope.loadEntries = function() { // Clean up selected entry. $scope.selectedItem = null; $scope.savedItem = null; - if(!$scope.entriesLoading) { - $scope.entriesLoading = true; - - $http.get("/api/entries", { - params: { - account: $scope.account.id, - begin: $scope.begin.format('YYYY-MM-DD'), - end: $scope.end.format('YYYY-MM-DD') - } - }).success(function(data) { - var entries = [{ - id: null, - pointed: false, - operation_date: null, - label: null, - value: null, - sold: null, - pointedsold: null, - category: null, - account_id: null, - state: 'new', - canceled: false, - scheduled_operation_id: null - }]; - - if(data) { - entries = entries.concat(angular.fromJson(data)); - } - - $scope.entries = entries; - - $scope.entriesLoading = false; - - $scope.$emit("entriesLoadedEvent", {entries: entries}); - }); - } - }; - - // Returns the CSS class for a pointed entry. - $scope.pointedEntryClass = function(entry) { - if(entry.pointed) { - return "active"; - } + $scope.entries = Entries.query({ + account: $scope.account.id, + begin: $scope.begin.format('YYYY-MM-DD'), + end: $scope.end.format('YYYY-MM-DD') + }, function(data) { + $scope.$emit("entriesLoadedEvent", {entries: data}); + }); }; // Returns the CSS class for an entry row. @@ -380,21 +374,18 @@ accountantApp.controller( } // Save current entry values. - $scope.savedItem = angular.copy(entry); - $scope.selectedItem = entry; + if(!entry.id) { + $scope.savedItem = angular.copy(entry); + $scope.selectedItem = entry; + } // Enter edit state. entry.state='edit'; }; - // Returns true if the entry is a new one. - $scope.isNew = function(entry) { - return !$scope.isSaved(entry) && (entry.state === 'edit' || entry.state === 'new'); - }; - // Returns true if the entry is in editing state. $scope.isEditing = function(entry) { - return entry.state === 'edit' || entry.state === 'new'; + return entry.state === 'edit'; }; // Returns true if the entry is in displaying state. @@ -407,38 +398,10 @@ accountantApp.controller( return entry.id !== null; }; - $scope.iconSaveClass = function(entry) { - if(!$scope.isSaved(entry)) { - return "fa fa-plus"; - } else if ($scope.isEditing(entry)) { - return "fa fa-floppy-o"; - } - }; - - $scope.iconCancelClass = function(entry) { - if($scope.isNew(entry)) { - return "fa fa-times"; - } else if ($scope.isEditing(entry)) { - return "fa fa-ban"; - } - }; - // Cancel current editing entry or clears field if a new one. $scope.cancelEditEntry = function(entry) { - if ($scope.savedItem === null) { - // We are cancelling the new entry, just reset it. - entry.id = null; // id should not change, but just in case... - entry.pointed = false; - entry.operation_date = null; - entry.label = null; - entry.value = null; - entry.account_id = $scope.account.id; // account_id should not change, but just in case... - entry.category = null; - entry.canceled = false; - entry.scheduled_operation_id = null; - - // Reset state to new. - entry.state = 'new'; + if (entry.id) { + entry.$get(); } else { // Reset selected item fields to saved item ones. entry.id = $scope.savedItem.id; // id should not change, but just in case... @@ -462,48 +425,55 @@ accountantApp.controller( // Points an entry. $scope.pointEntry = function(entry) { - if(entry.pointed) { - entry.pointed = false; - } else { - entry.pointed = true; - } + entry.pointed = !entry.pointed; - // Save the entry if not new. - if(!$scope.isNew(entry)) { $scope.saveEntry(entry); - } }; - // Saves an entry. + // Create an new entry. + $scope.createEntry = function(entry) { + entry.account_id = $scope.account.id; + + // Ajax call to create an entry + $scope.newEntry.$save(function(data) { + $scope.resetNewEntry(); + + // Send the "entry saved" event. + $scope.$emit("entryCreatedEvent", entry); + }); + }; + + $rootScope.$on("entryCreatedEvent", function(e, entry) { + new PNotify({ + type: "success", + title: "Save", + text: "Entry #" + entry.id + " created." + }); + + }); + + // Saves an existing entry. $scope.saveEntry = function(entry) { - // Affects the account ID if not (should never happen) + if(!entry.account_id) { entry.account_id = $scope.account.id; } - // Prepare the Ajax call to save the entry. - var type; - var url = "/api/entries"; - - if($scope.isSaved(entry)) { - url += "/" + entry.id; - } - // Ajax call to save an entry - $http.put(url, angular.toJson(entry)).success(function(data) { - $.pnotify({ - type: "success", - title: "Save", - text: data - }); - - // $scope.savedItem = null; - + entry.$save(function(data) { // Send the "entry saved" event. $scope.$emit("entrySavedEvent", entry); }); }; + $rootScope.$on("entrySavedEvent", function(e, entry) { + new PNotify({ + type: "success", + title: "Save", + text: "Entry #" + entry.id + " saved." + }); + }); + $scope.removeEntry = function(entry) { $scope.removingEntry = entry; $("#remove_entry").modal({ @@ -520,13 +490,7 @@ accountantApp.controller( $scope.confirmRemoveEntry = function() { // Cancel current editing. if ($scope.removingEntry) { - $http.delete("/api/entries/" + $scope.removingEntry.id).success(function (result) { - $.pnotify({ - type: "success", - title: "Delete", - text: result - }); - + $scope.removingEntry.$delete(function (result) { // Send the "entry removed" event. $scope.$emit("entryRemovedEvent", $scope.removingEntry); @@ -535,10 +499,18 @@ accountantApp.controller( } }; + $rootScope.$on("entryRemovedEvent", function(e, entry) { + new PNotify({ + type: "success", + title: "Delete", + text: "Entry #" + entry.id + " deleted." + }); + }); + $scope.closeModal = function(modalScope) { // Close the modal dialog if(modalScope && modalScope.dismiss) { modalScope.dismiss(); } }; -}); +}]);