diff --git a/accountant/api/model/accounts.py b/accountant/api/model/accounts.py index 5d79d68..1bb7d6c 100644 --- a/accountant/api/model/accounts.py +++ b/accountant/api/model/accounts.py @@ -14,8 +14,12 @@ You should have received a copy of the GNU Affero General Public License along with Accountant. If not, see . """ +from sqlalchemy import func, case, cast + from accountant import db +from .entries import Entry + class Account(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -25,3 +29,18 @@ class Account(db.Model): def __init__(self, name, authorized_overdraft): self.name = name self.authorized_overdraft = authorized_overdraft + + @classmethod + def get_accounts(cls, session): + query = session.query( + cls.id.label("id"), + cls.name.label("name"), + cls.authorized_overdraft.label("authorized_overdraft"), + func.sum(Entry.value).label("future"), + func.sum(case([(Entry.pointed, Entry.value,)], + else_=cast(0, db.Numeric(15, 2)))).label("pointed"), + func.sum(case([(Entry.operation_date < func.now(), Entry.value,)], + else_=cast(0, db.Numeric(15, 2)))).label("current") + ).outerjoin(Entry).group_by(cls.id).order_by(cls.id) + + return query diff --git a/accountant/api/model/entries.py b/accountant/api/model/entries.py index a459903..df64e8d 100644 --- a/accountant/api/model/entries.py +++ b/accountant/api/model/entries.py @@ -14,10 +14,9 @@ You should have received a copy of the GNU Affero General Public License along with Accountant. If not, see . """ -from accountant import db -from .accounts import Account -from .scheduled_operations import ScheduledOperation +from sqlalchemy import distinct, func, cast, extract +from accountant import db class Entry(db.Model): @@ -30,9 +29,9 @@ class Entry(db.Model): scheduled_operation_id = db.Column(db.Integer, db.ForeignKey('scheduled_operation.id')) - account = db.relationship(Account, backref=db.backref('entry', - lazy="dynamic")) - scheduled_operation = db.relationship(ScheduledOperation, + account = db.relationship("Account", backref=db.backref('entry', + lazy="dynamic")) + scheduled_operation = db.relationship("ScheduledOperation", backref=db.backref('entry', lazy="dynamic")) @@ -47,3 +46,19 @@ class Entry(db.Model): self.account_id = account_id self.category = category self.scheduled_operation_id = scheduled_operation_id + + @classmethod + def get_months_for_account(cls, session, account): + if isinstance(account, int) or isinstance(account, str): + account_id = account + else: + account_id = account.id + + query = session.query( + distinct(func.lpad(cast(extract("year", cls.operation_date), + db.String), 4, '0')).label("year"), + func.lpad(cast(extract("month", cls.operation_date), + db.String), 2, '0').label("month") + ).filter(cls.account_id == account_id).order_by("year", "month") + + return query diff --git a/accountant/api/model/operations.py b/accountant/api/model/operations.py index a92b482..3b212fd 100644 --- a/accountant/api/model/operations.py +++ b/accountant/api/model/operations.py @@ -14,6 +14,8 @@ You should have received a copy of the GNU Affero General Public License along with Accountant. If not, see . """ +from sqlalchemy import func, case, desc + from accountant import db from .accounts import Account @@ -44,3 +46,52 @@ class Operation(db.Model): self.value = value self.account_id = account_id self.category = category + + @classmethod + def get_for_account_and_month(cls, session, account, year, month): + if isinstance(account, int) or isinstance(account, str): + account_id = account + else: + account_id = account.id + + base_query = session.query( + cls, + case( + whens={cls.canceled: None}, + else_=func.sum(cls.value).over( + partition_by="canceled", + order_by=["operation_date", desc("value"), desc("label")]) + ).label("sold") + ).filter(cls.account_id == account_id).order_by( + desc(cls.operation_date), + cls.value, + cls.label, + ).subquery() + + query = session.query(base_query).select_from(base_query) + query = query.filter(func.date_trunc( + 'month', base_query.c.operation_date) == "%s-%s-01" % (year, month)) + + return query + + @classmethod + def get_account_status(cls, session, account, year, month): + if isinstance(account, int) or isinstance(account, str): + account_id = account + else: + account_id = account.id + + query = session.query( + func.sum(case([(func.sign(cls.value) == -1, cls.value)], + else_=0)).label("expenses"), + func.sum(case([(func.sign(cls.value) == 1, cls.value)], + else_=0)).label("revenues"), + func.sum(cls.value).label("balance") + ).filter( + cls.account_id == account_id + ).filter( + func.date_trunc('month', + cls.operation_date) == "%s-%s-01" % (year, month) + ).group_by(cls.account_id) + + return query diff --git a/accountant/api/model/scheduled_operations.py b/accountant/api/model/scheduled_operations.py index b0a837d..4eaad97 100644 --- a/accountant/api/model/scheduled_operations.py +++ b/accountant/api/model/scheduled_operations.py @@ -14,6 +14,8 @@ You should have received a copy of the GNU Affero General Public License along with Accountant. If not, see . """ +from sqlalchemy import desc + from accountant import db from .accounts import Account @@ -44,3 +46,24 @@ class ScheduledOperation(db.Model): self.value = value self.account_id = account_id self.category = category + + @classmethod + def get_scheduled_operations_for_account(cls, session, account): + if isinstance(account, int) or isinstance(account, str): + account_id = account + else: + account_id = account.id + + query = session.query( + cls + ).select_from( + session.query(cls) + .filter(cls.account_id == account_id) + .order_by( + desc(cls.day), + cls.value, + cls.label, + ).subquery() + ) + + return query diff --git a/accountant/api/views/accounts.py b/accountant/api/views/accounts.py index 4c6951f..46dde6f 100644 --- a/accountant/api/views/accounts.py +++ b/accountant/api/views/accounts.py @@ -16,11 +16,9 @@ """ from flask import json, request -from sqlalchemy import func, case, cast, extract, distinct - from forms.accounts import AccountForm -from accountant import db, session_scope +from accountant import session_scope from . import auth from .. import api @@ -37,16 +35,7 @@ def get_accounts(): Returns accounts with their solds. """ with session_scope() as session: - query = session.query( - Account.id.label("id"), - Account.name.label("name"), - Account.authorized_overdraft.label("authorized_overdraft"), - func.sum(Entry.value).label("future"), - func.sum(case([(Entry.pointed, Entry.value,)], - else_=cast(0, db.Numeric(15, 2)))).label("pointed"), - func.sum(case([(Entry.operation_date < func.now(), Entry.value,)], - else_=cast(0, db.Numeric(15, 2)))).label("current") - ).outerjoin(Entry).group_by(Account.id).order_by(Account.id) + query = Account.get_accounts(session) return json.dumps([{ "id": i.id, @@ -62,19 +51,7 @@ def get_accounts(): @auth.login_required def get_account_status(account_id, year, month): with session_scope() as session: - query = session.query( - func.sum(case([(func.sign(Operation.value) == -1, Operation.value)], - else_=0)).label("expenses"), - func.sum(case([(func.sign(Operation.value) == 1, Operation.value)], - else_=0)).label("revenues"), - func.sum(Operation.value).label("balance") - ).filter( - Operation.account_id == account_id - ).filter( - func.date_trunc('month', - Operation.operation_date) == "%s-%s-01" % (year, - month) - ).group_by(Operation.account_id) + query = Operation.get_account_status(session, account_id, year, month) if query.count() == 1: result = query.one() @@ -97,12 +74,7 @@ def get_account_status(account_id, year, month): @auth.login_required def get_months(account_id): with session_scope() as session: - query = session.query( - distinct(func.lpad(cast(extract("year", Entry.operation_date), - db.String), 4, '0')).label("year"), - func.lpad(cast(extract("month", Entry.operation_date), - db.String), 2, '0').label("month") - ).filter(Entry.account_id == account_id).order_by("year", "month") + query = Entry.get_months_for_account(session, account_id) return json.dumps([{ "year": i.year, @@ -129,10 +101,7 @@ def update_account(account_id): if account_form.validate(): with session_scope() as session: - query = session.query(Account) - query = query.filter(Account.id == account_id) - - account = query.first() + account = session.query(Account).get(account_id) account.name = request.json['name'] account.authorized_overdraft = request.json['authorized_overdraft'] diff --git a/accountant/api/views/entries.py b/accountant/api/views/entries.py index 370ce2f..994a793 100644 --- a/accountant/api/views/entries.py +++ b/accountant/api/views/entries.py @@ -16,8 +16,6 @@ """ from flask import json, request -from sqlalchemy import func, case, desc - from accountant import session_scope from .. import api @@ -32,23 +30,8 @@ def get_entries(account_id, year, month): Return entries for an account, year, and month. """ with session_scope() as session: - base_query = session.query( - Operation, - case( - whens={Operation.canceled: None}, - else_=func.sum(Operation.value).over( - partition_by="canceled", - order_by=["operation_date", desc("value"), desc("label")]) - ).label("sold") - ).filter(Operation.account_id == account_id).order_by( - desc(Operation.operation_date), - Operation.value, - Operation.label, - ).subquery() - - query = session.query(base_query).select_from(base_query) - query = query.filter(func.date_trunc( - 'month', base_query.c.operation_date) == "%s-%s-01" % (year, month)) + query = Operation.get_for_account_and_month(session, account_id, year, + month) return json.dumps([{ "id": i.id, @@ -85,7 +68,7 @@ def add_entry(): @api.route("/entries/", methods=["PUT"]) def update_entry(entry_id): with session_scope() as session: - entry = session.query(Entry).filter(Entry.id == entry_id).first() + entry = session.query(Entry).get(entry_id) entry.id = entry_id entry.operation_date = request.json['operation_date'] diff --git a/accountant/api/views/scheduled_operations.py b/accountant/api/views/scheduled_operations.py index 0df8042..26bde80 100644 --- a/accountant/api/views/scheduled_operations.py +++ b/accountant/api/views/scheduled_operations.py @@ -16,8 +16,6 @@ """ from flask import json, request -from sqlalchemy import desc - from accountant import session_scope from .. import api @@ -30,17 +28,8 @@ def get_scheduled_operations(account_id): Return entries for an account, year, and month. """ with session_scope() as session: - query = session.query( - ScheduledOperation - ).select_from( - session.query(ScheduledOperation) - .filter(ScheduledOperation.account_id == account_id) - .order_by( - desc(ScheduledOperation.day), - ScheduledOperation.value, - ScheduledOperation.label, - ).subquery() - ) + query = ScheduledOperation.get_scheduled_operations_for_account( + session, account_id) return json.dumps([{ "id": i.id,