patch: better overview for datas
This commit is contained in:
parent
d52e5a63d5
commit
fd75f419e8
1
.gitignore
vendored
1
.gitignore
vendored
@ -131,3 +131,4 @@ dmypy.json
|
|||||||
# Misc
|
# Misc
|
||||||
config.yaml
|
config.yaml
|
||||||
scans.json
|
scans.json
|
||||||
|
test.*
|
||||||
15
server/src/app/templates/base.html
Normal file
15
server/src/app/templates/base.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
server/src/app/templates/overview.html
Normal file
27
server/src/app/templates/overview.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% for user, dates in results.items() %}
|
||||||
|
<div class="row">
|
||||||
|
<h3>{{ user }}: {{ dates.sum }}€</h3>
|
||||||
|
{% for date, items in dates.items() %}
|
||||||
|
{% if date != "sum" %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-1"></div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>{{ date }}</h4>
|
||||||
|
{% for item, values in items.items() %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-1"></div>
|
||||||
|
<div class="col">
|
||||||
|
{{ values.0 }}x {{ item }} je {{ values.1 }}€
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from app import app
|
from app import app
|
||||||
from app.database import Database
|
from app.database import Database
|
||||||
from flask import abort, request
|
from flask import abort, request, render_template
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
from os import makedirs
|
from os import makedirs
|
||||||
from os.path import dirname, exists
|
from os.path import dirname, exists
|
||||||
@ -75,7 +75,10 @@ def get_monthly_report_from_user(user: str = None, year: int = None, month: int
|
|||||||
result_dict = group_results(results)
|
result_dict = group_results(results)
|
||||||
else:
|
else:
|
||||||
result_dict = {}
|
result_dict = {}
|
||||||
return jsonify(result_dict)
|
if request.content_type == "application/json":
|
||||||
|
return jsonify(result_dict)
|
||||||
|
else:
|
||||||
|
return render_template("overview.html", results=result_dict)
|
||||||
|
|
||||||
|
|
||||||
def group_results(results: tuple) -> dict:
|
def group_results(results: tuple) -> dict:
|
||||||
@ -83,10 +86,11 @@ def group_results(results: tuple) -> dict:
|
|||||||
LOGGER.debug("Grouping...")
|
LOGGER.debug("Grouping...")
|
||||||
for result in results:
|
for result in results:
|
||||||
if result[0] not in result_dict:
|
if result[0] not in result_dict:
|
||||||
result_dict[result[0]] = {}
|
result_dict[result[0]] = {"sum": 0}
|
||||||
if str(result[1]) not in result_dict[result[0]]:
|
if str(result[1]) not in result_dict[result[0]]:
|
||||||
result_dict[result[0]][str(result[1])] = {}
|
result_dict[result[0]][str(result[1])] = {}
|
||||||
result_dict[result[0]][str(result[1])][result[2]] = (
|
result_dict[result[0]][str(result[1])][result[2]] = (
|
||||||
result[3], result[4])
|
result[3], result[4])
|
||||||
|
result_dict[result[0]]["sum"] += (result[3] * (result[4] * 100)) / 100
|
||||||
LOGGER.debug("Grouped.")
|
LOGGER.debug("Grouped.")
|
||||||
return result_dict
|
return result_dict
|
||||||
@ -1,141 +0,0 @@
|
|||||||
from os.path import dirname
|
|
||||||
from psycopg2 import connect as psyconn, ProgrammingError, errors
|
|
||||||
from yaml import safe_load
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
DIR = dirname(__file__) + "/"
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
LOGGER.setLevel(logging.DEBUG)
|
|
||||||
logFormatter = logging.Formatter(
|
|
||||||
"%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s")
|
|
||||||
|
|
||||||
fileHandler = logging.FileHandler(DIR + "../logs/database.log")
|
|
||||||
fileHandler.setFormatter(logFormatter)
|
|
||||||
fileHandler.setLevel(logging.INFO)
|
|
||||||
LOGGER.addHandler(fileHandler)
|
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
|
||||||
consoleHandler.setLevel(logging.DEBUG)
|
|
||||||
LOGGER.addHandler(consoleHandler)
|
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def connect(self, **kwargs):
|
|
||||||
with open(DIR + 'config.yaml', 'r') as file:
|
|
||||||
data = safe_load(file)['database']
|
|
||||||
LOGGER.debug('Merging passed arguments with default arguments.')
|
|
||||||
for key, value in data.items():
|
|
||||||
if key not in kwargs or not kwargs[key]:
|
|
||||||
kwargs[key] = value
|
|
||||||
LOGGER.info('Connecting to Database.')
|
|
||||||
self.conn = psyconn(host=kwargs["host"], port=kwargs["port"], dbname=kwargs["database"],
|
|
||||||
user=kwargs["user"], password=kwargs["password"])
|
|
||||||
self.conn.autocommit = True
|
|
||||||
|
|
||||||
def test_connection(self):
|
|
||||||
if not hasattr(self, "conn"):
|
|
||||||
LOGGER.info("Connection was not set, setting...")
|
|
||||||
self.connect()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
with self.conn.cursor() as cursor:
|
|
||||||
try:
|
|
||||||
cursor.execute("SELECT 1;")
|
|
||||||
cursor.fetchall()
|
|
||||||
except:
|
|
||||||
LOGGER.warn(
|
|
||||||
'Connection seem to timed out, reconnecting...')
|
|
||||||
self.connect()
|
|
||||||
except:
|
|
||||||
LOGGER.warn('Connection seem to timed out, reconnecting...')
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
def connectionpersistence(func):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
self = args[0]
|
|
||||||
self.test_connection()
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
@connectionpersistence
|
|
||||||
def get_user(self, **kwargs):
|
|
||||||
result = ()
|
|
||||||
if 'login' in kwargs:
|
|
||||||
query = "SELECT login FROM users WHERE login = %(login)s;"
|
|
||||||
with self.conn.cursor() as cursor:
|
|
||||||
cursor.execute(query, kwargs)
|
|
||||||
try:
|
|
||||||
result = cursor.fetchall()
|
|
||||||
except ProgrammingError as e:
|
|
||||||
LOGGER.exception("")
|
|
||||||
except Exception as e:
|
|
||||||
LOGGER.exception("")
|
|
||||||
return result
|
|
||||||
|
|
||||||
@connectionpersistence
|
|
||||||
def get_report(self, **kwargs) -> list:
|
|
||||||
query = "SELECT u.name, bp.date, i.name, bp.amount, bp.price FROM bought_with_prices bp INNER JOIN items i ON bp.item = i.id INNER JOIN users u ON bp.user = u.login"
|
|
||||||
if kwargs:
|
|
||||||
query += " WHERE "
|
|
||||||
tempquery = []
|
|
||||||
if "user" in kwargs and kwargs['user']:
|
|
||||||
tempquery.append(f"bp.user = '{kwargs['user']}'")
|
|
||||||
if "year" in kwargs and kwargs['year']:
|
|
||||||
tempstring = "bp.date BETWEEN "
|
|
||||||
if "month" in kwargs and kwargs['month']:
|
|
||||||
tempstring += f"'{kwargs['year']}-{kwargs['month']}-01' AND "
|
|
||||||
tempstring += f"'{kwargs['year']+1}-01-01'" if kwargs['month'] == 12 else f"'{kwargs['year']}-{kwargs['month']+1}-01'"
|
|
||||||
else:
|
|
||||||
tempstring += f"'{kwargs['year']}-01-01' AND '{kwargs['year']+1}-01-01'"
|
|
||||||
tempstring += "::date - INTERVAL '1' DAY"
|
|
||||||
tempquery.append(tempstring)
|
|
||||||
query += " AND ".join(tempquery)
|
|
||||||
query += " ORDER BY u.name, bp.date, i.name ASC;"
|
|
||||||
LOGGER.debug(f"Executing query: {query}")
|
|
||||||
result = []
|
|
||||||
with self.conn.cursor() as cursor:
|
|
||||||
cursor.execute(query)
|
|
||||||
try:
|
|
||||||
result = cursor.fetchall()
|
|
||||||
except ProgrammingError as e:
|
|
||||||
LOGGER.exception("")
|
|
||||||
except Exception as e:
|
|
||||||
LOGGER.exception("")
|
|
||||||
return result
|
|
||||||
|
|
||||||
@connectionpersistence
|
|
||||||
def insert_bought_items(self, user: str, items: dict, date: str = None):
|
|
||||||
temp = ['"user", item, amount', "%(user)s, %(item)s, %(amount)s",
|
|
||||||
"bought.user = %(user)s AND bought.item = %(item)s AND bought.date = " + ("%(date)s" if date else "NOW()::date")]
|
|
||||||
if date:
|
|
||||||
temp[0] += ", date"
|
|
||||||
temp[1] += ", %(date)s"
|
|
||||||
values = [{'user': user, 'item': int(key), 'amount': value, 'date': date} for key, value in items.items()]
|
|
||||||
else:
|
|
||||||
values = [{'user': user, 'item': int(key), 'amount': value} for key, value in items.items()]
|
|
||||||
query = f"INSERT INTO bought({temp[0]}) VALUES({temp[1]}) ON CONFLICT ON CONSTRAINT bought_user_item_date DO UPDATE SET amount = bought.amount + %(amount)s WHERE {temp[2]};"
|
|
||||||
with self.conn.cursor() as cursor:
|
|
||||||
failed = {}
|
|
||||||
for value in values:
|
|
||||||
try:
|
|
||||||
cursor.execute(query, value)
|
|
||||||
except errors.ForeignKeyViolation as e:
|
|
||||||
if failed:
|
|
||||||
failed['items'][value['item']] = value['amount']
|
|
||||||
else:
|
|
||||||
failed = {'user': user, 'items': {value['item']: value['amount']}}
|
|
||||||
if date:
|
|
||||||
failed['date'] = date
|
|
||||||
LOGGER.exception("")
|
|
||||||
except Exception as e:
|
|
||||||
LOGGER.exception("")
|
|
||||||
return failed
|
|
||||||
|
|
||||||
def __delete__(self):
|
|
||||||
self.conn.close()
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
from database import Database
|
|
||||||
from flask import Flask, abort, request
|
|
||||||
from flask.json import jsonify
|
|
||||||
from gevent.pywsgi import WSGIServer
|
|
||||||
from os import makedirs
|
|
||||||
from os.path import dirname, exists
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
DIR = dirname(__file__) + "/"
|
|
||||||
|
|
||||||
if not exists(DIR + "../logs"):
|
|
||||||
makedirs(DIR + "../logs")
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
LOGGER.setLevel(logging.DEBUG)
|
|
||||||
logFormatter = logging.Formatter(
|
|
||||||
"%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s")
|
|
||||||
|
|
||||||
fileHandler = logging.FileHandler(DIR + "../logs/server.log")
|
|
||||||
fileHandler.setFormatter(logFormatter)
|
|
||||||
fileHandler.setLevel(logging.INFO)
|
|
||||||
LOGGER.addHandler(fileHandler)
|
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
|
||||||
consoleHandler.setLevel(logging.DEBUG)
|
|
||||||
LOGGER.addHandler(consoleHandler)
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
DATABASE = Database()
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return "<h1>Hello, World!</h>", 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/scan2kasse/login')
|
|
||||||
def login():
|
|
||||||
if not request.json or 'login' not in request.json:
|
|
||||||
abort(400)
|
|
||||||
if not DATABASE.get_user(login = request.json['login']):
|
|
||||||
abort(403)
|
|
||||||
return jsonify({}), 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/scan2kasse/insert', methods=['POST'])
|
|
||||||
def insert():
|
|
||||||
match request.json:
|
|
||||||
case {'user': user, 'items': items, 'date': date}:
|
|
||||||
failed = DATABASE.insert_bought_items(user, items, date)
|
|
||||||
if failed:
|
|
||||||
return jsonify(failed), 400
|
|
||||||
return jsonify({'inserted': True}), 201
|
|
||||||
case {'user': user, 'items': items}:
|
|
||||||
failed = DATABASE.insert_bought_items(user, items)
|
|
||||||
if failed:
|
|
||||||
return jsonify(failed), 400
|
|
||||||
return jsonify({'inserted': True}), 201
|
|
||||||
case _:
|
|
||||||
abort(400)
|
|
||||||
|
|
||||||
@app.route('/scan2kasse/<string:user>', methods=['GET'])
|
|
||||||
@app.route('/scan2kasse/<int:year>/<int:month>', methods=['GET'])
|
|
||||||
@app.route('/scan2kasse/<string:user>/<int:year>', methods=['GET'])
|
|
||||||
@app.route('/scan2kasse/<string:user>/<int:year>/<int:month>', methods=['GET'])
|
|
||||||
def get_monthly_report_from_user(user: str = None, year: int = None, month: int = None):
|
|
||||||
if month and (month > 12 or month < 1):
|
|
||||||
abort(400)
|
|
||||||
LOGGER.info("Getting results.")
|
|
||||||
results = DATABASE.get_report(user=user, year=year, month=month)
|
|
||||||
LOGGER.debug(f"Results received: {results}")
|
|
||||||
if results:
|
|
||||||
result_dict = group_results(results)
|
|
||||||
else:
|
|
||||||
result_dict = {}
|
|
||||||
return jsonify(result_dict)
|
|
||||||
|
|
||||||
|
|
||||||
def group_results(results: tuple) -> dict:
|
|
||||||
result_dict = {}
|
|
||||||
LOGGER.debug("Grouping...")
|
|
||||||
for result in results:
|
|
||||||
if result[0] not in result_dict:
|
|
||||||
result_dict[result[0]] = {}
|
|
||||||
if str(result[1]) not in result_dict[result[0]]:
|
|
||||||
result_dict[result[0]][str(result[1])] = {}
|
|
||||||
result_dict[result[0]][str(result[1])][result[2]] = (
|
|
||||||
result[3], result[4])
|
|
||||||
LOGGER.debug("Grouped.")
|
|
||||||
return result_dict
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
http_server = WSGIServer(('', 5000), app)
|
|
||||||
http_server.serve_forever()
|
|
||||||
Loading…
x
Reference in New Issue
Block a user