flaschengeist/flaschengeist/plugins/balance/balance_controller.py

157 lines
5.1 KiB
Python

# German: Soll -> Abgang vom Konto
# Haben -> Zugang aufs Konto
# English: Debit -> from account
# Credit -> to account
from sqlalchemy import func
from datetime import datetime
from werkzeug.exceptions import BadRequest, NotFound, Conflict
from flaschengeist.database import db
from flaschengeist.models.user import User
from .models import Transaction
from . import permissions, BalancePlugin
__attribute_limit = "balance_limit"
def set_limit(user: User, limit: float, override=True):
if override or not user.has_attribute(__attribute_limit):
user.set_attribute(__attribute_limit, limit)
db.session.commit()
def get_limit(user: User) -> float:
return user.get_attribute(__attribute_limit, default=None)
def get_balance(user, start: datetime = None, end: datetime = None):
query = db.session.query(func.sum(Transaction.amount))
if start:
query = query.filter(start <= Transaction.time)
if end:
query = query.filter(Transaction.time <= end)
credit = query.filter(Transaction.receiver_ == user).scalar() or 0
debit = query.filter(Transaction.sender_ == user).scalar() or 0
return credit, debit, credit - debit
def get_balances(start: datetime = None, end: datetime = None):
debit = db.session.query(Transaction.sender_id, func.sum(Transaction.amount)).filter(Transaction.sender_ != None)
credit = db.session.query(Transaction.receiver_id, func.sum(Transaction.amount)).filter(
Transaction.receiver_ != None
)
if start:
debit = debit.filter(start <= Transaction.time)
credit = credit.filter(start <= Transaction.time)
if end:
debit = debit.filter(Transaction.time <= end)
credit = credit.filter(Transaction.time <= end)
debit = debit.group_by(Transaction._sender_id).all()
credit = credit.group_by(Transaction._receiver_id).all()
all = {}
for uid, cred in credit:
all[uid] = [cred, 0]
for uid, deb in debit:
all.setdefault(uid, [0, 0])
all[uid][1] = deb
return all
def send(sender: User, receiver, amount: float, author: User):
"""Send credit from one user to an other
Args:
sender: User who sends the amount
receiver: User who receives the amount
amount: Amount to send
author: User authoring this transaction
Returns:
Transaction that was created
Raises:
BadRequest if amount <= 0
"""
if amount <= 0:
raise BadRequest
if sender and sender.has_attribute(__attribute_limit):
if (get_balance(sender)[2] - amount) < sender.get_attribute(__attribute_limit) and not author.has_permission(
permissions.EXCEED_LIMIT
):
raise Conflict("Limit exceeded")
transaction = Transaction(sender_=sender, receiver_=receiver, amount=amount, author_=author)
db.session.add(transaction)
db.session.commit()
if sender is not None and sender.id_ != author.id_:
BalancePlugin.plugin.notify(sender, "Neue Transaktion")
if receiver is not None and receiver.id_ != author.id_:
BalancePlugin.plugin.notify(receiver, "Neue Transaktion")
return transaction
def change_balance(user, amount: float, author):
"""Change balance of user
Args:
user: User to change balance
amount: Amount to change balance
author: User authoring this transaction
"""
sender = user if amount < 0 else None
receiver = user if amount > 0 else None
return send(sender, receiver, abs(amount), author)
def get_transaction(transaction_id) -> Transaction:
transaction = Transaction.query.get(transaction_id)
if not transaction:
raise NotFound
return transaction
def get_transactions(
user, start=None, end=None, limit=None, offset=None, show_reversal=False, show_cancelled=True, descending=False
):
count = None
query = Transaction.query.filter((Transaction.sender_ == user) | (Transaction.receiver_ == user))
if start:
query = query.filter(start <= Transaction.time)
if end:
query = query.filter(Transaction.time <= end)
# Do not show reversals if disabled or cancelled ones are hidden
if not show_reversal or not show_cancelled:
query = query.filter(Transaction.original_ == None)
if not show_cancelled:
query = query.filter(Transaction.reversal_id.is_(None))
if descending:
query = query.order_by(Transaction.time.desc())
else:
query = query.order_by(Transaction.time)
if limit is not None:
count = query.count()
query = query.limit(limit)
if offset is not None:
query = query.offset(offset)
return query.all(), count
def reverse_transaction(transaction: Transaction, author: User):
"""Reverse a transaction
Args:
transaction: Transaction to reverse
author: User that wants the transaction to be reverted
"""
if transaction.reversal_:
raise Conflict
reversal = send(transaction.receiver_, transaction.sender_, transaction.amount, author)
reversal.original_ = transaction
transaction.reversal = reversal
db.session.commit()
return reversal