/* 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. Foobar 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 . */ // Entry object function entry(){ this.id=ko.observable(); this.pointed=ko.observable(); this.operation_date=ko.observable(); this.label=ko.observable(); this.value=ko.observable(); this.account_id=ko.observable(); this.sold=ko.observable(); this.pointedsold=ko.observable(); this.category=ko.observable(); } // Util function to show a message in message placeholder. function message(alertType, title, message) { $(".alert").alert('close'); $("#message-placeholder").append('

' + title + '

' + message + '
'); } // The ListViewModel used to instanciate viewmodel. var EntryController = function($scope, $http, $rootScope, $filter) { // Entry store and selection $scope.entries = []; $scope.selectedItem = null; $scope.account = null; // Placeholder for saved value to cancel entry edition $scope.savedItem = null; $scope.entriesLoaded = function(entries) { var entriesReversed = entries.slice().reverse(); var categories = []; var chartValues = []; var pieChartValuesTmp = {}; var pieChartValues = []; angular.forEach(entriesReversed, function(entry) { var category = entry.category; var value = entry.value ? Number(entry.value) : null; var sold = entry.sold ? Number(entry.sold) : null; var operation_date = entry.operation_date; if(operation_date && sold) { chartValues.push([entry.operation_date, sold]); } if(category && category != '') { if(categories.indexOf(category) == -1) { categories.push(category); } if(value && value < 0.0) { var oldValue = 0.0; if(pieChartValuesTmp[category]) { oldValue = pieChartValuesTmp[category]; } pieChartValuesTmp[category] = oldValue - value; } } }); // Second pass: transform to an array readable by jqplot. angular.forEach(pieChartValuesTmp, function(value, key) { pieChartValues.push([key, value]); }); $scope.categories = categories; $scope.drawChart({account: $scope.account, entries: chartValues}, "#entries-chart-placeholder"); $scope.drawPieChart(pieChartValues, "#expense-categories-chart-placeholder"); }; // Function to load entries from server for a specific account and month. $scope.loadEntries = function(account, month) { if(account) { $scope.account = account; } // Clean up selected entry. $scope.selectedItem = null; $scope.savedItem = null; if(account && month) { $http.get("api/entries/" + account.id + "/" + month.year + "/" + month.month).success($scope.loadEntries_success); } else { $scope.loadEntries_success(null); } }; // Load entries success callback $scope.loadEntries_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: 'edit' }]; if(data) { entries = entries.concat(angular.fromJson(data)); } $scope.entries = entries; $scope.$emit("entriesLoadedEvent", {entries: entries}); }; // Returns the CSS class for a pointed entry. $scope.pointedEntryClass = function(entry) { if(entry.pointed) { return "active"; } }; // Returns the CSS class for an entry row. $scope.entryRowClass = function(sold) { if(sold < $scope.account.authorized_overdraft) { return 'error'; } else if (sold < 0) { return 'warning'; } }; // Returns the CSS class for an entry sold. $scope.entryValueClass = function(sold) { if(sold < $scope.account.authorized_overdraft) { return 'text-error'; } else if (sold < 0) { return 'text-warning'; } }; // Starts editing an entry $scope.editEntry = function(entry) { // Cancel previous editing. if($scope.selectedItem) { $scope.cancelEditEntry($scope.selectedItem); } // Save current entry values. $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 !entry.id; }; // Returns true if the entry is in editing state. $scope.isEditing = function(entry) { return entry.state === 'edit'; }; // Returns true if the entry is in displaying state. $scope.isDisplaying = function(entry) { return entry.id && (!entry.state || entry.state === 'display'); }; $scope.iconSaveClass = function(entry) { if($scope.isNew(entry)) { return "icon-plus"; } else if ($scope.isEditing(entry)) { return "icon-ok"; } }; $scope.iconCancelClass = function(entry) { if($scope.isNew(entry)) { return "icon-remove"; } else if ($scope.isEditing(entry)) { return "icon-ban-circle"; } }; // Cancel current editing entry or clears field if a new one. $scope.cancelEditEntry = function(entry) { if ($scope.isNew(entry)) { // 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; } else if($scope.isEditing(entry)) { if($scope.savedItem) { // Reset selected item fields to saved item ones. entry.id = $scope.savedItem.id; // id should not change, but just in case... entry.pointed = $scope.savedItem.pointed; entry.operation_date = $scope.savedItem.operation_date; entry.label = $scope.savedItem.label; entry.value = $scope.savedItem.value; entry.account_id = $scope.savedItem.account_id; // account_id should not change, but just in case... entry.category = $scope.savedItem.category; } // Reset saved and selected items to null. $scope.savedItem = null; $scope.selectedItem = null; // Enter display state. entry.state = 'display'; } }; // Points an entry. $scope.pointEntry = function(entry) { if(entry.pointed) { entry.pointed = false; } else { entry.pointed = true; } // Save the entry if not new. if(!$scope.isNew(entry)) { $scope.saveEntry(entry); } }; // Saves an 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.isNew(entry)) { url += "/" + entry.id; } // Ajax call to save an entry $http.put(url, angular.toJson(entry)).success(function(data) { message("success", "Save", data); // $scope.savedItem = null; // Send the "entry saved" event. $scope.$emit("entrySavedEvent", entry); }); }; // Removes an entry. $scope.removeEntry = function(entry, modalScope) { // Cancel current editing. //$scope.cancelEditEntry(entry); if (!$scope.isNew(entry)) { $http.delete("api/entries/" + entry.id).success(function (result) { message("success", "Delete", result); // Send the "entry removed" event. $scope.$emit("entryRemovedEvent", entry); $scope.closeModal(modalScope); }).error(function (data) { message("error", "Delete", data); $scope.closeModal(modalScope); }); } }; $scope.closeModal = function(modalScope) { // Close the modal dialog if(modalScope && modalScope.dismiss) { modalScope.dismiss(); } }; // Function to draw the sold evolution chart. $scope.drawChart = function(data, elementId) { // Clear previous chart var element = angular.element(elementId); element.html(""); element.css("height", "0px"); var entries = data.entries; if(entries && entries.length > 1) { // Prepare for today vertical line. var today = new Date(); today.setHours(0); today.setMinutes(0); // Find first and last days to set limits of the x axis. var day = 24 * 60 * 60 * 1000; var firstDate = $filter('date')(new Date(Date.parse(entries[0][0]).valueOf() - day), 'yyyy-MM-dd'); var lastDate = $filter('date')(new Date(Date.parse(entries[entries.length -1][0]).valueOf() + day), 'yyyy-MM-dd'); // Plot chart, and store it in a window parameter for resize callback (need to be done better than it...) window.chart = $.jqplot(element.prop("id"), [entries], { // Title of the chart title: "Évolution du solde", axes:{ // Parameters for the date axis xaxis:{ autoscale: true, // Date rendere for this axis renderer:$.jqplot.DateAxisRenderer, // Limits min: firstDate, max: lastDate, // Tick options tickOptions: { // Format date for x axis. formatString: "%F" } }, // Parameters for the value axis yaxis: { autoscale: true, } }, // Highlighter parameters highlighter: { show: true, //yvalues: 4, formatString: '' }, // Series parameters series: [{ // We use the OHLC (open, high, low, close) rendered. //renderer:$.jqplot.OHLCRenderer, color: "blue", rendererOptions: { smooth: true, } }], // To display horizontal (0) and vertical (today) lines canvasOverlay: { show: true, objects: [ // Orange horizontal line for 0 limit {dashedHorizontalLine: { name: "zero", y: 0, lineWidth: 1, color: "orange", shadow: false }}, // Red horizontal line for authorized overdraft limit {dashedHorizontalLine: { name: "overdraft", y: data.account.authorized_overdraft, lineWidth: 1, color: "red", shadow: false }}, // Gray vertical line for today { dashedVerticalLine: { name: "today", x: today, lineWidth: 1, color: "gray", shadow: false }} ] } }); } else { // Reset chart to null to avoid redraw of a non existing chart in resize callback. window.chart = null; } }; // Function to draw the expense category pie chart. $scope.drawPieChart = function(entries, elementId) { // Clear previous chart var element = angular.element(elementId); element.html(""); element.css("height", "0px"); if(entries && entries.length > 1) { // Plot chart, and store it in a window parameter for resize callback (need to be done better than it...) window.pieChart = $.jqplot(element.attr("id"), [entries], { // Title of the chart title: "Dépenses", // Series parameters. seriesDefaults: { // Pie chart renderer renderer: $.jqplot.PieRenderer, rendererOptions: { showDataLabels: true, } }, // Legend parameters. legend: { show: true, location: 'e', rendererOptions: { numberRows: 9, numberColumns: 2 } }, // Highlighter parameters; highlighter: { show: true, formatString:'%s: %s', tooltipLocation:'sw', useAxesFormatters:false } }); } else { // Reset chart to null to avoid redraw of a non existing chart in resize callback. window.pieChart = null; } }; $rootScope.$on("monthsLoadedEvent", function(event, args){ $scope.loadEntries(args.account, args.month); }); $scope.$on("entriesLoadedEvent", function(event, args) { $scope.entriesLoaded(args.entries); }); }; // Default AJAX error handler. //$(document).ajaxError(function(event, xhr, settings) { // message("error", xhr.statusText, xhr.responseText); //});
date:%s
sold:%s