Separate solds and balance from account model.

This commit is contained in:
Alexis Lahouze 2016-01-13 18:05:46 +01:00
parent 21c98a1dcf
commit e4c0752a2c
5 changed files with 108 additions and 85 deletions

View File

@ -30,8 +30,14 @@ class Account(db.Model):
@classmethod @classmethod
def query(cls, begin=None, end=None): def query(cls, begin=None, end=None):
query = db.session.query( return db.session.query(
Operation.account_id, cls
).order_by(
cls.name
)
def solds(self):
return db.session.query(
db.func.sum(Operation.value).label("future"), db.func.sum(Operation.value).label("future"),
db.func.sum( db.func.sum(
db.case( db.case(
@ -47,14 +53,12 @@ class Account(db.Model):
else_=0 else_=0
) )
).label("current"), ).label("current"),
).group_by( ).filter(
Operation.account_id Operation.account_id == self.id
) ).one()
status_query = query.subquery()
def balance(self, begin, end):
query = db.session.query( query = db.session.query(
Operation.account_id,
db.func.sum( db.func.sum(
db.case( db.case(
[(db.func.sign(Operation.value) == -1, [(db.func.sign(Operation.value) == -1,
@ -70,8 +74,8 @@ class Account(db.Model):
) )
).label("revenues"), ).label("revenues"),
db.func.sum(Operation.value).label("balance") db.func.sum(Operation.value).label("balance")
).group_by( ).filter(
Operation.account_id Operation.account_id == self.id,
) )
if begin: if begin:
@ -80,31 +84,7 @@ class Account(db.Model):
if end: if end:
query = query.filter(Operation.operation_date <= str(end)) query = query.filter(Operation.operation_date <= str(end))
balance_query = query.subquery() return query.one()
query = db.session.query(
cls.id,
cls.name,
cls.authorized_overdraft,
db.func.coalesce(
status_query.c.current, 0).label('current'),
db.func.coalesce(
status_query.c.pointed, 0).label('pointed'),
db.func.coalesce(
status_query.c.future, 0).label('future'),
db.func.coalesce(
balance_query.c.expenses, 0).label('expenses'),
db.func.coalesce(
balance_query.c.revenues, 0).label('revenues'),
db.func.coalesce(
balance_query.c.balance, 0).label('balance'),
).outerjoin(
status_query, status_query.c.account_id == cls.id
).outerjoin(
balance_query, balance_query.c.account_id == cls.id
)
return query.order_by(cls.name)
@db.validates('authorized_overdraft') @db.validates('authorized_overdraft')
def validate_authorized_overdraft(self, key, authorized_overdraft): def validate_authorized_overdraft(self, key, authorized_overdraft):

View File

@ -18,8 +18,6 @@ import dateutil.parser
from flask.ext.restful import Resource, fields, reqparse, marshal_with_field from flask.ext.restful import Resource, fields, reqparse, marshal_with_field
from sqlalchemy.orm.exc import NoResultFound
from accountant import db from accountant import db
from .. import api from .. import api
@ -36,9 +34,15 @@ account_model = {
'id': fields.Integer(default=None), 'id': fields.Integer(default=None),
'name': fields.String, 'name': fields.String,
'authorized_overdraft': fields.Float, 'authorized_overdraft': fields.Float,
}
solds_model = {
'current': fields.Float, 'current': fields.Float,
'pointed': fields.Float, 'pointed': fields.Float,
'future': fields.Float, 'future': fields.Float,
}
balance_model = {
'expenses': fields.Float, 'expenses': fields.Float,
'revenues': fields.Float, 'revenues': fields.Float,
'balance': fields.Float, 'balance': fields.Float,
@ -80,10 +84,8 @@ class AccountListResource(Resource):
# Flush session to have id in account. # Flush session to have id in account.
db.session.flush() db.session.flush()
# Return account with solds. # Return account data.
return Account.query().filter( return account, 201
Account.id == account.id
).one(), 201
class AccountResource(Resource): class AccountResource(Resource):
@ -93,17 +95,13 @@ class AccountResource(Resource):
""" """
Get account. Get account.
""" """
data = date_parser.parse_args() account = Account.query().get(id)
try: if not account:
return Account.query(
**data
).filter(
Account.id == id
).one(), 200
except NoResultFound:
return None, 404 return None, 404
return account, 200
@requires_auth @requires_auth
@marshal_with_field(Object(account_model)) @marshal_with_field(Object(account_model))
def post(self, id): def post(self, id):
@ -113,7 +111,7 @@ class AccountResource(Resource):
or data.id == id) or data.id == id)
# Need to get the object to update it. # Need to get the object to update it.
account = db.session.query(Account).get(id) account = Account.query().get(id)
if not account: if not account:
return None, 404 return None, 404
@ -124,16 +122,14 @@ class AccountResource(Resource):
db.session.merge(account) db.session.merge(account)
# Return account with solds. # Return account.
return Account.query().filter( return account, 200
Account.id == id
).one(), 200
@requires_auth @requires_auth
@marshal_with_field(Object(account_model)) @marshal_with_field(Object(account_model))
def delete(self, id): def delete(self, id):
# Need to get the object to update it. # Need to get the object to update it.
account = db.session.query(Account).get(id) account = Account.query().get(id)
if not account: if not account:
return None, 404 return None, 404
@ -147,11 +143,47 @@ api.add_resource(AccountListResource, '/account')
api.add_resource(AccountResource, '/account/<int:id>') api.add_resource(AccountResource, '/account/<int:id>')
class SoldsResource(Resource):
@requires_auth
@marshal_with_field(Object(solds_model))
def get(self, id):
"""
Get solds for a specific account and date range.
"""
account = Account.query().get(id)
if not account:
return None, 404
# Note: if we don't pass the code, the result is seen as a tuple and
# causes error on marshalling.
return account.solds(), 200
range_parser = reqparse.RequestParser() range_parser = reqparse.RequestParser()
range_parser.add_argument('begin', type=lambda a: dateutil.parser.parse(a)) range_parser.add_argument('begin', type=lambda a: dateutil.parser.parse(a))
range_parser.add_argument('end', type=lambda a: dateutil.parser.parse(a)) range_parser.add_argument('end', type=lambda a: dateutil.parser.parse(a))
class BalanceResource(Resource):
@requires_auth
@marshal_with_field(Object(balance_model))
def get(self, id):
"""
Get account balance for a specific date range.
"""
account = Account.query().get(id)
if not account:
return None, 404
data = range_parser.parse_args()
# Note: if we don't pass the code, the result is seen as a tuple and
# causes error on marshalling.
return account.balance(**data), 200
category_model = { category_model = {
'category': fields.String, 'category': fields.String,
'expenses': fields.Float, 'expenses': fields.Float,
@ -186,5 +218,7 @@ class OHLCResource(Resource):
return Operation.get_ohlc_per_day_for_range(id, **data).all() return Operation.get_ohlc_per_day_for_range(id, **data).all()
api.add_resource(SoldsResource, "/account/<int:id>/solds")
api.add_resource(BalanceResource, "/account/<int:id>/balance")
api.add_resource(CategoryResource, "/account/<int:id>/category") api.add_resource(CategoryResource, "/account/<int:id>/category")
api.add_resource(OHLCResource, "/account/<int:id>/ohlc") api.add_resource(OHLCResource, "/account/<int:id>/ohlc")

View File

@ -19,11 +19,19 @@
accountantApp accountantApp
.factory("Account", ["$resource", function($resource) { .factory("Account", ["$resource", function($resource) {
return $resource( var Account = $resource(
"/api/account/:id", { "/api/account/:id", {
id: "@id" id: "@id"
} }
); );
Account.prototype.getSolds = function() {
var Solds = $resource("/api/account/:id/solds", {id: this.id});
this.solds = Solds.get();
};
return Account;
}]) }])
.controller( .controller(

View File

@ -43,13 +43,22 @@ accountantApp
); );
}]) }])
.factory("Balance", [ "$resource", "$routeParams",
function($resource, $routeParams) {
return $resource(
"/api/account/:account_id/balance", {
account_id: $routeParams.accountId
}
);
}])
/* /*
* Controller for category chart. * Controller for category chart.
*/ */
.controller( .controller(
"CategoryChartController", [ "CategoryChartController", [
"$rootScope", "$scope", "$http", "$routeParams", "Category", "$rootScope", "$scope", "$http", "Category", "Balance",
function($rootScope, $scope, $http, $routeParams, Category) { function($rootScope, $scope, $http, Category, Balance) {
var colors = Highcharts.getOptions().colors; var colors = Highcharts.getOptions().colors;
$scope.revenueColor = colors[2]; $scope.revenueColor = colors[2];
@ -150,30 +159,25 @@ accountantApp
}; };
/* /*
* Get account status. * Get account balance.
*/ */
$scope.getAccountStatus = function(begin, end) { $scope.getBalance = function(begin, end) {
$http.get("/api/account/" + $routeParams.accountId, { Balance.get({
params: { begin: begin.format("YYYY-MM-DD"),
begin: begin.format('YYYY-MM-DD'), end: end.format("YYYY-MM-DD")
end: end.format('YYYY-MM-DD') }, function(balance) {
}
}).success(function(account) {
// Emit accountLoadedEvent.
$scope.$emit("accountLoadedEvent", account);
// Update pie chart subtitle with Balance. // Update pie chart subtitle with Balance.
$scope.config.subtitle = { $scope.config.subtitle = {
text: "Balance: " + account.balance text: "Balance: " + balance.balance
}; };
$scope.config.series[0].data = [{ $scope.config.series[0].data = [{
name: "Revenues", name: "Revenues",
y: account.revenues, y: balance.revenues,
color: $scope.revenueColor color: $scope.revenueColor
}, { }, {
name: "Expenses", name: "Expenses",
y: -account.expenses, y: -balance.expenses,
color: $scope.expenseColor, color: $scope.expenseColor,
}]; }];
}); });
@ -182,7 +186,7 @@ accountantApp
// Reload categories and account status on range selection. // Reload categories and account status on range selection.
$rootScope.$on("rangeSelectedEvent", function(e, args) { $rootScope.$on("rangeSelectedEvent", function(e, args) {
$scope.load(args.begin, args.end); $scope.load(args.begin, args.end);
$scope.getAccountStatus(args.begin, args.end); $scope.getBalance(args.begin, args.end);
}); });
}]) }])
@ -329,8 +333,8 @@ accountantApp
*/ */
.controller( .controller(
"OperationController", [ "OperationController", [
"$scope", "$rootScope", "$routeParams", "notify", "Operation", "$scope", "$rootScope", "$routeParams", "notify", "Account", "Operation",
function($scope, $rootScope, $routeParams, notify, Operation) { function($scope, $rootScope, $routeParams, notify, Account, Operation) {
// List of operations. // List of operations.
$scope.operations = []; $scope.operations = [];
@ -442,11 +446,8 @@ accountantApp
); );
}; };
/* $scope.account = Account.get({
* Save account in scope to colorize with authorized overdraft. id: $routeParams.accountId
*/
$rootScope.$on("accountLoadedEvent", function(e, account) {
$scope.account = account;
}); });
/* /*

View File

@ -37,7 +37,7 @@
<tr id="{{ account.id }}" <tr id="{{ account.id }}"
class="form-inline" ng-class="rowClass(account)" class="form-inline" ng-class="rowClass(account)"
ng-repeat="account in accounts"> ng-repeat="account in accounts" ng-init="account.getSolds()">
<td> <td>
<span editable-text="account.name" <span editable-text="account.name"
e-placeholder="Nom du compte" e-placeholder="Nom du compte"
@ -48,14 +48,14 @@
</td> </td>
<td> <td>
<span ng-class="valueClass(account, account.current)"> <span ng-class="valueClass(account, account.solds.current)">
{{ account.current | currency : "€" }} {{ account.solds.current | currency : "€" }}
</span> </span>
</td> </td>
<td> <td>
<span ng-class="valueClass(account, account.pointed)"> <span ng-class="valueClass(account, account.solds.pointed)">
{{ account.pointed | currency : "€" }} {{ account.solds.pointed | currency : "€" }}
</span> </span>
</td> </td>