2015-07-18 10:03:43 +02:00

505 lines
14 KiB
JavaScript

/*
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 <http://www.gnu.org/licenses/>.
*/
accountantApp
.factory("Entries", [ "$resource", function($resource) {
return $resource(
"/api/entries/:id", {
id: "@id"
}
);
}])
.controller(
"EntryController", [
"$scope", "$http", "$rootScope", "$filter", "$routeParams", "Entries",
function($scope, $http, $rootScope, $filter, $routeParams, 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.account = null;
// Function to reset the new entry.
$scope.resetNewEntry = function() {
// The new entry.
$scope.newEntry = new Entries({});
};
$scope.resetNewEntry();
$scope.setExtremes = function(e) {
begin = moment.utc(e.min);
end = moment.utc(e.max);
$scope.selectRange(begin, end);
};
$scope.selectRange = function(begin, end) {
$scope.begin = begin;
$scope.end = end;
$scope.$emit("rangeSelectedEvent", {begin: begin, end: end});
};
// Configure pie chart for categories.
$scope.categoriesChartConfig = {
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;
},
}
}]
};
// Configure chart for entries.
$scope.soldChartConfig = {
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: $scope.setExtremes
}
},
yAxis: {
plotLines: [{
color: "orange",
width: 2,
value: 0.0
}, {
color: "red",
width: 2,
value: 0.0
}]
},
useHighStocks: true
};
// Load categories, mainly to populate the pie chart.
$scope.loadCategories = function() {
$scope.categoriesChartConfig.loading = true;
$http.get("/api/categories", {
params: {
account: $scope.account.id,
begin: $scope.begin.format('YYYY-MM-DD'),
end: $scope.end.format('YYYY-MM-DD')
}
}).success(function(data) {
var expenses = [], revenues = [];
var colors = Highcharts.getOptions().colors;
var config = $scope.categoriesChartConfig;
angular.forEach(angular.fromJson(data), function(category) {
brightness = 0.2;
expenses.push({
name: category.category,
y: -category.expenses,
color: Highcharts.Color(config.series[0].data[1].color).brighten(brightness).get()
});
revenues.push({
name: category.category,
y: category.revenues,
color: Highcharts.Color(config.series[0].data[0].color).brighten(brightness).get()
});
});
// Note: expenses and revenues must be in the same order than in series[0].
config.series[1].data = revenues.concat(expenses);
$scope.categoriesChartConfig.loading = false;
$scope.loadSolds();
});
};
$scope.loadSolds = function() {
$scope.soldChartConfig.loading = true;
$http.get("/api/solds", {
params: {
account: $scope.account.id,
}
}).success(function(data) {
var config = $scope.soldChartConfig;
config.series[0].data = [];
angular.forEach(data, function(entry) {
config.series[0].data.push([
moment.utc(entry.operation_date).valueOf(),
entry.open, entry.high, entry.low, entry.close
]);
});
$scope.soldChartConfig.loading = false;
$scope.loadEntries();
});
};
/*
* Hook on account selected event to display account status.
*/
$rootScope.$on("accountSelectedEvent", function(event, args) {
$scope.getAccountStatus($routeParams.accountId);
});
$rootScope.$on("rangeSelectedEvent", function(event, args) {
$scope.getAccountStatus($routeParams.accountId);
});
$scope.getAccountStatus = function(accountId) {
$scope.categoriesChartConfig.loading = true;
$http.get("/api/accounts/" + accountId, {
params: {
begin: $scope.begin.format('YYYY-MM-DD'),
end: $scope.end.format('YYYY-MM-DD')
}
}).success(function(account) {
$scope.account = account;
$scope.categoriesChartConfig.loading = false;
$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;
$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.
$scope.entryRowClass = function(entry) {
if($scope.isSaved(entry)) {
cssclass="";
} else {
cssclass="italic";
}
if(entry.canceled) {
cssclass += " stroke";
}
if(entry.sold < $scope.account.authorized_overdraft) {
cssclass += " danger";
} else if (entry.sold < 0) {
cssclass += " warning";
}
return cssclass;
};
// Returns the CSS class for an entry sold.
$scope.entryValueClass = function(sold) {
if(sold && sold < $scope.account.authorized_overdraft) {
return 'text-danger';
} else if (sold && sold < 0) {
return 'text-warning';
}
};
// Starts editing an entry
$scope.editEntry = function(entry) {
// Enter edit state.
entry.confirmed=true;
entry.state='edit';
};
// 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.state || entry.state === 'display';
};
// Returns true if the entry is a scheduled one.
$scope.isSaved = function(entry) {
return entry.confirmed;
};
// Cancel current editing entry or clears field if a new one.
$scope.cancelEditEntry = function(entry) {
sold = entry.sold;
entry.$get(function(entry) {
entry.sold = sold;
});
};
// Points an entry.
$scope.pointEntry = function(entry) {
entry.confirmed = true;
entry.pointed = !entry.pointed;
$scope.saveEntry(entry);
};
// Confirm an entry.
$scope.confirmEntry = function(entry) {
entry.confirmed = true;
$scope.saveEntry(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) {
if(!entry.account_id) {
entry.account_id = $scope.account.id;
}
sold = entry.sold;
// Ajax call to save an entry
entry.$save(function(data) {
data.sold = sold;
// Send the "entry saved" event.
$scope.$emit("entrySavedEvent", entry);
});
};
$rootScope.$on("entrySavedEvent", function(e, entry) {
$scope.getAccountStatus($routeParams.accountId);
});
$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({
keyboard: false,
});
};
$scope.hideRemoveEntryPopup = function() {
$scope.removingEntry = null;
$("#remove_entry").modal("hide");
};
// Removes an entry.
$scope.confirmRemoveEntry = function() {
// Cancel current editing.
if ($scope.removingEntry) {
$scope.removingEntry.$delete(function (result) {
// Send the "entry removed" event.
$scope.$emit("entryRemovedEvent", $scope.removingEntry);
$scope.hideRemoveEntryPopup();
});
}
};
$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();
}
};
$scope.getAccountStatus($routeParams.accountId);
}]);