diff --git a/accountant/models/operations.py b/accountant/models/operations.py index ee9ce69..08e3553 100644 --- a/accountant/models/operations.py +++ b/accountant/models/operations.py @@ -95,41 +95,59 @@ class Operation(db.Model): self.canceled = canceled @classmethod - def query(cls, begin=None, end=None): + def query(cls): """Return query for this class.""" - # We have to use a join because the sold is not computed from the - # begining. - base_query = db.session.query( - cls.id, - cls.sold - ).subquery() query = db.session.query( - cls.id, - cls.operation_date, - cls.label, - cls.value, - base_query.c.sold, - cls.category, - cls.scheduled_operation_id, - cls.account_id, - cls.pointed, - cls.confirmed, - cls.canceled - ).join( - base_query, base_query.c.id == cls.id - ).order_by( - db.desc(cls.operation_date), - cls.value, - cls.label, + cls + ) + + return query + + @classmethod + def get_with_balance(cls, account_id, begin=None, end=None): + """Get operations with cumulated balance for a speciific account, + optionally for a specific time period.""" + query = db.session.query( + *cls.__table__.columns.keys(), + db.func.sum( + cls.value + ).filter( + db.not_(cls.canceled) + ).over( + partition_by=[cls.account_id], + order_by=[ + cls.operation_date, + db.desc(cls.value), + db.desc(cls.label), + cls.id + ] + ).label("balance") + ).filter( + cls.account_id == account_id ) if begin: - query = query.filter(cls.operation_date >= str(begin)) + base_query = query.subquery() + + query = db.session.query( + base_query + ).filter( + cls.operation_date >= str(begin) + ).join( + cls, base_query.c.id == cls.id + ) if end: query = query.filter(cls.operation_date <= str(end)) + query = query.order_by( + cls.operation_date, + db.desc(cls.value), + db.desc(cls.label), + cls.id + ) + return query @classmethod diff --git a/accountant/views/operations.py b/accountant/views/operations.py index b21c513..0679c15 100644 --- a/accountant/views/operations.py +++ b/accountant/views/operations.py @@ -7,7 +7,7 @@ import dateutil.parser from flask_jwt_extended import jwt_required from flask_restplus import Namespace, Resource, fields -from ..models import db +from ..models import db, result_as_dicts from ..models.accounts import Account from ..models.operations import Operation @@ -21,7 +21,7 @@ operation_model = ns.model('Operation', { default=None, readonly=True, description='Id of the operation'), - 'operation_date': fields.DateTime( + 'operation_date': fields.Date( dt_format='iso8601', required=True, description='Date of the operation'), @@ -52,11 +52,11 @@ operation_model = ns.model('Operation', { description='Canceled status of the operation (for a scheduled one)') }) -operation_with_sold_model = ns.clone( - 'OperationWithSold', operation_model, { - 'sold': fields.Float( +operation_with_balance_model = ns.clone( + 'OperationWithBalance', operation_model, { + 'balance': fields.Float( readonly=True, - description='Cumulated sold' + description='Cumulated balance' ), } ) @@ -99,21 +99,22 @@ account_range_parser.add_argument( class OperationListResource(Resource): """Resource to handle operation lists.""" - @ns.response(200, 'OK', [operation_with_sold_model]) - @ns.expect(parser=account_range_parser) - @ns.marshal_list_with(operation_with_sold_model) - @jwt_required + @ns.response(200, 'OK', [operation_with_balance_model]) + @ns.expect(account_range_parser) + @ns.marshal_list_with(operation_with_balance_model) + #@jwt_required def get(self): - """Get operations with solds for a specific account.""" + """Get operations with cumulated balance for a specific account.""" data = account_range_parser.parse_args() - return Operation.query( - begin=data['begin'], - end=data['end'] - ).filter( - Operation.account_id == data['account_id'] - ).all(), 200 + account_id = data['account_id'] + begin = data['begin'] + end = data['end'] + + return list(result_as_dicts( + Operation.get_with_balance(account_id, begin=begin, end=end) + )), 200 @ns.response(201, 'Operation created', operation_model) @ns.response(404, 'Account not found')