Separate solds and balance from account model.
This commit is contained in:
parent
21c98a1dcf
commit
e4c0752a2c
@ -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):
|
||||||
|
@ -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")
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user