Restructure project #3
4
conf.py
4
conf.py
|
@ -1,8 +1,8 @@
|
||||||
db_url = 'postgresql://robertem@127.0.0.1/pvvvv'
|
db_url = "postgresql://robertem@127.0.0.1/pvvvv"
|
||||||
quit_allowed = True
|
quit_allowed = True
|
||||||
stop_allowed = False
|
stop_allowed = False
|
||||||
show_tracebacks = True
|
show_tracebacks = True
|
||||||
input_encoding = 'utf8'
|
input_encoding = "utf8"
|
||||||
|
|
||||||
low_credit_warning_limit = -100
|
low_credit_warning_limit = -100
|
||||||
user_recent_transaction_limit = 100
|
user_recent_transaction_limit = 100
|
||||||
|
|
|
@ -3,5 +3,5 @@ from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from dibbler.conf import config
|
from dibbler.conf import config
|
||||||
|
|
||||||
engine = create_engine(config.get('database', 'url'))
|
engine = create_engine(config.get("database", "url"))
|
||||||
Session = sessionmaker(bind=engine)
|
Session = sessionmaker(bind=engine)
|
|
@ -6,20 +6,20 @@ from brother_ql.devicedependent import label_type_specs
|
||||||
|
|
||||||
|
|
||||||
def px2mm(px, dpi=300):
|
def px2mm(px, dpi=300):
|
||||||
return (25.4 * px)/dpi
|
return (25.4 * px) / dpi
|
||||||
|
|
||||||
|
|
||||||
class BrotherLabelWriter(ImageWriter):
|
class BrotherLabelWriter(ImageWriter):
|
||||||
def __init__(self, typ='62', max_height=350, rot=False, text=None):
|
def __init__(self, typ="62", max_height=350, rot=False, text=None):
|
||||||
super(BrotherLabelWriter, self).__init__()
|
super(BrotherLabelWriter, self).__init__()
|
||||||
assert typ in label_type_specs
|
assert typ in label_type_specs
|
||||||
self.rot = rot
|
self.rot = rot
|
||||||
if self.rot:
|
if self.rot:
|
||||||
self._h, self._w = label_type_specs[typ]['dots_printable']
|
self._h, self._w = label_type_specs[typ]["dots_printable"]
|
||||||
if self._w == 0 or self._w > max_height:
|
if self._w == 0 or self._w > max_height:
|
||||||
self._w = min(max_height, self._h / 2)
|
self._w = min(max_height, self._h / 2)
|
||||||
else:
|
else:
|
||||||
self._w, self._h = label_type_specs[typ]['dots_printable']
|
self._w, self._h = label_type_specs[typ]["dots_printable"]
|
||||||
if self._h == 0 or self._h > max_height:
|
if self._h == 0 or self._h > max_height:
|
||||||
self._h = min(max_height, self._w / 2)
|
self._h = min(max_height, self._w / 2)
|
||||||
self._xo = 0.0
|
self._xo = 0.0
|
||||||
|
@ -31,36 +31,40 @@ class BrotherLabelWriter(ImageWriter):
|
||||||
super(BrotherLabelWriter, self)._init(code)
|
super(BrotherLabelWriter, self)._init(code)
|
||||||
|
|
||||||
def calculate_size(self, modules_per_line, number_of_lines, dpi=300):
|
def calculate_size(self, modules_per_line, number_of_lines, dpi=300):
|
||||||
x, y = super(BrotherLabelWriter, self).calculate_size(modules_per_line, number_of_lines, dpi)
|
x, y = super(BrotherLabelWriter, self).calculate_size(
|
||||||
|
modules_per_line, number_of_lines, dpi
|
||||||
|
)
|
||||||
|
|
||||||
self._xo = (px2mm(self._w)-px2mm(x))/2
|
self._xo = (px2mm(self._w) - px2mm(x)) / 2
|
||||||
self._yo = (px2mm(self._h)-px2mm(y))
|
self._yo = px2mm(self._h) - px2mm(y)
|
||||||
assert self._xo >= 0
|
assert self._xo >= 0
|
||||||
assert self._yo >= 0
|
assert self._yo >= 0
|
||||||
|
|
||||||
return int(self._w), int(self._h)
|
return int(self._w), int(self._h)
|
||||||
|
|
||||||
def _paint_module(self, xpos, ypos, width, color):
|
def _paint_module(self, xpos, ypos, width, color):
|
||||||
super(BrotherLabelWriter, self)._paint_module(xpos+self._xo, ypos+self._yo, width, color)
|
super(BrotherLabelWriter, self)._paint_module(
|
||||||
|
xpos + self._xo, ypos + self._yo, width, color
|
||||||
|
)
|
||||||
|
|
||||||
def _paint_text(self, xpos, ypos):
|
def _paint_text(self, xpos, ypos):
|
||||||
super(BrotherLabelWriter, self)._paint_text(xpos+self._xo, ypos+self._yo)
|
super(BrotherLabelWriter, self)._paint_text(xpos + self._xo, ypos + self._yo)
|
||||||
|
|
||||||
def _finish(self):
|
def _finish(self):
|
||||||
if self._title:
|
if self._title:
|
||||||
width = self._w+1
|
width = self._w + 1
|
||||||
height = 0
|
height = 0
|
||||||
max_h = self._h - mm2px(self._yo, self.dpi)
|
max_h = self._h - mm2px(self._yo, self.dpi)
|
||||||
fs = int(max_h / 1.2)
|
fs = int(max_h / 1.2)
|
||||||
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "Stranger back in the Night.ttf")
|
font_path = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)),
|
||||||
|
"Stranger back in the Night.ttf",
|
||||||
|
)
|
||||||
font = ImageFont.truetype(font_path, 10)
|
font = ImageFont.truetype(font_path, 10)
|
||||||
while width > self._w or height > max_h:
|
while width > self._w or height > max_h:
|
||||||
font = ImageFont.truetype(font_path, fs)
|
font = ImageFont.truetype(font_path, fs)
|
||||||
width, height = font.getsize(self._title)
|
width, height = font.getsize(self._title)
|
||||||
fs -= 1
|
fs -= 1
|
||||||
pos = (
|
pos = ((self._w - width) // 2, 0 - (height // 8))
|
||||||
(self._w-width)//2,
|
|
||||||
0 - (height // 8)
|
|
||||||
)
|
|
||||||
self._draw.text(pos, self._title, font=font, fill=self.foreground)
|
self._draw.text(pos, self._title, font=font, fill=self.foreground)
|
||||||
return self._image
|
return self._image
|
||||||
|
|
|
@ -7,31 +7,72 @@ from sqlalchemy import or_, and_
|
||||||
|
|
||||||
from ..models import User, Product
|
from ..models import User, Product
|
||||||
|
|
||||||
|
|
||||||
def search_user(string, session, ignorethisflag=None):
|
def search_user(string, session, ignorethisflag=None):
|
||||||
string = string.lower()
|
string = string.lower()
|
||||||
exact_match = session.query(User).filter(or_(User.name == string, User.card == string, User.rfid == string)).first()
|
exact_match = (
|
||||||
|
session.query(User)
|
||||||
|
.filter(or_(User.name == string, User.card == string, User.rfid == string))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if exact_match:
|
if exact_match:
|
||||||
return exact_match
|
return exact_match
|
||||||
user_list = session.query(User).filter(or_(User.name.ilike(f'%{string}%'),
|
user_list = (
|
||||||
User.card.ilike(f'%{string}%'),
|
session.query(User)
|
||||||
User.rfid.ilike(f'%{string}%'))).all()
|
.filter(
|
||||||
|
or_(
|
||||||
|
User.name.ilike(f"%{string}%"),
|
||||||
|
User.card.ilike(f"%{string}%"),
|
||||||
|
User.rfid.ilike(f"%{string}%"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
return user_list
|
return user_list
|
||||||
|
|
||||||
|
|
||||||
def search_product(string, session, find_hidden_products=True):
|
def search_product(string, session, find_hidden_products=True):
|
||||||
if find_hidden_products:
|
if find_hidden_products:
|
||||||
exact_match = session.query(Product).filter(or_(Product.bar_code == string, Product.name == string)).first()
|
exact_match = (
|
||||||
|
session.query(Product)
|
||||||
|
.filter(or_(Product.bar_code == string, Product.name == string))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
exact_match = session.query(Product).filter(or_(Product.bar_code == string,
|
exact_match = (
|
||||||
and_(Product.name == string, Product.hidden == False))).first()
|
session.query(Product)
|
||||||
|
.filter(
|
||||||
|
or_(
|
||||||
|
Product.bar_code == string,
|
||||||
|
and_(Product.name == string, Product.hidden == False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if exact_match:
|
if exact_match:
|
||||||
return exact_match
|
return exact_match
|
||||||
if find_hidden_products:
|
if find_hidden_products:
|
||||||
product_list = session.query(Product).filter(or_(Product.bar_code.ilike(f'%{string}%'),
|
product_list = (
|
||||||
Product.name.ilike(f'%{string}%'))).all()
|
session.query(Product)
|
||||||
|
.filter(
|
||||||
|
or_(
|
||||||
|
Product.bar_code.ilike(f"%{string}%"),
|
||||||
|
Product.name.ilike(f"%{string}%"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
product_list = session.query(Product).filter(or_(Product.bar_code.ilike(f'%{string}%'),
|
product_list = (
|
||||||
and_(Product.name.ilike(f'%{string}%'),
|
session.query(Product)
|
||||||
Product.hidden == False))).all()
|
.filter(
|
||||||
|
or_(
|
||||||
|
Product.bar_code.ilike(f"%{string}%"),
|
||||||
|
and_(Product.name.ilike(f"%{string}%"), Product.hidden == False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
return product_list
|
return product_list
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,61 +86,21 @@ def system_user_exists(username):
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def guess_data_type(string):
|
def guess_data_type(string):
|
||||||
if string.startswith('ntnu') and string[4:].isdigit():
|
if string.startswith("ntnu") and string[4:].isdigit():
|
||||||
return 'card'
|
return "card"
|
||||||
if string.isdigit() and len(string) == 10:
|
if string.isdigit() and len(string) == 10:
|
||||||
return 'rfid'
|
return "rfid"
|
||||||
if string.isdigit() and len(string) in [8,13]:
|
if string.isdigit() and len(string) in [8, 13]:
|
||||||
return 'bar_code'
|
return "bar_code"
|
||||||
# if string.isdigit() and len(string) > 5:
|
# if string.isdigit() and len(string) > 5:
|
||||||
# return 'card'
|
# return 'card'
|
||||||
if string.isalpha() and string.islower() and system_user_exists(string):
|
if string.isalpha() and string.islower() and system_user_exists(string):
|
||||||
return 'username'
|
return "username"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# def retrieve_user(string, session):
|
|
||||||
# # first = session.query(User).filter(or_(User.name==string, User.card==string)).first()
|
|
||||||
# search = search_user(string,session)
|
|
||||||
# if isinstance(search,User):
|
|
||||||
# print "Found user "+search.name
|
|
||||||
# return search
|
|
||||||
# else:
|
|
||||||
# if len(search) == 0:
|
|
||||||
# print "No users found matching your search"
|
|
||||||
# return None
|
|
||||||
# if len(search) == 1:
|
|
||||||
# print "Found one user: "+list[0].name
|
|
||||||
# if confirm():
|
|
||||||
# return list[0]
|
|
||||||
# else:
|
|
||||||
# return None
|
|
||||||
# else:
|
|
||||||
# print "Found "+str(len(search))+" users:"
|
|
||||||
# return select_from_list(search)
|
|
||||||
|
|
||||||
|
|
||||||
# def confirm(prompt='Confirm? (y/n) '):
|
|
||||||
# while True:
|
|
||||||
# input = raw_input(prompt)
|
|
||||||
# if input in ["y","yes"]:
|
|
||||||
# return True
|
|
||||||
# elif input in ["n","no"]:
|
|
||||||
# return False
|
|
||||||
# else:
|
|
||||||
# print "Nonsense!"
|
|
||||||
|
|
||||||
# def select_from_list(list):
|
|
||||||
# while True:
|
|
||||||
# for i in range(len(list)):
|
|
||||||
# print i+1, " ) ", list[i].name
|
|
||||||
# choice = raw_input("Select user :\n")
|
|
||||||
# if choice in [str(x+1) for x in range(len(list))]:
|
|
||||||
# return list[int(choice)-1]
|
|
||||||
# else:
|
|
||||||
# return None
|
|
||||||
|
|
||||||
def argmax(d, all=False, value=None):
|
def argmax(d, all=False, value=None):
|
||||||
maxarg = None
|
maxarg = None
|
||||||
maxargs = []
|
maxargs = []
|
||||||
|
@ -117,14 +118,14 @@ def argmax(d, all=False, value=None):
|
||||||
|
|
||||||
|
|
||||||
def less(string):
|
def less(string):
|
||||||
'''
|
"""
|
||||||
Run less with string as input; wait until it finishes.
|
Run less with string as input; wait until it finishes.
|
||||||
'''
|
"""
|
||||||
# If we don't ignore SIGINT while running the `less` process,
|
# If we don't ignore SIGINT while running the `less` process,
|
||||||
# it will become a zombie when someone presses C-c.
|
# it will become a zombie when someone presses C-c.
|
||||||
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
env['LESSSECURE'] = '1'
|
env["LESSSECURE"] = "1"
|
||||||
proc = subprocess.Popen('less', env=env, encoding='utf-8', stdin=subprocess.PIPE)
|
proc = subprocess.Popen("less", env=env, encoding="utf-8", stdin=subprocess.PIPE)
|
||||||
proc.communicate(string)
|
proc.communicate(string)
|
||||||
signal.signal(signal.SIGINT, int_handler)
|
signal.signal(signal.SIGINT, int_handler)
|
||||||
|
|
|
@ -10,35 +10,41 @@ from PIL import Image, ImageDraw, ImageFont
|
||||||
from .barcode_helpers import BrotherLabelWriter
|
from .barcode_helpers import BrotherLabelWriter
|
||||||
|
|
||||||
|
|
||||||
def print_name_label(text, margin=10, rotate=False, label_type="62", printer_type="QL-700",):
|
def print_name_label(
|
||||||
|
text,
|
||||||
|
margin=10,
|
||||||
|
rotate=False,
|
||||||
|
label_type="62",
|
||||||
|
printer_type="QL-700",
|
||||||
|
):
|
||||||
if not rotate:
|
if not rotate:
|
||||||
width, height = label_type_specs[label_type]['dots_printable']
|
width, height = label_type_specs[label_type]["dots_printable"]
|
||||||
else:
|
else:
|
||||||
height, width = label_type_specs[label_type]['dots_printable']
|
height, width = label_type_specs[label_type]["dots_printable"]
|
||||||
|
|
||||||
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ChopinScript.ttf")
|
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ChopinScript.ttf")
|
||||||
fs = 2000
|
fs = 2000
|
||||||
tw, th = width, height
|
tw, th = width, height
|
||||||
if width == 0:
|
if width == 0:
|
||||||
while th + 2*margin > height:
|
while th + 2 * margin > height:
|
||||||
font = ImageFont.truetype(font_path, fs)
|
font = ImageFont.truetype(font_path, fs)
|
||||||
tw, th = font.getsize(text)
|
tw, th = font.getsize(text)
|
||||||
fs -= 1
|
fs -= 1
|
||||||
width = tw+2*margin
|
width = tw + 2 * margin
|
||||||
elif height == 0:
|
elif height == 0:
|
||||||
while tw + 2*margin > width:
|
while tw + 2 * margin > width:
|
||||||
font = ImageFont.truetype(font_path, fs)
|
font = ImageFont.truetype(font_path, fs)
|
||||||
tw, th = font.getsize(text)
|
tw, th = font.getsize(text)
|
||||||
fs -= 1
|
fs -= 1
|
||||||
height = th+2*margin
|
height = th + 2 * margin
|
||||||
else:
|
else:
|
||||||
while tw + 2*margin > width or th + 2*margin > height:
|
while tw + 2 * margin > width or th + 2 * margin > height:
|
||||||
font = ImageFont.truetype(font_path, fs)
|
font = ImageFont.truetype(font_path, fs)
|
||||||
tw, th = font.getsize(text)
|
tw, th = font.getsize(text)
|
||||||
fs -= 1
|
fs -= 1
|
||||||
|
|
||||||
xp = (width//2)-(tw//2)
|
xp = (width // 2) - (tw // 2)
|
||||||
yp = (height//2)-(th//2)
|
yp = (height // 2) - (th // 2)
|
||||||
|
|
||||||
im = Image.new("RGB", (width, height), (255, 255, 255))
|
im = Image.new("RGB", (width, height), (255, 255, 255))
|
||||||
dr = ImageDraw.Draw(im)
|
dr = ImageDraw.Draw(im)
|
||||||
|
@ -55,8 +61,14 @@ def print_name_label(text, margin=10, rotate=False, label_type="62", printer_typ
|
||||||
print_image(fn, printer_type, label_type)
|
print_image(fn, printer_type, label_type)
|
||||||
|
|
||||||
|
|
||||||
def print_bar_code(barcode_value, barcode_text, barcode_type="ean13", rotate=False, printer_type="QL-700",
|
def print_bar_code(
|
||||||
label_type="62"):
|
barcode_value,
|
||||||
|
barcode_text,
|
||||||
|
barcode_type="ean13",
|
||||||
|
rotate=False,
|
||||||
|
printer_type="QL-700",
|
||||||
|
label_type="62",
|
||||||
|
):
|
||||||
bar_coder = barcode.get_barcode_class(barcode_type)
|
bar_coder = barcode.get_barcode_class(barcode_type)
|
||||||
wr = BrotherLabelWriter(typ=label_type, rot=rotate, text=barcode_text, max_height=1000)
|
wr = BrotherLabelWriter(typ=label_type, rot=rotate, text=barcode_text, max_height=1000)
|
||||||
|
|
||||||
|
@ -72,12 +84,12 @@ def print_image(fn, printer_type="QL-700", label_type="62"):
|
||||||
create_label(qlr, fn, label_type, threshold=70, cut=True)
|
create_label(qlr, fn, label_type, threshold=70, cut=True)
|
||||||
|
|
||||||
be = backend_factory("pyusb")
|
be = backend_factory("pyusb")
|
||||||
list_available_devices = be['list_available_devices']
|
list_available_devices = be["list_available_devices"]
|
||||||
BrotherQLBackend = be['backend_class']
|
BrotherQLBackend = be["backend_class"]
|
||||||
|
|
||||||
ad = list_available_devices()
|
ad = list_available_devices()
|
||||||
assert ad
|
assert ad
|
||||||
string_descr = ad[0]['string_descr']
|
string_descr = ad[0]["string_descr"]
|
||||||
|
|
||||||
printer = BrotherQLBackend(string_descr)
|
printer = BrotherQLBackend(string_descr)
|
||||||
|
|
||||||
|
|
|
@ -8,137 +8,157 @@ from .helpers import *
|
||||||
from ..models import Transaction
|
from ..models import Transaction
|
||||||
from ..db import Session
|
from ..db import Session
|
||||||
|
|
||||||
|
|
||||||
def getUser():
|
def getUser():
|
||||||
while 1:
|
while 1:
|
||||||
string = input('user? ')
|
string = input("user? ")
|
||||||
session = Session()
|
session = Session()
|
||||||
user = search_user(string, session)
|
user = search_user(string, session)
|
||||||
session.close()
|
session.close()
|
||||||
if not isinstance(user, list):
|
if not isinstance(user, list):
|
||||||
return user.name
|
return user.name
|
||||||
i=0
|
i = 0
|
||||||
if len(user)==0:
|
if len(user) == 0:
|
||||||
print('no matching string')
|
print("no matching string")
|
||||||
if len(user)==1:
|
if len(user) == 1:
|
||||||
print('antar: ', user[0].name, '\n')
|
print("antar: ", user[0].name, "\n")
|
||||||
return user[0].name
|
return user[0].name
|
||||||
if len(user)>10:
|
if len(user) > 10:
|
||||||
continue
|
continue
|
||||||
for u in user:
|
for u in user:
|
||||||
print(i, u.name)
|
print(i, u.name)
|
||||||
i += 1
|
i += 1
|
||||||
try:
|
try:
|
||||||
n = int(input ('enter number:'))
|
n = int(input("enter number:"))
|
||||||
except:
|
except:
|
||||||
print('invalid input, restarting')
|
print("invalid input, restarting")
|
||||||
continue
|
continue
|
||||||
if (n>-1) and (n<i):
|
if (n > -1) and (n < i):
|
||||||
return user[n].name
|
return user[n].name
|
||||||
|
|
||||||
|
|
||||||
def getProduct():
|
def getProduct():
|
||||||
while 1:
|
while 1:
|
||||||
string = input('product? ')
|
string = input("product? ")
|
||||||
session = Session()
|
session = Session()
|
||||||
product = search_product(string, session)
|
product = search_product(string, session)
|
||||||
session.close()
|
session.close()
|
||||||
if not isinstance(product, list):
|
if not isinstance(product, list):
|
||||||
return product.name
|
return product.name
|
||||||
i=0
|
i = 0
|
||||||
if len(product)==0:
|
if len(product) == 0:
|
||||||
print('no matching string')
|
print("no matching string")
|
||||||
if len(product)==1:
|
if len(product) == 1:
|
||||||
print('antar: ', product[0].name, '\n')
|
print("antar: ", product[0].name, "\n")
|
||||||
return product[0].name
|
return product[0].name
|
||||||
if len(product)>10:
|
if len(product) > 10:
|
||||||
continue
|
continue
|
||||||
for u in product:
|
for u in product:
|
||||||
print(i, u.name)
|
print(i, u.name)
|
||||||
i += 1
|
i += 1
|
||||||
try:
|
try:
|
||||||
n = int(input ('enter number:'))
|
n = int(input("enter number:"))
|
||||||
except:
|
except:
|
||||||
print('invalid input, restarting')
|
print("invalid input, restarting")
|
||||||
continue
|
continue
|
||||||
if (n>-1) and (n<i):
|
if (n > -1) and (n < i):
|
||||||
return product[n].name
|
return product[n].name
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
#for varer
|
# for varer
|
||||||
varePersonAntall = defaultdict(dict) #varePersonAntall[Oreo][trygvrad] == 3
|
varePersonAntall = defaultdict(dict) # varePersonAntall[Oreo][trygvrad] == 3
|
||||||
vareDatoAntall = defaultdict(list) #dict->array
|
vareDatoAntall = defaultdict(list) # dict->array
|
||||||
vareUkedagAntall = defaultdict(list)
|
vareUkedagAntall = defaultdict(list)
|
||||||
#for personer
|
# for personer
|
||||||
personVareAntall = defaultdict(dict) #personVareAntall[trygvrad][Oreo] == 3
|
personVareAntall = defaultdict(dict) # personVareAntall[trygvrad][Oreo] == 3
|
||||||
personVareVerdi = defaultdict(dict) #personVareVerdi[trygvrad][Oreo] == 30 #[kr]
|
personVareVerdi = defaultdict(dict) # personVareVerdi[trygvrad][Oreo] == 30 #[kr]
|
||||||
personDatoVerdi = defaultdict(list) #dict->array
|
personDatoVerdi = defaultdict(list) # dict->array
|
||||||
personUkedagVerdi = defaultdict(list)
|
personUkedagVerdi = defaultdict(list)
|
||||||
#for global
|
# for global
|
||||||
personPosTransactions = {} # personPosTransactions[trygvrad] == 100 #trygvrad har lagt 100kr i boksen
|
personPosTransactions = (
|
||||||
personNegTransactions = {} # personNegTransactions[trygvrad» == 70 #trygvrad har tatt 70kr fra boksen
|
{}
|
||||||
globalVareAntall = {}#globalVareAntall[Oreo] == 3
|
) # personPosTransactions[trygvrad] == 100 #trygvrad har lagt 100kr i boksen
|
||||||
globalVareVerdi = {}#globalVareVerdi[Oreo] == 30 #[kr]
|
personNegTransactions = (
|
||||||
globalPersonAntall = {}#globalPersonAntall[trygvrad] == 3
|
{}
|
||||||
globalPersonForbruk = {}#globalPersonVerdi == 30 #[kr]
|
) # personNegTransactions[trygvrad» == 70 #trygvrad har tatt 70kr fra boksen
|
||||||
|
globalVareAntall = {} # globalVareAntall[Oreo] == 3
|
||||||
|
globalVareVerdi = {} # globalVareVerdi[Oreo] == 30 #[kr]
|
||||||
|
globalPersonAntall = {} # globalPersonAntall[trygvrad] == 3
|
||||||
|
globalPersonForbruk = {} # globalPersonVerdi == 30 #[kr]
|
||||||
globalUkedagForbruk = []
|
globalUkedagForbruk = []
|
||||||
globalDatoVarer = []
|
globalDatoVarer = []
|
||||||
globalDatoForbruk = []
|
globalDatoForbruk = []
|
||||||
pengebeholdning = []
|
pengebeholdning = []
|
||||||
|
|
||||||
|
|
||||||
class InputLine:
|
class InputLine:
|
||||||
def __init__(self, u, p, t):
|
def __init__(self, u, p, t):
|
||||||
self.inputUser = u
|
self.inputUser = u
|
||||||
self.inputProduct = p
|
self.inputProduct = p
|
||||||
self.inputType = t
|
self.inputType = t
|
||||||
|
|
||||||
|
|
||||||
def getDateDb(date, inp):
|
def getDateDb(date, inp):
|
||||||
try:
|
try:
|
||||||
year = inp.partition('-')
|
year = inp.partition("-")
|
||||||
month = year[2].partition('-')
|
month = year[2].partition("-")
|
||||||
return datetime.datetime(int(year[0]), int(month[0]), int(month[2]))
|
return datetime.datetime(int(year[0]), int(month[0]), int(month[2]))
|
||||||
except:
|
except:
|
||||||
print('invalid date, setting date to date found in db')
|
print("invalid date, setting date to date found in db")
|
||||||
print(date)
|
print(date)
|
||||||
return date
|
return date
|
||||||
|
|
||||||
|
|
||||||
def dateToDateNumDb(date, startDate):
|
def dateToDateNumDb(date, startDate):
|
||||||
deltaDays = date-startDate
|
deltaDays = date - startDate
|
||||||
return int(deltaDays.days), date.weekday()
|
return int(deltaDays.days), date.weekday()
|
||||||
|
|
||||||
|
|
||||||
def getInputType():
|
def getInputType():
|
||||||
inp = 0
|
inp = 0
|
||||||
while not (inp == '1' or inp == '2' or inp == '3' or inp == '4'):
|
while not (inp == "1" or inp == "2" or inp == "3" or inp == "4"):
|
||||||
print('type 1 for user-statistics')
|
print("type 1 for user-statistics")
|
||||||
print('type 2 for product-statistics')
|
print("type 2 for product-statistics")
|
||||||
print('type 3 for global-statistics')
|
print("type 3 for global-statistics")
|
||||||
print('type 4 to enter loop-mode')
|
print("type 4 to enter loop-mode")
|
||||||
inp = input('')
|
inp = input("")
|
||||||
return int(inp)
|
return int(inp)
|
||||||
|
|
||||||
|
|
||||||
def getProducts(products):
|
def getProducts(products):
|
||||||
product = []
|
product = []
|
||||||
products = products.partition('¤')
|
products = products.partition("¤")
|
||||||
product.append(products[0])
|
product.append(products[0])
|
||||||
while (products[1]=='¤'):
|
while products[1] == "¤":
|
||||||
products = products[2].partition('¤')
|
products = products[2].partition("¤")
|
||||||
product.append(products[0])
|
product.append(products[0])
|
||||||
return product
|
return product
|
||||||
|
|
||||||
|
|
||||||
def getDateFile(date, inp):
|
def getDateFile(date, inp):
|
||||||
try:
|
try:
|
||||||
year = inp.partition('-')
|
year = inp.partition("-")
|
||||||
month = year[2].partition('-')
|
month = year[2].partition("-")
|
||||||
return datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
return datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
||||||
except:
|
except:
|
||||||
print('invalid date, setting date to date found on file file')
|
print("invalid date, setting date to date found on file file")
|
||||||
print(date)
|
print(date)
|
||||||
return datetime.date(int(date.partition('-')[0]), int(date.partition('-')[2].partition('-')[0]), int(date.partition('-')[2].partition('-')[2]))
|
return datetime.date(
|
||||||
|
int(date.partition("-")[0]),
|
||||||
|
int(date.partition("-")[2].partition("-")[0]),
|
||||||
|
int(date.partition("-")[2].partition("-")[2]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def dateToDateNumFile(date, startDate):
|
def dateToDateNumFile(date, startDate):
|
||||||
year = date.partition('-')
|
year = date.partition("-")
|
||||||
month = year[2].partition('-')
|
month = year[2].partition("-")
|
||||||
day = datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
day = datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
||||||
deltaDays = day-startDate
|
deltaDays = day - startDate
|
||||||
return int(deltaDays.days), day.weekday()
|
return int(deltaDays.days), day.weekday()
|
||||||
|
|
||||||
|
|
||||||
def clearDatabase(database):
|
def clearDatabase(database):
|
||||||
database.varePersonAntall.clear()
|
database.varePersonAntall.clear()
|
||||||
database.vareDatoAntall.clear()
|
database.vareDatoAntall.clear()
|
||||||
|
@ -153,266 +173,342 @@ def clearDatabase(database):
|
||||||
database.globalVareVerdi.clear()
|
database.globalVareVerdi.clear()
|
||||||
database.globalPersonAntall.clear()
|
database.globalPersonAntall.clear()
|
||||||
database.globalPersonForbruk.clear()
|
database.globalPersonForbruk.clear()
|
||||||
return(database)
|
return database
|
||||||
|
|
||||||
|
|
||||||
def addLineToDatabase(database, inputLine):
|
def addLineToDatabase(database, inputLine):
|
||||||
if abs(inputLine.price)>90000:
|
if abs(inputLine.price) > 90000:
|
||||||
return database
|
return database
|
||||||
#fyller inn for varer
|
# fyller inn for varer
|
||||||
if (not inputLine.product=='') and ((inputLine.inputProduct=='') or (inputLine.inputProduct==inputLine.product)):
|
if (not inputLine.product == "") and (
|
||||||
database.varePersonAntall[inputLine.product][inputLine.user] = database.varePersonAntall[inputLine.product].setdefault(inputLine.user,0) + 1
|
(inputLine.inputProduct == "") or (inputLine.inputProduct == inputLine.product)
|
||||||
|
):
|
||||||
|
database.varePersonAntall[inputLine.product][inputLine.user] = (
|
||||||
|
database.varePersonAntall[inputLine.product].setdefault(inputLine.user, 0) + 1
|
||||||
|
)
|
||||||
if inputLine.product not in database.vareDatoAntall:
|
if inputLine.product not in database.vareDatoAntall:
|
||||||
database.vareDatoAntall[inputLine.product] = [0]*(inputLine.numberOfDays+1)
|
database.vareDatoAntall[inputLine.product] = [0] * (inputLine.numberOfDays + 1)
|
||||||
database.vareDatoAntall[inputLine.product][inputLine.dateNum] += 1
|
database.vareDatoAntall[inputLine.product][inputLine.dateNum] += 1
|
||||||
if inputLine.product not in database.vareUkedagAntall:
|
if inputLine.product not in database.vareUkedagAntall:
|
||||||
database.vareUkedagAntall[inputLine.product] = [0]*7
|
database.vareUkedagAntall[inputLine.product] = [0] * 7
|
||||||
database.vareUkedagAntall[inputLine.product][inputLine.weekday] += 1
|
database.vareUkedagAntall[inputLine.product][inputLine.weekday] += 1
|
||||||
#fyller inn for personer
|
# fyller inn for personer
|
||||||
if (inputLine.inputUser=='') or (inputLine.inputUser==inputLine.user):
|
if (inputLine.inputUser == "") or (inputLine.inputUser == inputLine.user):
|
||||||
if not inputLine.product == '':
|
if not inputLine.product == "":
|
||||||
database.personVareAntall[inputLine.user][inputLine.product] = database.personVareAntall[inputLine.user].setdefault(inputLine.product,0) + 1
|
database.personVareAntall[inputLine.user][inputLine.product] = (
|
||||||
database.personVareVerdi[inputLine.user][inputLine.product] = database.personVareVerdi[inputLine.user].setdefault(inputLine.product,0) + inputLine.price
|
database.personVareAntall[inputLine.user].setdefault(inputLine.product, 0) + 1
|
||||||
|
)
|
||||||
|
database.personVareVerdi[inputLine.user][inputLine.product] = (
|
||||||
|
database.personVareVerdi[inputLine.user].setdefault(inputLine.product, 0)
|
||||||
|
+ inputLine.price
|
||||||
|
)
|
||||||
if inputLine.user not in database.personDatoVerdi:
|
if inputLine.user not in database.personDatoVerdi:
|
||||||
database.personDatoVerdi[inputLine.user] = [0]*(inputLine.numberOfDays+1)
|
database.personDatoVerdi[inputLine.user] = [0] * (inputLine.numberOfDays + 1)
|
||||||
database.personDatoVerdi[inputLine.user][inputLine.dateNum] += inputLine.price
|
database.personDatoVerdi[inputLine.user][inputLine.dateNum] += inputLine.price
|
||||||
if inputLine.user not in database.personUkedagVerdi:
|
if inputLine.user not in database.personUkedagVerdi:
|
||||||
database.personUkedagVerdi[inputLine.user] = [0]*7
|
database.personUkedagVerdi[inputLine.user] = [0] * 7
|
||||||
database.personUkedagVerdi[inputLine.user][inputLine.weekday] += inputLine.price
|
database.personUkedagVerdi[inputLine.user][inputLine.weekday] += inputLine.price
|
||||||
#fyller inn delt statistikk (genereres uansett)
|
# fyller inn delt statistikk (genereres uansett)
|
||||||
if (inputLine.product==''):
|
if inputLine.product == "":
|
||||||
if (inputLine.price>0):
|
if inputLine.price > 0:
|
||||||
database.personPosTransactions[inputLine.user] = database.personPosTransactions.setdefault(inputLine.user,0) + inputLine.price
|
database.personPosTransactions[inputLine.user] = (
|
||||||
|
database.personPosTransactions.setdefault(inputLine.user, 0) + inputLine.price
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
database.personNegTransactions[inputLine.user] = database.personNegTransactions.setdefault(inputLine.user,0) + inputLine.price
|
database.personNegTransactions[inputLine.user] = (
|
||||||
elif not (inputLine.inputType==1):
|
database.personNegTransactions.setdefault(inputLine.user, 0) + inputLine.price
|
||||||
database.globalVareAntall[inputLine.product] = database.globalVareAntall.setdefault(inputLine.product,0) + 1
|
)
|
||||||
database.globalVareVerdi[inputLine.product] = database.globalVareVerdi.setdefault(inputLine.product,0) + inputLine.price
|
elif not (inputLine.inputType == 1):
|
||||||
|
database.globalVareAntall[inputLine.product] = (
|
||||||
|
database.globalVareAntall.setdefault(inputLine.product, 0) + 1
|
||||||
|
)
|
||||||
|
database.globalVareVerdi[inputLine.product] = (
|
||||||
|
database.globalVareVerdi.setdefault(inputLine.product, 0) + inputLine.price
|
||||||
|
)
|
||||||
|
|
||||||
#fyller inn for global statistikk
|
# fyller inn for global statistikk
|
||||||
if (inputLine.inputType==3) or (inputLine.inputType==4):
|
if (inputLine.inputType == 3) or (inputLine.inputType == 4):
|
||||||
database.pengebeholdning[inputLine.dateNum] += inputLine.price
|
database.pengebeholdning[inputLine.dateNum] += inputLine.price
|
||||||
if not (inputLine.product==''):
|
if not (inputLine.product == ""):
|
||||||
database.globalPersonAntall[inputLine.user] = database.globalPersonAntall.setdefault(inputLine.user,0) + 1
|
database.globalPersonAntall[inputLine.user] = (
|
||||||
database.globalPersonForbruk[inputLine.user] = database.globalPersonForbruk.setdefault(inputLine.user,0) + inputLine.price
|
database.globalPersonAntall.setdefault(inputLine.user, 0) + 1
|
||||||
|
)
|
||||||
|
database.globalPersonForbruk[inputLine.user] = (
|
||||||
|
database.globalPersonForbruk.setdefault(inputLine.user, 0) + inputLine.price
|
||||||
|
)
|
||||||
database.globalDatoVarer[inputLine.dateNum] += 1
|
database.globalDatoVarer[inputLine.dateNum] += 1
|
||||||
database.globalDatoForbruk[inputLine.dateNum] += inputLine.price
|
database.globalDatoForbruk[inputLine.dateNum] += inputLine.price
|
||||||
database.globalUkedagForbruk[inputLine.weekday] += inputLine.price
|
database.globalUkedagForbruk[inputLine.weekday] += inputLine.price
|
||||||
return database
|
return database
|
||||||
|
|
||||||
|
|
||||||
def buildDatabaseFromDb(inputType, inputProduct, inputUser):
|
def buildDatabaseFromDb(inputType, inputProduct, inputUser):
|
||||||
sdate = input('enter start date (yyyy-mm-dd)? ')
|
sdate = input("enter start date (yyyy-mm-dd)? ")
|
||||||
edate = input('enter end date (yyyy-mm-dd)? ')
|
edate = input("enter end date (yyyy-mm-dd)? ")
|
||||||
print('building database...')
|
print("building database...")
|
||||||
session = Session()
|
session = Session()
|
||||||
transaction_list = session.query(Transaction).all()
|
transaction_list = session.query(Transaction).all()
|
||||||
inputLine = InputLine(inputUser, inputProduct, inputType)
|
inputLine = InputLine(inputUser, inputProduct, inputType)
|
||||||
startDate = getDateDb(transaction_list[0].time, sdate)
|
startDate = getDateDb(transaction_list[0].time, sdate)
|
||||||
endDate = getDateDb(transaction_list[-1].time, edate)
|
endDate = getDateDb(transaction_list[-1].time, edate)
|
||||||
inputLine.numberOfDays = (endDate-startDate).days
|
inputLine.numberOfDays = (endDate - startDate).days
|
||||||
database = Database()
|
database = Database()
|
||||||
database = clearDatabase(database)
|
database = clearDatabase(database)
|
||||||
|
|
||||||
if (inputType==3) or (inputType==4):
|
if (inputType == 3) or (inputType == 4):
|
||||||
database.globalDatoVarer = [0]*(inputLine.numberOfDays+1)
|
database.globalDatoVarer = [0] * (inputLine.numberOfDays + 1)
|
||||||
database.globalDatoForbruk = [0]*(inputLine.numberOfDays+1)
|
database.globalDatoForbruk = [0] * (inputLine.numberOfDays + 1)
|
||||||
database.globalUkedagForbruk = [0]*7
|
database.globalUkedagForbruk = [0] * 7
|
||||||
database.pengebeholdning = [0]*(inputLine.numberOfDays+1)
|
database.pengebeholdning = [0] * (inputLine.numberOfDays + 1)
|
||||||
print('wait for it.... ')
|
print("wait for it.... ")
|
||||||
for transaction in transaction_list:
|
for transaction in transaction_list:
|
||||||
if transaction.purchase:
|
if transaction.purchase:
|
||||||
products = [ent.product.name for ent in transaction.purchase.entries]
|
products = [ent.product.name for ent in transaction.purchase.entries]
|
||||||
else:
|
else:
|
||||||
products = []
|
products = []
|
||||||
products.append('')
|
products.append("")
|
||||||
inputLine.dateNum, inputLine.weekday = dateToDateNumDb(transaction.time, startDate)
|
inputLine.dateNum, inputLine.weekday = dateToDateNumDb(transaction.time, startDate)
|
||||||
if inputLine.dateNum<0 or inputLine.dateNum>(inputLine.numberOfDays):
|
if inputLine.dateNum < 0 or inputLine.dateNum > (inputLine.numberOfDays):
|
||||||
continue
|
continue
|
||||||
inputLine.user=transaction.user.name
|
inputLine.user = transaction.user.name
|
||||||
inputLine.price=transaction.amount
|
inputLine.price = transaction.amount
|
||||||
for inputLine.product in products:
|
for inputLine.product in products:
|
||||||
database=addLineToDatabase(database, inputLine )
|
database = addLineToDatabase(database, inputLine)
|
||||||
inputLine.price = 0;
|
inputLine.price = 0
|
||||||
|
|
||||||
print('saving as default.dibblerlog...', end=' ')
|
print("saving as default.dibblerlog...", end=" ")
|
||||||
f=open('default.dibblerlog','w')
|
f = open("default.dibblerlog", "w")
|
||||||
line_format = '%s|%s|%s|%s|%s|%s\n'
|
line_format = "%s|%s|%s|%s|%s|%s\n"
|
||||||
transaction_list = session.query(Transaction).all()
|
transaction_list = session.query(Transaction).all()
|
||||||
for transaction in transaction_list:
|
for transaction in transaction_list:
|
||||||
if transaction.purchase:
|
if transaction.purchase:
|
||||||
products = '¤'.join([ent.product.name for ent in transaction.purchase.entries])
|
products = "¤".join([ent.product.name for ent in transaction.purchase.entries])
|
||||||
description = ''
|
description = ""
|
||||||
else:
|
else:
|
||||||
products = ''
|
products = ""
|
||||||
description = transaction.description
|
description = transaction.description
|
||||||
line = line_format % ('purchase', transaction.time, products, transaction.user.name, transaction.amount, transaction.description)
|
line = line_format % (
|
||||||
f.write(line.encode('utf8'))
|
"purchase",
|
||||||
|
transaction.time,
|
||||||
|
products,
|
||||||
|
transaction.user.name,
|
||||||
|
transaction.amount,
|
||||||
|
transaction.description,
|
||||||
|
)
|
||||||
|
f.write(line.encode("utf8"))
|
||||||
session.close()
|
session.close()
|
||||||
f.close
|
f.close
|
||||||
#bygg database.pengebeholdning
|
# bygg database.pengebeholdning
|
||||||
if (inputType==3) or (inputType==4):
|
if (inputType == 3) or (inputType == 4):
|
||||||
for i in range(inputLine.numberOfDays+1):
|
for i in range(inputLine.numberOfDays + 1):
|
||||||
if i > 0:
|
if i > 0:
|
||||||
database.pengebeholdning[i] +=database.pengebeholdning[i-1]
|
database.pengebeholdning[i] += database.pengebeholdning[i - 1]
|
||||||
#bygg dateLine
|
# bygg dateLine
|
||||||
day=datetime.timedelta(days=1)
|
day = datetime.timedelta(days=1)
|
||||||
dateLine=[]
|
dateLine = []
|
||||||
dateLine.append(startDate)
|
dateLine.append(startDate)
|
||||||
for n in range(inputLine.numberOfDays):
|
for n in range(inputLine.numberOfDays):
|
||||||
dateLine.append(startDate+n*day)
|
dateLine.append(startDate + n * day)
|
||||||
print('done')
|
print("done")
|
||||||
return database, dateLine
|
return database, dateLine
|
||||||
|
|
||||||
def buildDatabaseFromFile(inputFile, inputType, inputProduct, inputUser):
|
|
||||||
sdate = input('enter start date (yyyy-mm-dd)? ')
|
|
||||||
edate = input('enter end date (yyyy-mm-dd)? ')
|
|
||||||
|
|
||||||
f=open(inputFile)
|
def buildDatabaseFromFile(inputFile, inputType, inputProduct, inputUser):
|
||||||
|
sdate = input("enter start date (yyyy-mm-dd)? ")
|
||||||
|
edate = input("enter end date (yyyy-mm-dd)? ")
|
||||||
|
|
||||||
|
f = open(inputFile)
|
||||||
try:
|
try:
|
||||||
fileLines=f.readlines()
|
fileLines = f.readlines()
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
inputLine = InputLine(inputUser, inputProduct, inputType)
|
inputLine = InputLine(inputUser, inputProduct, inputType)
|
||||||
startDate = getDateFile(fileLines[0].partition('|')[2].partition(' ')[0], sdate)
|
startDate = getDateFile(fileLines[0].partition("|")[2].partition(" ")[0], sdate)
|
||||||
endDate = getDateFile(fileLines[-1].partition('|')[2].partition(' ')[0], edate)
|
endDate = getDateFile(fileLines[-1].partition("|")[2].partition(" ")[0], edate)
|
||||||
inputLine.numberOfDays = (endDate-startDate).days
|
inputLine.numberOfDays = (endDate - startDate).days
|
||||||
database = Database()
|
database = Database()
|
||||||
database = clearDatabase(database)
|
database = clearDatabase(database)
|
||||||
|
|
||||||
if (inputType==3) or (inputType==4):
|
if (inputType == 3) or (inputType == 4):
|
||||||
database.globalDatoVarer = [0]*(inputLine.numberOfDays+1)
|
database.globalDatoVarer = [0] * (inputLine.numberOfDays + 1)
|
||||||
database.globalDatoForbruk = [0]*(inputLine.numberOfDays+1)
|
database.globalDatoForbruk = [0] * (inputLine.numberOfDays + 1)
|
||||||
database.globalUkedagForbruk = [0]*7
|
database.globalUkedagForbruk = [0] * 7
|
||||||
database.pengebeholdning = [0]*(inputLine.numberOfDays+1)
|
database.pengebeholdning = [0] * (inputLine.numberOfDays + 1)
|
||||||
for linje in fileLines:
|
for linje in fileLines:
|
||||||
if not (linje[0]=='#') and not (linje=='\n') :
|
if not (linje[0] == "#") and not (linje == "\n"):
|
||||||
#henter dateNum, products, user, price
|
# henter dateNum, products, user, price
|
||||||
restDel = linje.partition('|')
|
restDel = linje.partition("|")
|
||||||
restDel = restDel[2].partition(' ')
|
restDel = restDel[2].partition(" ")
|
||||||
inputLine.dateNum, inputLine.weekday = dateToDateNumFile(restDel[0], startDate)
|
inputLine.dateNum, inputLine.weekday = dateToDateNumFile(restDel[0], startDate)
|
||||||
if inputLine.dateNum<0 or inputLine.dateNum>(inputLine.numberOfDays):
|
if inputLine.dateNum < 0 or inputLine.dateNum > (inputLine.numberOfDays):
|
||||||
continue
|
continue
|
||||||
restDel=restDel[2].partition('|')
|
restDel = restDel[2].partition("|")
|
||||||
restDel=restDel[2].partition('|')
|
restDel = restDel[2].partition("|")
|
||||||
products = restDel[0]
|
products = restDel[0]
|
||||||
restDel=restDel[2].partition('|')
|
restDel = restDel[2].partition("|")
|
||||||
inputLine.user=restDel[0]
|
inputLine.user = restDel[0]
|
||||||
inputLine.price=int(restDel[2].partition('|')[0])
|
inputLine.price = int(restDel[2].partition("|")[0])
|
||||||
for inputLine.product in getProducts(products):
|
for inputLine.product in getProducts(products):
|
||||||
database=addLineToDatabase(database, inputLine )
|
database = addLineToDatabase(database, inputLine)
|
||||||
inputLine.price = 0;
|
inputLine.price = 0
|
||||||
#bygg database.pengebeholdning
|
# bygg database.pengebeholdning
|
||||||
if (inputType==3) or (inputType==4):
|
if (inputType == 3) or (inputType == 4):
|
||||||
for i in range(inputLine.numberOfDays+1):
|
for i in range(inputLine.numberOfDays + 1):
|
||||||
if i > 0:
|
if i > 0:
|
||||||
database.pengebeholdning[i] +=database.pengebeholdning[i-1]
|
database.pengebeholdning[i] += database.pengebeholdning[i - 1]
|
||||||
#bygg dateLine
|
# bygg dateLine
|
||||||
day=datetime.timedelta(days=1)
|
day = datetime.timedelta(days=1)
|
||||||
dateLine=[]
|
dateLine = []
|
||||||
dateLine.append(startDate)
|
dateLine.append(startDate)
|
||||||
for n in range(inputLine.numberOfDays):
|
for n in range(inputLine.numberOfDays):
|
||||||
dateLine.append(startDate+n*day)
|
dateLine.append(startDate + n * day)
|
||||||
return database, dateLine
|
return database, dateLine
|
||||||
|
|
||||||
|
|
||||||
def printTopDict(dictionary, n, k):
|
def printTopDict(dictionary, n, k):
|
||||||
i=0
|
i = 0
|
||||||
for key in sorted(dictionary, key=dictionary.get, reverse=k):
|
for key in sorted(dictionary, key=dictionary.get, reverse=k):
|
||||||
print(key, ': ',dictionary[key])
|
print(key, ": ", dictionary[key])
|
||||||
if i<n:
|
if i < n:
|
||||||
i += 1
|
i += 1
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def printTopDict2(dictionary, dictionary2, n):
|
def printTopDict2(dictionary, dictionary2, n):
|
||||||
print('')
|
print("")
|
||||||
print('product : price[kr] ( number )')
|
print("product : price[kr] ( number )")
|
||||||
i=0
|
i = 0
|
||||||
for key in sorted(dictionary, key=dictionary.get, reverse=True):
|
for key in sorted(dictionary, key=dictionary.get, reverse=True):
|
||||||
print(key, ': ',dictionary[key], ' (', dictionary2[key], ') ')
|
print(key, ": ", dictionary[key], " (", dictionary2[key], ") ")
|
||||||
if i<n:
|
if i < n:
|
||||||
i += 1
|
i += 1
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def printWeekdays(week, days):
|
def printWeekdays(week, days):
|
||||||
if week==[] or days==0:
|
if week == [] or days == 0:
|
||||||
return
|
return
|
||||||
print('mon: ', '%.2f'%(week[0]*7.0/days), ' tue: ', '%.2f'%(week[1]*7.0/days), ' wen: ', '%.2f'%(week[2]*7.0/days), ' thu: ', '%.2f'%(week[3]*7.0/days), ' fri: ', '%.2f'%(week[4]*7.0/days), ' sat: ','%.2f'%( week[5]*7.0/days), ' sun: ', '%.2f'%(week[6]*7.0/days))
|
print(
|
||||||
print('forbruk per dag (snitt): ', '%.2f'%(sum(week)*1.0/days))
|
"mon: ",
|
||||||
print('')
|
"%.2f" % (week[0] * 7.0 / days),
|
||||||
|
" tue: ",
|
||||||
|
"%.2f" % (week[1] * 7.0 / days),
|
||||||
|
" wen: ",
|
||||||
|
"%.2f" % (week[2] * 7.0 / days),
|
||||||
|
" thu: ",
|
||||||
|
"%.2f" % (week[3] * 7.0 / days),
|
||||||
|
" fri: ",
|
||||||
|
"%.2f" % (week[4] * 7.0 / days),
|
||||||
|
" sat: ",
|
||||||
|
"%.2f" % (week[5] * 7.0 / days),
|
||||||
|
" sun: ",
|
||||||
|
"%.2f" % (week[6] * 7.0 / days),
|
||||||
|
)
|
||||||
|
print("forbruk per dag (snitt): ", "%.2f" % (sum(week) * 1.0 / days))
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
def printBalance(database, user):
|
def printBalance(database, user):
|
||||||
forbruk = 0
|
forbruk = 0
|
||||||
if (user in database.personVareVerdi):
|
if user in database.personVareVerdi:
|
||||||
forbruk = sum([i for i in list(database.personVareVerdi[user].values())])
|
forbruk = sum([i for i in list(database.personVareVerdi[user].values())])
|
||||||
print('totalt kjøpt for: ', forbruk, end=' ')
|
print("totalt kjøpt for: ", forbruk, end=" ")
|
||||||
if (user in database.personNegTransactions):
|
if user in database.personNegTransactions:
|
||||||
print('kr, totalt lagt til: ', -database.personNegTransactions[user], end=' ')
|
print("kr, totalt lagt til: ", -database.personNegTransactions[user], end=" ")
|
||||||
forbruk=-database.personNegTransactions[user]-forbruk
|
forbruk = -database.personNegTransactions[user] - forbruk
|
||||||
if (user in database.personPosTransactions):
|
if user in database.personPosTransactions:
|
||||||
print('kr, totalt tatt fra boks: ', database.personPosTransactions[user], end=' ')
|
print("kr, totalt tatt fra boks: ", database.personPosTransactions[user], end=" ")
|
||||||
forbruk=forbruk-database.personPosTransactions[user]
|
forbruk = forbruk - database.personPosTransactions[user]
|
||||||
print('balanse: ', forbruk, 'kr', end=' ')
|
print("balanse: ", forbruk, "kr", end=" ")
|
||||||
print('')
|
print("")
|
||||||
|
|
||||||
|
|
||||||
def printUser(database, dateLine, user, n):
|
def printUser(database, dateLine, user, n):
|
||||||
printTopDict2(database.personVareVerdi[user], database.personVareAntall[user], n)
|
printTopDict2(database.personVareVerdi[user], database.personVareAntall[user], n)
|
||||||
print('\nforbruk per ukedag [kr/dag],', end=' ')
|
print("\nforbruk per ukedag [kr/dag],", end=" ")
|
||||||
printWeekdays(database.personUkedagVerdi[user], len(dateLine))
|
printWeekdays(database.personUkedagVerdi[user], len(dateLine))
|
||||||
printBalance(database, user)
|
printBalance(database, user)
|
||||||
|
|
||||||
|
|
||||||
def printProduct(database, dateLine, product, n):
|
def printProduct(database, dateLine, product, n):
|
||||||
printTopDict(database.varePersonAntall[product], n, 1)
|
printTopDict(database.varePersonAntall[product], n, 1)
|
||||||
print('\nforbruk per ukedag [antall/dag],', end=' ')
|
print("\nforbruk per ukedag [antall/dag],", end=" ")
|
||||||
printWeekdays(database.vareUkedagAntall[product], len(dateLine))
|
printWeekdays(database.vareUkedagAntall[product], len(dateLine))
|
||||||
print('Det er solgt: ', database.globalVareAntall[product], product, 'til en verdi av: ', database.globalVareVerdi[product], 'kr')
|
print(
|
||||||
|
"Det er solgt: ",
|
||||||
|
database.globalVareAntall[product],
|
||||||
|
product,
|
||||||
|
"til en verdi av: ",
|
||||||
|
database.globalVareVerdi[product],
|
||||||
|
"kr",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def printGlobal(database, dateLine, n):
|
def printGlobal(database, dateLine, n):
|
||||||
print('\nmest lagt til: ')
|
print("\nmest lagt til: ")
|
||||||
printTopDict(database.personNegTransactions, n, 0)
|
printTopDict(database.personNegTransactions, n, 0)
|
||||||
print('\nmest tatt fra:')
|
print("\nmest tatt fra:")
|
||||||
printTopDict(database.personPosTransactions, n, 1)
|
printTopDict(database.personPosTransactions, n, 1)
|
||||||
print('\nstørst forbruk:')
|
print("\nstørst forbruk:")
|
||||||
printTopDict(database.globalPersonForbruk, n, 1)
|
printTopDict(database.globalPersonForbruk, n, 1)
|
||||||
printTopDict2(database.globalVareVerdi, database.globalVareAntall, n)
|
printTopDict2(database.globalVareVerdi, database.globalVareAntall, n)
|
||||||
print('\nforbruk per ukedag [kr/dag],', end=' ')
|
print("\nforbruk per ukedag [kr/dag],", end=" ")
|
||||||
printWeekdays(database.globalUkedagForbruk, len(dateLine))
|
printWeekdays(database.globalUkedagForbruk, len(dateLine))
|
||||||
print('Det er solgt varer til en verdi av: ', sum(database.globalDatoForbruk), 'kr, det er lagt til', -sum([i for i in list(database.personNegTransactions.values())]), 'og tatt fra', sum([i for i in list(database.personPosTransactions.values())]), end=' ')
|
print(
|
||||||
print('balansen blir:', database.pengebeholdning[len(dateLine)-1], 'der negative verdier representerer at brukere har kreditt tilgjengelig')
|
"Det er solgt varer til en verdi av: ",
|
||||||
|
sum(database.globalDatoForbruk),
|
||||||
|
"kr, det er lagt til",
|
||||||
|
-sum([i for i in list(database.personNegTransactions.values())]),
|
||||||
|
"og tatt fra",
|
||||||
|
sum([i for i in list(database.personPosTransactions.values())]),
|
||||||
|
end=" ",
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"balansen blir:",
|
||||||
|
database.pengebeholdning[len(dateLine) - 1],
|
||||||
|
"der negative verdier representerer at brukere har kreditt tilgjengelig",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def alt4menuTextOnly(database, dateLine):
|
def alt4menuTextOnly(database, dateLine):
|
||||||
n=10
|
n = 10
|
||||||
while 1:
|
while 1:
|
||||||
print('\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit')
|
print(
|
||||||
inp = input('')
|
"\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit"
|
||||||
if inp == 'q':
|
)
|
||||||
|
inp = input("")
|
||||||
|
if inp == "q":
|
||||||
break
|
break
|
||||||
elif inp == '1':
|
elif inp == "1":
|
||||||
try:
|
try:
|
||||||
printUser(database, dateLine, getUser(), n)
|
printUser(database, dateLine, getUser(), n)
|
||||||
except:
|
except:
|
||||||
print('\n\nSomething is not right, (last date prior to first date?)')
|
print("\n\nSomething is not right, (last date prior to first date?)")
|
||||||
elif inp == '2':
|
elif inp == "2":
|
||||||
try:
|
try:
|
||||||
printProduct(database, dateLine, getProduct(), n)
|
printProduct(database, dateLine, getProduct(), n)
|
||||||
except:
|
except:
|
||||||
print('\n\nSomething is not right, (last date prior to first date?)')
|
print("\n\nSomething is not right, (last date prior to first date?)")
|
||||||
elif inp == '3':
|
elif inp == "3":
|
||||||
try:
|
try:
|
||||||
printGlobal(database, dateLine, n)
|
printGlobal(database, dateLine, n)
|
||||||
except:
|
except:
|
||||||
print('\n\nSomething is not right, (last date prior to first date?)')
|
print("\n\nSomething is not right, (last date prior to first date?)")
|
||||||
elif inp == 'n':
|
elif inp == "n":
|
||||||
n=int(input('set number to show '));
|
n = int(input("set number to show "))
|
||||||
|
|
||||||
|
|
||||||
def statisticsTextOnly():
|
def statisticsTextOnly():
|
||||||
inputType = 4
|
inputType = 4
|
||||||
product = ''
|
product = ""
|
||||||
user = ''
|
user = ""
|
||||||
print('\n0: from file, 1: from database, q:quit')
|
print("\n0: from file, 1: from database, q:quit")
|
||||||
inp = input('')
|
inp = input("")
|
||||||
if inp == '1':
|
if inp == "1":
|
||||||
database, dateLine = buildDatabaseFromDb(inputType, product, user)
|
database, dateLine = buildDatabaseFromDb(inputType, product, user)
|
||||||
elif inp=='0' or inp == '':
|
elif inp == "0" or inp == "":
|
||||||
database, dateLine = buildDatabaseFromFile('default.dibblerlog', inputType, product, user)
|
database, dateLine = buildDatabaseFromFile("default.dibblerlog", inputType, product, user)
|
||||||
if not inp == 'q':
|
if not inp == "q":
|
||||||
alt4menuTextOnly(database, dateLine)
|
alt4menuTextOnly(database, dateLine)
|
||||||
|
|
|
@ -13,37 +13,32 @@ parser.add_argument(
|
||||||
)
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(
|
subparsers = parser.add_subparsers(
|
||||||
title='subcommands',
|
title="subcommands",
|
||||||
dest='subcommand',
|
dest="subcommand",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
subparsers.add_parser(
|
subparsers.add_parser("loop", help="Run the dibbler loop")
|
||||||
'loop',
|
subparsers.add_parser("create-db", help="Create the database")
|
||||||
help='Run the dibbler loop'
|
subparsers.add_parser("slabbedasker", help="Find out who is slabbedasker")
|
||||||
)
|
|
||||||
subparsers.add_parser(
|
|
||||||
'create-db',
|
|
||||||
help='Create the database'
|
|
||||||
)
|
|
||||||
subparsers.add_parser(
|
|
||||||
'slabbedasker',
|
|
||||||
help='Find out who is slabbedasker'
|
|
||||||
)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
config.read(args.config)
|
config.read(args.config)
|
||||||
|
|
||||||
if args.subcommand == 'loop':
|
if args.subcommand == "loop":
|
||||||
import dibbler.subcommands.loop as loop
|
import dibbler.subcommands.loop as loop
|
||||||
|
|
||||||
loop.main()
|
loop.main()
|
||||||
|
|
||||||
elif args.subcommand == 'create-db':
|
elif args.subcommand == "create-db":
|
||||||
import dibbler.subcommands.makedb as makedb
|
import dibbler.subcommands.makedb as makedb
|
||||||
|
|
||||||
makedb.main()
|
makedb.main()
|
||||||
|
|
||||||
elif args.subcommand == 'slabbedasker':
|
elif args.subcommand == "slabbedasker":
|
||||||
import dibbler.subcommands.slabbedasker as slabbedasker
|
import dibbler.subcommands.slabbedasker as slabbedasker
|
||||||
|
|
||||||
slabbedasker.main()
|
slabbedasker.main()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,5 +24,5 @@ from .stats import (
|
||||||
ProductPopularityMenu,
|
ProductPopularityMenu,
|
||||||
ProductRevenueMenu,
|
ProductRevenueMenu,
|
||||||
BalanceMenu,
|
BalanceMenu,
|
||||||
LoggedStatisticsMenu
|
LoggedStatisticsMenu,
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,10 +14,10 @@ from .helpermenus import Menu
|
||||||
|
|
||||||
class AddStockMenu(Menu):
|
class AddStockMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Add stock and adjust credit', uses_db=True)
|
Menu.__init__(self, "Add stock and adjust credit", uses_db=True)
|
||||||
self.help_text = '''
|
self.help_text = """
|
||||||
Enter what you have bought for PVVVV here, along with your user name and how
|
Enter what you have bought for PVVVV here, along with your user name and how
|
||||||
much money you're due in credits for the purchase when prompted.\n'''
|
much money you're due in credits for the purchase when prompted.\n"""
|
||||||
self.users = []
|
self.users = []
|
||||||
self.users = []
|
self.users = []
|
||||||
self.products = {}
|
self.products = {}
|
||||||
|
@ -25,10 +25,19 @@ much money you're due in credits for the purchase when prompted.\n'''
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
questions = {
|
questions = {
|
||||||
(False, False): 'Enter user id or a string of the form "<number> <product>"',
|
(
|
||||||
(False, True): 'Enter user id or more strings of the form "<number> <product>"',
|
False,
|
||||||
|
False,
|
||||||
|
): 'Enter user id or a string of the form "<number> <product>"',
|
||||||
|
(
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
): 'Enter user id or more strings of the form "<number> <product>"',
|
||||||
(True, False): 'Enter a string of the form "<number> <product>"',
|
(True, False): 'Enter a string of the form "<number> <product>"',
|
||||||
(True, True): 'Enter more strings of the form "<number> <product>", or an empty line to confirm'
|
(
|
||||||
|
True,
|
||||||
|
True,
|
||||||
|
): 'Enter more strings of the form "<number> <product>", or an empty line to confirm',
|
||||||
}
|
}
|
||||||
|
|
||||||
self.users = []
|
self.users = []
|
||||||
|
@ -41,24 +50,33 @@ much money you're due in credits for the purchase when prompted.\n'''
|
||||||
thing_price = 0
|
thing_price = 0
|
||||||
|
|
||||||
# Read in a 'thing' (product or user):
|
# Read in a 'thing' (product or user):
|
||||||
line = self.input_multiple(add_nonexisting=('user', 'product'), empty_input_permitted=True,
|
line = self.input_multiple(
|
||||||
find_hidden_products=False)
|
add_nonexisting=("user", "product"),
|
||||||
|
empty_input_permitted=True,
|
||||||
|
find_hidden_products=False,
|
||||||
|
)
|
||||||
|
|
||||||
if line:
|
if line:
|
||||||
(thing, amount) = line
|
(thing, amount) = line
|
||||||
|
|
||||||
if isinstance(thing, Product):
|
if isinstance(thing, Product):
|
||||||
self.printc(f"{amount:d} of {thing.name} registered")
|
self.printc(f"{amount:d} of {thing.name} registered")
|
||||||
thing_price = self.input_int('What did you pay a piece?', 1, 100000, default=thing.price) * amount
|
thing_price = (
|
||||||
|
self.input_int("What did you pay a piece?", 1, 100000, default=thing.price)
|
||||||
|
* amount
|
||||||
|
)
|
||||||
self.price += thing_price
|
self.price += thing_price
|
||||||
|
|
||||||
# once we get something in the
|
# once we get something in the
|
||||||
# purchase, we want to protect the
|
# purchase, we want to protect the
|
||||||
# user from accidentally killing it
|
# user from accidentally killing it
|
||||||
self.exit_confirm_msg = 'Abort transaction?'
|
self.exit_confirm_msg = "Abort transaction?"
|
||||||
else:
|
else:
|
||||||
if not self.complete_input():
|
if not self.complete_input():
|
||||||
if self.confirm('Not enough information entered. Abort transaction?', default=True):
|
if self.confirm(
|
||||||
|
"Not enough information entered. Abort transaction?",
|
||||||
|
default=True,
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
@ -74,7 +92,7 @@ much money you're due in credits for the purchase when prompted.\n'''
|
||||||
def print_info(self):
|
def print_info(self):
|
||||||
width = 6 + Product.name_length
|
width = 6 + Product.name_length
|
||||||
print()
|
print()
|
||||||
print(width * '-')
|
print(width * "-")
|
||||||
if self.price:
|
if self.price:
|
||||||
print(f"Amount to be credited:{self.price:>{width - 22}}")
|
print(f"Amount to be credited:{self.price:>{width - 22}}")
|
||||||
if self.users:
|
if self.users:
|
||||||
|
@ -84,41 +102,47 @@ much money you're due in credits for the purchase when prompted.\n'''
|
||||||
print()
|
print()
|
||||||
print("Products", end="")
|
print("Products", end="")
|
||||||
print("Amount".rjust(width - 8))
|
print("Amount".rjust(width - 8))
|
||||||
print(width * '-')
|
print(width * "-")
|
||||||
if len(self.products):
|
if len(self.products):
|
||||||
for product in list(self.products.keys()):
|
for product in list(self.products.keys()):
|
||||||
print(f"{product.name}", end="")
|
print(f"{product.name}", end="")
|
||||||
print(f"{self.products[product][0]}".rjust(width - len(product.name)))
|
print(f"{self.products[product][0]}".rjust(width - len(product.name)))
|
||||||
print(width * '-')
|
print(width * "-")
|
||||||
|
|
||||||
def add_thing_to_pending(self, thing, amount, price):
|
def add_thing_to_pending(self, thing, amount, price):
|
||||||
if isinstance(thing, User):
|
if isinstance(thing, User):
|
||||||
self.users.append(thing)
|
self.users.append(thing)
|
||||||
elif thing in list(self.products.keys()):
|
elif thing in list(self.products.keys()):
|
||||||
print('Already added this product, adding amounts')
|
print("Already added this product, adding amounts")
|
||||||
self.products[thing][0] += amount
|
self.products[thing][0] += amount
|
||||||
self.products[thing][1] += price
|
self.products[thing][1] += price
|
||||||
else:
|
else:
|
||||||
self.products[thing] = [amount, price]
|
self.products[thing] = [amount, price]
|
||||||
|
|
||||||
def perform_transaction(self):
|
def perform_transaction(self):
|
||||||
print('Did you pay a different price?')
|
print("Did you pay a different price?")
|
||||||
if self.confirm('>', default=False):
|
if self.confirm(">", default=False):
|
||||||
self.price = self.input_int('How much did you pay?', 0, self.price, default=self.price)
|
self.price = self.input_int("How much did you pay?", 0, self.price, default=self.price)
|
||||||
|
|
||||||
description = self.input_str('Log message', length_range=(0, 50))
|
description = self.input_str("Log message", length_range=(0, 50))
|
||||||
if description == '':
|
if description == "":
|
||||||
description = 'Purchased products for PVVVV, adjusted credit ' + str(self.price)
|
description = "Purchased products for PVVVV, adjusted credit " + str(self.price)
|
||||||
for product in self.products:
|
for product in self.products:
|
||||||
value = max(product.stock, 0) * product.price + self.products[product][1]
|
value = max(product.stock, 0) * product.price + self.products[product][1]
|
||||||
old_price = product.price
|
old_price = product.price
|
||||||
old_hidden = product.hidden
|
old_hidden = product.hidden
|
||||||
product.price = int(ceil(float(value) / (max(product.stock, 0) + self.products[product][0])))
|
product.price = int(
|
||||||
product.stock = max(self.products[product][0], product.stock + self.products[product][0])
|
ceil(float(value) / (max(product.stock, 0) + self.products[product][0]))
|
||||||
|
)
|
||||||
|
product.stock = max(
|
||||||
|
self.products[product][0], product.stock + self.products[product][0]
|
||||||
|
)
|
||||||
product.hidden = False
|
product.hidden = False
|
||||||
print(f"New stock for {product.name}: {product.stock:d}",
|
print(
|
||||||
|
f"New stock for {product.name}: {product.stock:d}",
|
||||||
f"- New price: {product.price}" if old_price != product.price else "",
|
f"- New price: {product.price}" if old_price != product.price else "",
|
||||||
"- Removed hidden status" if old_hidden != product.hidden else "")
|
"- Removed hidden status" if old_hidden != product.hidden else "",
|
||||||
|
)
|
||||||
|
|
||||||
purchase = Purchase()
|
purchase = Purchase()
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
|
@ -136,4 +160,4 @@ much money you're due in credits for the purchase when prompted.\n'''
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
print(f"User {user.name}'s credit is now {user.credit:d}")
|
print(f"User {user.name}'s credit is now {user.credit:d}")
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not perform transaction: {e}')
|
print(f"Could not perform transaction: {e}")
|
||||||
|
|
|
@ -14,18 +14,18 @@ from .helpermenus import Menu
|
||||||
|
|
||||||
class BuyMenu(Menu):
|
class BuyMenu(Menu):
|
||||||
def __init__(self, session=None):
|
def __init__(self, session=None):
|
||||||
Menu.__init__(self, 'Buy', uses_db=True)
|
Menu.__init__(self, "Buy", uses_db=True)
|
||||||
if session:
|
if session:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.superfast_mode = False
|
self.superfast_mode = False
|
||||||
self.help_text = '''
|
self.help_text = """
|
||||||
Each purchase may contain one or more products and one or more buyers.
|
Each purchase may contain one or more products and one or more buyers.
|
||||||
|
|
||||||
Enter products (by name or bar code) and buyers (by name or bar code)
|
Enter products (by name or bar code) and buyers (by name or bar code)
|
||||||
in any order. The information gathered so far is displayed after each
|
in any order. The information gathered so far is displayed after each
|
||||||
addition, and you can type 'what' at any time to redisplay it.
|
addition, and you can type 'what' at any time to redisplay it.
|
||||||
|
|
||||||
When finished, write an empty line to confirm the purchase.\n'''
|
When finished, write an empty line to confirm the purchase.\n"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def credit_check(user):
|
def credit_check(user):
|
||||||
|
@ -37,7 +37,7 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
"""
|
"""
|
||||||
assert isinstance(user, User)
|
assert isinstance(user, User)
|
||||||
|
|
||||||
return user.credit > config.getint('limits', 'low_credit_warning_limit')
|
return user.credit > config.getint("limits", "low_credit_warning_limit")
|
||||||
|
|
||||||
def low_credit_warning(self, user, timeout=False):
|
def low_credit_warning(self, user, timeout=False):
|
||||||
assert isinstance(user, User)
|
assert isinstance(user, User)
|
||||||
|
@ -57,7 +57,9 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
print("***********************************************************************")
|
print("***********************************************************************")
|
||||||
print("***********************************************************************")
|
print("***********************************************************************")
|
||||||
print("")
|
print("")
|
||||||
print(f"USER {user.name} HAS LOWER CREDIT THAN {config.getint('limits', 'low_credit_warning_limit'):d}.")
|
print(
|
||||||
|
f"USER {user.name} HAS LOWER CREDIT THAN {config.getint('limits', 'low_credit_warning_limit'):d}."
|
||||||
|
)
|
||||||
print("THIS PURCHASE WILL CHARGE YOUR CREDIT TWICE AS MUCH.")
|
print("THIS PURCHASE WILL CHARGE YOUR CREDIT TWICE AS MUCH.")
|
||||||
print("CONSIDER PUTTING MONEY IN THE BOX TO AVOID THIS.")
|
print("CONSIDER PUTTING MONEY IN THE BOX TO AVOID THIS.")
|
||||||
print("")
|
print("")
|
||||||
|
@ -72,10 +74,10 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
def add_thing_to_purchase(self, thing, amount=1):
|
def add_thing_to_purchase(self, thing, amount=1):
|
||||||
if isinstance(thing, User):
|
if isinstance(thing, User):
|
||||||
if thing.is_anonymous():
|
if thing.is_anonymous():
|
||||||
print('---------------------------------------------')
|
print("---------------------------------------------")
|
||||||
print('| You are now purchasing as the user anonym.|')
|
print("| You are now purchasing as the user anonym.|")
|
||||||
print('| You have to put money in the anonym-jar. |')
|
print("| You have to put money in the anonym-jar. |")
|
||||||
print('---------------------------------------------')
|
print("---------------------------------------------")
|
||||||
|
|
||||||
if not self.credit_check(thing):
|
if not self.credit_check(thing):
|
||||||
if self.low_credit_warning(user=thing, timeout=self.superfast_mode):
|
if self.low_credit_warning(user=thing, timeout=self.superfast_mode):
|
||||||
|
@ -110,24 +112,32 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
|
|
||||||
if len(initial_contents) > 0 and all(map(is_product, initial_contents)):
|
if len(initial_contents) > 0 and all(map(is_product, initial_contents)):
|
||||||
self.superfast_mode = True
|
self.superfast_mode = True
|
||||||
print('***********************************************')
|
print("***********************************************")
|
||||||
print('****** Buy menu is in SUPERFASTmode[tm]! ******')
|
print("****** Buy menu is in SUPERFASTmode[tm]! ******")
|
||||||
print('*** The purchase will be stored immediately ***')
|
print("*** The purchase will be stored immediately ***")
|
||||||
print('*** when you enter a user. ***')
|
print("*** when you enter a user. ***")
|
||||||
print('***********************************************')
|
print("***********************************************")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self.print_purchase()
|
self.print_purchase()
|
||||||
self.printc({(False, False): 'Enter user or product identification',
|
self.printc(
|
||||||
(False, True): 'Enter user identification or more products',
|
{
|
||||||
(True, False): 'Enter product identification or more users',
|
(False, False): "Enter user or product identification",
|
||||||
(True, True): 'Enter more products or users, or an empty line to confirm'
|
(False, True): "Enter user identification or more products",
|
||||||
}[(len(self.purchase.transactions) > 0,
|
(True, False): "Enter product identification or more users",
|
||||||
len(self.purchase.entries) > 0)])
|
(
|
||||||
|
True,
|
||||||
|
True,
|
||||||
|
): "Enter more products or users, or an empty line to confirm",
|
||||||
|
}[(len(self.purchase.transactions) > 0, len(self.purchase.entries) > 0)]
|
||||||
|
)
|
||||||
|
|
||||||
# Read in a 'thing' (product or user):
|
# Read in a 'thing' (product or user):
|
||||||
line = self.input_multiple(add_nonexisting=('user', 'product'), empty_input_permitted=True,
|
line = self.input_multiple(
|
||||||
find_hidden_products=False)
|
add_nonexisting=("user", "product"),
|
||||||
|
empty_input_permitted=True,
|
||||||
|
find_hidden_products=False,
|
||||||
|
)
|
||||||
if line is not None:
|
if line is not None:
|
||||||
thing, num = line
|
thing, num = line
|
||||||
else:
|
else:
|
||||||
|
@ -136,7 +146,9 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
# Possibly exit from the menu:
|
# Possibly exit from the menu:
|
||||||
if thing is None:
|
if thing is None:
|
||||||
if not self.complete_input():
|
if not self.complete_input():
|
||||||
if self.confirm('Not enough information entered. Abort purchase?', default=True):
|
if self.confirm(
|
||||||
|
"Not enough information entered. Abort purchase?", default=True
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
@ -144,7 +156,7 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
# once we get something in the
|
# once we get something in the
|
||||||
# purchase, we want to protect the
|
# purchase, we want to protect the
|
||||||
# user from accidentally killing it
|
# user from accidentally killing it
|
||||||
self.exit_confirm_msg = 'Abort purchase?'
|
self.exit_confirm_msg = "Abort purchase?"
|
||||||
|
|
||||||
# Add the thing to our purchase object:
|
# Add the thing to our purchase object:
|
||||||
if not self.add_thing_to_purchase(thing, amount=num):
|
if not self.add_thing_to_purchase(thing, amount=num):
|
||||||
|
@ -159,16 +171,18 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
try:
|
try:
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not store purchase: {e}')
|
print(f"Could not store purchase: {e}")
|
||||||
else:
|
else:
|
||||||
print('Purchase stored.')
|
print("Purchase stored.")
|
||||||
self.print_purchase()
|
self.print_purchase()
|
||||||
for t in self.purchase.transactions:
|
for t in self.purchase.transactions:
|
||||||
if not t.user.is_anonymous():
|
if not t.user.is_anonymous():
|
||||||
print(f"User {t.user.name}'s credit is now {t.user.credit:d} kr")
|
print(f"User {t.user.name}'s credit is now {t.user.credit:d} kr")
|
||||||
if t.user.credit < config.getint('limits', 'low_credit_warning_limit'):
|
if t.user.credit < config.getint("limits", "low_credit_warning_limit"):
|
||||||
print(f'USER {t.user.name} HAS LOWER CREDIT THAN {config.getint("limits", "low_credit_warning_limit"):d},',
|
print(
|
||||||
'AND SHOULD CONSIDER PUTTING SOME MONEY IN THE BOX.')
|
f'USER {t.user.name} HAS LOWER CREDIT THAN {config.getint("limits", "low_credit_warning_limit"):d},',
|
||||||
|
"AND SHOULD CONSIDER PUTTING SOME MONEY IN THE BOX.",
|
||||||
|
)
|
||||||
|
|
||||||
# Superfast mode skips a linebreak for some reason.
|
# Superfast mode skips a linebreak for some reason.
|
||||||
if self.superfast_mode:
|
if self.superfast_mode:
|
||||||
|
@ -184,30 +198,33 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||||
entries = self.purchase.entries
|
entries = self.purchase.entries
|
||||||
if len(transactions) == 0 and len(entries) == 0:
|
if len(transactions) == 0 and len(entries) == 0:
|
||||||
return None
|
return None
|
||||||
string = 'Purchase:'
|
string = "Purchase:"
|
||||||
string += '\n buyers: '
|
string += "\n buyers: "
|
||||||
if len(transactions) == 0:
|
if len(transactions) == 0:
|
||||||
string += '(empty)'
|
string += "(empty)"
|
||||||
else:
|
else:
|
||||||
string += ', '.join(
|
string += ", ".join(
|
||||||
[t.user.name + ("*" if not self.credit_check(t.user) else "") for t in transactions])
|
[t.user.name + ("*" if not self.credit_check(t.user) else "") for t in transactions]
|
||||||
string += '\n products: '
|
)
|
||||||
|
string += "\n products: "
|
||||||
if len(entries) == 0:
|
if len(entries) == 0:
|
||||||
string += '(empty)'
|
string += "(empty)"
|
||||||
else:
|
else:
|
||||||
string += "\n "
|
string += "\n "
|
||||||
string += '\n '.join([f'{e.amount:d}x {e.product.name} ({e.product.price:d} kr)' for e in entries])
|
string += "\n ".join(
|
||||||
|
[f"{e.amount:d}x {e.product.name} ({e.product.price:d} kr)" for e in entries]
|
||||||
|
)
|
||||||
if len(transactions) > 1:
|
if len(transactions) > 1:
|
||||||
string += f'\n price per person: {self.purchase.price_per_transaction():d} kr'
|
string += f"\n price per person: {self.purchase.price_per_transaction():d} kr"
|
||||||
if any(t.penalty > 1 for t in transactions):
|
if any(t.penalty > 1 for t in transactions):
|
||||||
# TODO: Use penalty multiplier instead of 2
|
# TODO: Use penalty multiplier instead of 2
|
||||||
string += f' *({self.purchase.price_per_transaction() * 2:d} kr)'
|
string += f" *({self.purchase.price_per_transaction() * 2:d} kr)"
|
||||||
|
|
||||||
string += f'\n total price: {self.purchase.price:d} kr'
|
string += f"\n total price: {self.purchase.price:d} kr"
|
||||||
|
|
||||||
if any(t.penalty > 1 for t in transactions):
|
if any(t.penalty > 1 for t in transactions):
|
||||||
total = sum(self.purchase.price_per_transaction() * t.penalty for t in transactions)
|
total = sum(self.purchase.price_per_transaction() * t.penalty for t in transactions)
|
||||||
string += f'\n *total with penalty: {total} kr'
|
string += f"\n *total with penalty: {total} kr"
|
||||||
|
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
|
@ -3,155 +3,184 @@ import sqlalchemy
|
||||||
from dibbler.models import User, Product
|
from dibbler.models import User, Product
|
||||||
from .helpermenus import Menu, Selector
|
from .helpermenus import Menu, Selector
|
||||||
|
|
||||||
__all__ = ["AddUserMenu", "AddProductMenu", "EditProductMenu", "AdjustStockMenu", "CleanupStockMenu", "EditUserMenu"]
|
__all__ = [
|
||||||
|
"AddUserMenu",
|
||||||
|
"AddProductMenu",
|
||||||
|
"EditProductMenu",
|
||||||
|
"AdjustStockMenu",
|
||||||
|
"CleanupStockMenu",
|
||||||
|
"EditUserMenu",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class AddUserMenu(Menu):
|
class AddUserMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Add user', uses_db=True)
|
Menu.__init__(self, "Add user", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
username = self.input_str('Username (should be same as PVV username)', regex=User.name_re, length_range=(1, 10))
|
username = self.input_str(
|
||||||
cardnum = self.input_str('Card number (optional)', regex=User.card_re, length_range=(0, 10))
|
"Username (should be same as PVV username)",
|
||||||
|
regex=User.name_re,
|
||||||
|
length_range=(1, 10),
|
||||||
|
)
|
||||||
|
cardnum = self.input_str("Card number (optional)", regex=User.card_re, length_range=(0, 10))
|
||||||
cardnum = cardnum.lower()
|
cardnum = cardnum.lower()
|
||||||
rfid = self.input_str('RFID (optional)', regex=User.rfid_re, length_range=(0, 10))
|
rfid = self.input_str("RFID (optional)", regex=User.rfid_re, length_range=(0, 10))
|
||||||
user = User(username, cardnum, rfid)
|
user = User(username, cardnum, rfid)
|
||||||
self.session.add(user)
|
self.session.add(user)
|
||||||
try:
|
try:
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
print(f'User {username} stored')
|
print(f"User {username} stored")
|
||||||
except sqlalchemy.exc.IntegrityError as e:
|
except sqlalchemy.exc.IntegrityError as e:
|
||||||
print(f'Could not store user {username}: {e}')
|
print(f"Could not store user {username}: {e}")
|
||||||
self.pause()
|
self.pause()
|
||||||
|
|
||||||
|
|
||||||
class EditUserMenu(Menu):
|
class EditUserMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Edit user', uses_db=True)
|
Menu.__init__(self, "Edit user", uses_db=True)
|
||||||
self.help_text = '''
|
self.help_text = """
|
||||||
The only editable part of a user is its card number and rfid.
|
The only editable part of a user is its card number and rfid.
|
||||||
|
|
||||||
First select an existing user, then enter a new card number for that
|
First select an existing user, then enter a new card number for that
|
||||||
user, then rfid (write an empty line to remove the card number or rfid).
|
user, then rfid (write an empty line to remove the card number or rfid).
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
user = self.input_user('User')
|
user = self.input_user("User")
|
||||||
self.printc(f'Editing user {user.name}')
|
self.printc(f"Editing user {user.name}")
|
||||||
card_str = f'"{user.card}"' if user.card is not None else 'empty'
|
card_str = f'"{user.card}"' if user.card is not None else "empty"
|
||||||
user.card = self.input_str(f'Card number (currently {card_str})',
|
user.card = self.input_str(
|
||||||
regex=User.card_re, length_range=(0, 10),
|
f"Card number (currently {card_str})",
|
||||||
empty_string_is_none=True)
|
regex=User.card_re,
|
||||||
|
length_range=(0, 10),
|
||||||
|
empty_string_is_none=True,
|
||||||
|
)
|
||||||
if user.card:
|
if user.card:
|
||||||
user.card = user.card.lower()
|
user.card = user.card.lower()
|
||||||
|
|
||||||
rfid_str = f'"{user.rfid}"' if user.rfid is not None else 'empty'
|
rfid_str = f'"{user.rfid}"' if user.rfid is not None else "empty"
|
||||||
user.rfid = self.input_str(f'RFID (currently {rfid_str})',
|
user.rfid = self.input_str(
|
||||||
regex=User.rfid_re, length_range=(0, 10),
|
f"RFID (currently {rfid_str})",
|
||||||
empty_string_is_none=True)
|
regex=User.rfid_re,
|
||||||
|
length_range=(0, 10),
|
||||||
|
empty_string_is_none=True,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
print(f'User {user.name} stored')
|
print(f"User {user.name} stored")
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not store user {user.name}: {e}')
|
print(f"Could not store user {user.name}: {e}")
|
||||||
self.pause()
|
self.pause()
|
||||||
|
|
||||||
|
|
||||||
class AddProductMenu(Menu):
|
class AddProductMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Add product', uses_db=True)
|
Menu.__init__(self, "Add product", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
bar_code = self.input_str('Bar code', regex=Product.bar_code_re, length_range=(8, 13))
|
bar_code = self.input_str("Bar code", regex=Product.bar_code_re, length_range=(8, 13))
|
||||||
name = self.input_str('Name', regex=Product.name_re, length_range=(1, Product.name_length))
|
name = self.input_str("Name", regex=Product.name_re, length_range=(1, Product.name_length))
|
||||||
price = self.input_int('Price', 1, 100000)
|
price = self.input_int("Price", 1, 100000)
|
||||||
product = Product(bar_code, name, price)
|
product = Product(bar_code, name, price)
|
||||||
self.session.add(product)
|
self.session.add(product)
|
||||||
try:
|
try:
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
print(f'Product {name} stored')
|
print(f"Product {name} stored")
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not store product {name}: {e}')
|
print(f"Could not store product {name}: {e}")
|
||||||
self.pause()
|
self.pause()
|
||||||
|
|
||||||
|
|
||||||
class EditProductMenu(Menu):
|
class EditProductMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Edit product', uses_db=True)
|
Menu.__init__(self, "Edit product", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
product = self.input_product('Product')
|
product = self.input_product("Product")
|
||||||
self.printc(f'Editing product {product.name}')
|
self.printc(f"Editing product {product.name}")
|
||||||
while True:
|
while True:
|
||||||
selector = Selector(f'Do what with {product.name}?',
|
selector = Selector(
|
||||||
items=[('name', 'Edit name'),
|
f"Do what with {product.name}?",
|
||||||
('price', 'Edit price'),
|
items=[
|
||||||
('barcode', 'Edit barcode'),
|
("name", "Edit name"),
|
||||||
('hidden', 'Edit hidden status'),
|
("price", "Edit price"),
|
||||||
('store', 'Store')])
|
("barcode", "Edit barcode"),
|
||||||
|
("hidden", "Edit hidden status"),
|
||||||
|
("store", "Store"),
|
||||||
|
],
|
||||||
|
)
|
||||||
what = selector.execute()
|
what = selector.execute()
|
||||||
if what == 'name':
|
if what == "name":
|
||||||
product.name = self.input_str('Name', default=product.name, regex=Product.name_re,
|
product.name = self.input_str(
|
||||||
length_range=(1, product.name_length))
|
"Name",
|
||||||
elif what == 'price':
|
default=product.name,
|
||||||
product.price = self.input_int('Price', 1, 100000, default=product.price)
|
regex=Product.name_re,
|
||||||
elif what == 'barcode':
|
length_range=(1, product.name_length),
|
||||||
product.bar_code = self.input_str('Bar code', default=product.bar_code, regex=Product.bar_code_re,
|
)
|
||||||
length_range=(8, 13))
|
elif what == "price":
|
||||||
elif what == 'hidden':
|
product.price = self.input_int("Price", 1, 100000, default=product.price)
|
||||||
product.hidden = self.confirm(f'Hidden(currently {product.hidden})', default=False)
|
elif what == "barcode":
|
||||||
elif what == 'store':
|
product.bar_code = self.input_str(
|
||||||
|
"Bar code",
|
||||||
|
default=product.bar_code,
|
||||||
|
regex=Product.bar_code_re,
|
||||||
|
length_range=(8, 13),
|
||||||
|
)
|
||||||
|
elif what == "hidden":
|
||||||
|
product.hidden = self.confirm(f"Hidden(currently {product.hidden})", default=False)
|
||||||
|
elif what == "store":
|
||||||
try:
|
try:
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
print(f'Product {product.name} stored')
|
print(f"Product {product.name} stored")
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not store product {product.name}: {e}')
|
print(f"Could not store product {product.name}: {e}")
|
||||||
self.pause()
|
self.pause()
|
||||||
return
|
return
|
||||||
elif what is None:
|
elif what is None:
|
||||||
print('Edit aborted')
|
print("Edit aborted")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
print('What what?')
|
print("What what?")
|
||||||
|
|
||||||
|
|
||||||
class AdjustStockMenu(Menu):
|
class AdjustStockMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Adjust stock', uses_db=True)
|
Menu.__init__(self, "Adjust stock", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
product = self.input_product('Product')
|
product = self.input_product("Product")
|
||||||
|
|
||||||
print(f'The stock of this product is: {product.stock:d}')
|
print(f"The stock of this product is: {product.stock:d}")
|
||||||
print('Write the number of products you have added to the stock')
|
print("Write the number of products you have added to the stock")
|
||||||
print('Alternatively, correct the stock for any mistakes')
|
print("Alternatively, correct the stock for any mistakes")
|
||||||
add_stock = self.input_int('Added stock', -1000, 1000, zero_allowed=False)
|
add_stock = self.input_int("Added stock", -1000, 1000, zero_allowed=False)
|
||||||
if add_stock > 0:
|
if add_stock > 0:
|
||||||
print(f'You added {add_stock:d} to the stock of {product}')
|
print(f"You added {add_stock:d} to the stock of {product}")
|
||||||
else:
|
else:
|
||||||
print(f'You removed {add_stock:d} from the stock of {product}')
|
print(f"You removed {add_stock:d} from the stock of {product}")
|
||||||
|
|
||||||
product.stock += add_stock
|
product.stock += add_stock
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
print('Stock is now stored')
|
print("Stock is now stored")
|
||||||
self.pause()
|
self.pause()
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not store stock: {e}')
|
print(f"Could not store stock: {e}")
|
||||||
self.pause()
|
self.pause()
|
||||||
return
|
return
|
||||||
print(f'The stock is now {product.stock:d}')
|
print(f"The stock is now {product.stock:d}")
|
||||||
|
|
||||||
|
|
||||||
class CleanupStockMenu(Menu):
|
class CleanupStockMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Stock Cleanup', uses_db=True)
|
Menu.__init__(self, "Stock Cleanup", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
|
@ -176,10 +205,10 @@ class CleanupStockMenu(Menu):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
print('New stocks are now stored.')
|
print("New stocks are now stored.")
|
||||||
self.pause()
|
self.pause()
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not store stock: {e}')
|
print(f"Could not store stock: {e}")
|
||||||
self.pause()
|
self.pause()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,11 @@ from .helpermenus import MessageMenu, Menu
|
||||||
|
|
||||||
class FAQMenu(Menu):
|
class FAQMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Frequently Asked Questions')
|
Menu.__init__(self, "Frequently Asked Questions")
|
||||||
self.items = [MessageMenu('What is the meaning with this program?',
|
self.items = [
|
||||||
'''
|
MessageMenu(
|
||||||
|
"What is the meaning with this program?",
|
||||||
|
"""
|
||||||
We want to avoid keeping lots of cash in PVVVV's money box and to
|
We want to avoid keeping lots of cash in PVVVV's money box and to
|
||||||
make it easy to pay for stuff without using money. (Without using
|
make it easy to pay for stuff without using money. (Without using
|
||||||
money each time, that is. You do of course have to pay for the things
|
money each time, that is. You do of course have to pay for the things
|
||||||
|
@ -19,15 +21,18 @@ class FAQMenu(Menu):
|
||||||
stock and adjust credit".
|
stock and adjust credit".
|
||||||
Alternatively, add money to the money box and use "Adjust credit" to
|
Alternatively, add money to the money box and use "Adjust credit" to
|
||||||
tell Dibbler about it.
|
tell Dibbler about it.
|
||||||
'''),
|
""",
|
||||||
MessageMenu('Can I still pay for stuff using cash?',
|
),
|
||||||
'''
|
MessageMenu(
|
||||||
|
"Can I still pay for stuff using cash?",
|
||||||
|
"""
|
||||||
Please put money in the money box and use "Adjust Credit" so that
|
Please put money in the money box and use "Adjust Credit" so that
|
||||||
dibbler can keep track of credit and purchases.'''),
|
dibbler can keep track of credit and purchases.""",
|
||||||
MessageMenu('How do I exit from a submenu/dialog/thing?',
|
),
|
||||||
'Type "exit", "q", or ^d.'),
|
MessageMenu("How do I exit from a submenu/dialog/thing?", 'Type "exit", "q", or ^d.'),
|
||||||
MessageMenu('What does "." mean?',
|
MessageMenu(
|
||||||
'''
|
'What does "." mean?',
|
||||||
|
"""
|
||||||
The "." character, known as "full stop" or "period", is most often
|
The "." character, known as "full stop" or "period", is most often
|
||||||
used to indicate the end of a sentence.
|
used to indicate the end of a sentence.
|
||||||
|
|
||||||
|
@ -35,29 +40,41 @@ class FAQMenu(Menu):
|
||||||
read some text before continuing. Whenever some output ends with a
|
read some text before continuing. Whenever some output ends with a
|
||||||
line containing only a period, you should read the lines above and
|
line containing only a period, you should read the lines above and
|
||||||
then press enter to continue.
|
then press enter to continue.
|
||||||
'''),
|
""",
|
||||||
MessageMenu('Why is the user interface so terribly unintuitive?',
|
),
|
||||||
'''
|
MessageMenu(
|
||||||
|
"Why is the user interface so terribly unintuitive?",
|
||||||
|
"""
|
||||||
Answer #1: It is not.
|
Answer #1: It is not.
|
||||||
|
|
||||||
Answer #2: We are trying to compete with PVV's microwave oven in
|
Answer #2: We are trying to compete with PVV's microwave oven in
|
||||||
userfriendliness.
|
userfriendliness.
|
||||||
|
|
||||||
Answer #3: YOU are unintuitive.
|
Answer #3: YOU are unintuitive.
|
||||||
'''),
|
""",
|
||||||
MessageMenu('Why is there no help command?',
|
),
|
||||||
'There is. Have you tried typing "help"?'),
|
MessageMenu(
|
||||||
MessageMenu('Where are the easter eggs? I tried saying "moo", but nothing happened.',
|
"Why is there no help command?",
|
||||||
'Don\'t say "moo".'),
|
'There is. Have you tried typing "help"?',
|
||||||
MessageMenu('Why does the program speak English when all the users are Norwegians?',
|
),
|
||||||
'Godt spørsmål. Det virket sikkert som en god idé der og da.'),
|
MessageMenu(
|
||||||
MessageMenu('Why does the screen have strange colours?',
|
'Where are the easter eggs? I tried saying "moo", but nothing happened.',
|
||||||
'''
|
'Don\'t say "moo".',
|
||||||
|
),
|
||||||
|
MessageMenu(
|
||||||
|
"Why does the program speak English when all the users are Norwegians?",
|
||||||
|
"Godt spørsmål. Det virket sikkert som en god idé der og da.",
|
||||||
|
),
|
||||||
|
MessageMenu(
|
||||||
|
"Why does the screen have strange colours?",
|
||||||
|
"""
|
||||||
Type "c" on the main menu to change the colours of the display, or
|
Type "c" on the main menu to change the colours of the display, or
|
||||||
"cs" if you are a boring person.
|
"cs" if you are a boring person.
|
||||||
'''),
|
""",
|
||||||
MessageMenu('I found a bug; is there a reward?',
|
),
|
||||||
'''
|
MessageMenu(
|
||||||
|
"I found a bug; is there a reward?",
|
||||||
|
"""
|
||||||
No.
|
No.
|
||||||
|
|
||||||
But if you are certain that it is a bug, not a feature, then you
|
But if you are certain that it is a bug, not a feature, then you
|
||||||
|
@ -83,9 +100,11 @@ class FAQMenu(Menu):
|
||||||
|
|
||||||
6. Type "restart" in Dibbler to replace the running process by a new
|
6. Type "restart" in Dibbler to replace the running process by a new
|
||||||
one using the updated files.
|
one using the updated files.
|
||||||
'''),
|
""",
|
||||||
MessageMenu('My question isn\'t listed here; what do I do?',
|
),
|
||||||
'''
|
MessageMenu(
|
||||||
|
"My question isn't listed here; what do I do?",
|
||||||
|
"""
|
||||||
DON'T PANIC.
|
DON'T PANIC.
|
||||||
|
|
||||||
Follow this procedure:
|
Follow this procedure:
|
||||||
|
@ -105,4 +124,6 @@ class FAQMenu(Menu):
|
||||||
|
|
||||||
5. Type "restart" in Dibbler to replace the running process by a new
|
5. Type "restart" in Dibbler to replace the running process by a new
|
||||||
one using the updated files.
|
one using the updated files.
|
||||||
''')]
|
""",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
|
@ -14,10 +14,10 @@ from dibbler.lib.helpers import (
|
||||||
argmax,
|
argmax,
|
||||||
)
|
)
|
||||||
|
|
||||||
exit_commands = ['exit', 'abort', 'quit', 'bye', 'eat flaming death', 'q']
|
exit_commands = ["exit", "abort", "quit", "bye", "eat flaming death", "q"]
|
||||||
help_commands = ['help', '?']
|
help_commands = ["help", "?"]
|
||||||
context_commands = ['what', '??']
|
context_commands = ["what", "??"]
|
||||||
local_help_commands = ['help!', '???']
|
local_help_commands = ["help!", "???"]
|
||||||
|
|
||||||
|
|
||||||
class ExitMenu(Exception):
|
class ExitMenu(Exception):
|
||||||
|
@ -25,10 +25,19 @@ class ExitMenu(Exception):
|
||||||
|
|
||||||
|
|
||||||
class Menu(object):
|
class Menu(object):
|
||||||
def __init__(self, name, items=None, prompt=None, end_prompt="> ",
|
def __init__(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
items=None,
|
||||||
|
prompt=None,
|
||||||
|
end_prompt="> ",
|
||||||
return_index=True,
|
return_index=True,
|
||||||
exit_msg=None, exit_confirm_msg=None, exit_disallowed_msg=None,
|
exit_msg=None,
|
||||||
help_text=None, uses_db=False):
|
exit_confirm_msg=None,
|
||||||
|
exit_disallowed_msg=None,
|
||||||
|
help_text=None,
|
||||||
|
uses_db=False,
|
||||||
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.items = items if items is not None else []
|
self.items = items if items is not None else []
|
||||||
self.prompt = prompt
|
self.prompt = prompt
|
||||||
|
@ -68,7 +77,7 @@ class Menu(object):
|
||||||
if self.context is None:
|
if self.context is None:
|
||||||
self.context = string
|
self.context = string
|
||||||
else:
|
else:
|
||||||
self.context += '\n' + string
|
self.context += "\n" + string
|
||||||
|
|
||||||
def show_context(self):
|
def show_context(self):
|
||||||
print(self.header())
|
print(self.header())
|
||||||
|
@ -93,8 +102,16 @@ class Menu(object):
|
||||||
return i
|
return i
|
||||||
return self.items[i]
|
return self.items[i]
|
||||||
|
|
||||||
def input_str(self, prompt=None, end_prompt=None, regex=None, length_range=(None, None),
|
def input_str(
|
||||||
empty_string_is_none=False, timeout=None, default=None):
|
self,
|
||||||
|
prompt=None,
|
||||||
|
end_prompt=None,
|
||||||
|
regex=None,
|
||||||
|
length_range=(None, None),
|
||||||
|
empty_string_is_none=False,
|
||||||
|
timeout=None,
|
||||||
|
default=None,
|
||||||
|
):
|
||||||
if prompt is None:
|
if prompt is None:
|
||||||
prompt = self.prompt if self.prompt is not None else ""
|
prompt = self.prompt if self.prompt is not None else ""
|
||||||
if default is not None:
|
if default is not None:
|
||||||
|
@ -114,13 +131,13 @@ class Menu(object):
|
||||||
rlist, _, _ = select([sys.stdin], [], [], timeout)
|
rlist, _, _ = select([sys.stdin], [], [], timeout)
|
||||||
if not rlist:
|
if not rlist:
|
||||||
# timeout occurred, simulate empty line
|
# timeout occurred, simulate empty line
|
||||||
result = ''
|
result = ""
|
||||||
else:
|
else:
|
||||||
result = input(prompt).strip()
|
result = input(prompt).strip()
|
||||||
else:
|
else:
|
||||||
result = input(prompt).strip()
|
result = input(prompt).strip()
|
||||||
except EOFError:
|
except EOFError:
|
||||||
print('quit')
|
print("quit")
|
||||||
self.exit_menu()
|
self.exit_menu()
|
||||||
continue
|
continue
|
||||||
if result in exit_commands:
|
if result in exit_commands:
|
||||||
|
@ -137,22 +154,26 @@ class Menu(object):
|
||||||
continue
|
continue
|
||||||
if self.special_input_options(result):
|
if self.special_input_options(result):
|
||||||
continue
|
continue
|
||||||
if empty_string_is_none and result == '':
|
if empty_string_is_none and result == "":
|
||||||
return None
|
return None
|
||||||
if default is not None and result == '':
|
if default is not None and result == "":
|
||||||
return default
|
return default
|
||||||
if regex is not None and not re.match(regex + '$', result):
|
if regex is not None and not re.match(regex + "$", result):
|
||||||
print(f'Value must match regular expression "{regex}"')
|
print(f'Value must match regular expression "{regex}"')
|
||||||
continue
|
continue
|
||||||
if length_range != (None, None):
|
if length_range != (None, None):
|
||||||
length = len(result)
|
length = len(result)
|
||||||
if ((length_range[0] and length < length_range[0]) or (length_range[1] and length > length_range[1])):
|
if (length_range[0] and length < length_range[0]) or (
|
||||||
|
length_range[1] and length > length_range[1]
|
||||||
|
):
|
||||||
if length_range[0] and length_range[1]:
|
if length_range[0] and length_range[1]:
|
||||||
print(f'Value must have length in range [{length_range[0]:d}, {length_range[1]:d}]')
|
print(
|
||||||
|
f"Value must have length in range [{length_range[0]:d}, {length_range[1]:d}]"
|
||||||
|
)
|
||||||
elif length_range[0]:
|
elif length_range[0]:
|
||||||
print(f'Value must have length at least {length_range[0]:d}')
|
print(f"Value must have length at least {length_range[0]:d}")
|
||||||
else:
|
else:
|
||||||
print(f'Value must have length at most {length_range[1]:d}')
|
print(f"Value must have length at most {length_range[1]:d}")
|
||||||
continue
|
continue
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -179,8 +200,8 @@ class Menu(object):
|
||||||
def input_choice(self, number_of_choices, prompt=None, end_prompt=None):
|
def input_choice(self, number_of_choices, prompt=None, end_prompt=None):
|
||||||
while True:
|
while True:
|
||||||
result = self.input_str(prompt, end_prompt)
|
result = self.input_str(prompt, end_prompt)
|
||||||
if result == '':
|
if result == "":
|
||||||
print('Please enter something')
|
print("Please enter something")
|
||||||
else:
|
else:
|
||||||
if result.isdigit():
|
if result.isdigit():
|
||||||
choice = int(result)
|
choice = int(result)
|
||||||
|
@ -192,9 +213,17 @@ class Menu(object):
|
||||||
self.invalid_menu_choice(result)
|
self.invalid_menu_choice(result)
|
||||||
|
|
||||||
def invalid_menu_choice(self, in_str):
|
def invalid_menu_choice(self, in_str):
|
||||||
print('Please enter a valid choice.')
|
print("Please enter a valid choice.")
|
||||||
|
|
||||||
def input_int(self, prompt=None, minimum=None, maximum=None, null_allowed=False, zero_allowed=True, default=None):
|
def input_int(
|
||||||
|
self,
|
||||||
|
prompt=None,
|
||||||
|
minimum=None,
|
||||||
|
maximum=None,
|
||||||
|
null_allowed=False,
|
||||||
|
zero_allowed=True,
|
||||||
|
default=None,
|
||||||
|
):
|
||||||
if minimum is not None and maximum is not None:
|
if minimum is not None and maximum is not None:
|
||||||
end_prompt = f"({minimum}-{maximum})>"
|
end_prompt = f"({minimum}-{maximum})>"
|
||||||
elif minimum is not None:
|
elif minimum is not None:
|
||||||
|
@ -206,15 +235,15 @@ class Menu(object):
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
result = self.input_str(prompt + end_prompt, default=default)
|
result = self.input_str(prompt + end_prompt, default=default)
|
||||||
if result == '' and null_allowed:
|
if result == "" and null_allowed:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
value = int(result)
|
value = int(result)
|
||||||
if minimum is not None and value < minimum:
|
if minimum is not None and value < minimum:
|
||||||
print(f'Value must be at least {minimum:d}')
|
print(f"Value must be at least {minimum:d}")
|
||||||
continue
|
continue
|
||||||
if maximum is not None and value > maximum:
|
if maximum is not None and value > maximum:
|
||||||
print(f'Value must be at most {maximum:d}')
|
print(f"Value must be at most {maximum:d}")
|
||||||
continue
|
continue
|
||||||
if not zero_allowed and value == 0:
|
if not zero_allowed and value == 0:
|
||||||
print("Value cannot be zero")
|
print("Value cannot be zero")
|
||||||
|
@ -230,7 +259,7 @@ class Menu(object):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def retrieve_user(self, search_str):
|
def retrieve_user(self, search_str):
|
||||||
return self.search_ui(search_user, search_str, 'user')
|
return self.search_ui(search_user, search_str, "user")
|
||||||
|
|
||||||
def input_product(self, prompt=None, end_prompt=None):
|
def input_product(self, prompt=None, end_prompt=None):
|
||||||
product = None
|
product = None
|
||||||
|
@ -239,47 +268,73 @@ class Menu(object):
|
||||||
return product
|
return product
|
||||||
|
|
||||||
def retrieve_product(self, search_str):
|
def retrieve_product(self, search_str):
|
||||||
return self.search_ui(search_product, search_str, 'product')
|
return self.search_ui(search_product, search_str, "product")
|
||||||
|
|
||||||
def input_thing(self, prompt=None, end_prompt=None, permitted_things=('user', 'product'),
|
def input_thing(
|
||||||
add_nonexisting=(), empty_input_permitted=False, find_hidden_products=True):
|
self,
|
||||||
|
prompt=None,
|
||||||
|
end_prompt=None,
|
||||||
|
permitted_things=("user", "product"),
|
||||||
|
add_nonexisting=(),
|
||||||
|
empty_input_permitted=False,
|
||||||
|
find_hidden_products=True,
|
||||||
|
):
|
||||||
result = None
|
result = None
|
||||||
while result is None:
|
while result is None:
|
||||||
search_str = self.input_str(prompt, end_prompt)
|
search_str = self.input_str(prompt, end_prompt)
|
||||||
if search_str == '' and empty_input_permitted:
|
if search_str == "" and empty_input_permitted:
|
||||||
return None
|
return None
|
||||||
result = self.search_for_thing(search_str, permitted_things, add_nonexisting, find_hidden_products)
|
result = self.search_for_thing(
|
||||||
|
search_str, permitted_things, add_nonexisting, find_hidden_products
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def input_multiple(self, prompt=None, end_prompt=None, permitted_things=('user', 'product'),
|
def input_multiple(
|
||||||
add_nonexisting=(), empty_input_permitted=False, find_hidden_products=True):
|
self,
|
||||||
|
prompt=None,
|
||||||
|
end_prompt=None,
|
||||||
|
permitted_things=("user", "product"),
|
||||||
|
add_nonexisting=(),
|
||||||
|
empty_input_permitted=False,
|
||||||
|
find_hidden_products=True,
|
||||||
|
):
|
||||||
result = None
|
result = None
|
||||||
num = 0
|
num = 0
|
||||||
while result is None:
|
while result is None:
|
||||||
search_str = self.input_str(prompt, end_prompt)
|
search_str = self.input_str(prompt, end_prompt)
|
||||||
search_lst = search_str.split(" ")
|
search_lst = search_str.split(" ")
|
||||||
if search_str == '' and empty_input_permitted:
|
if search_str == "" and empty_input_permitted:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
result = self.search_for_thing(search_str, permitted_things, add_nonexisting, find_hidden_products)
|
result = self.search_for_thing(
|
||||||
|
search_str, permitted_things, add_nonexisting, find_hidden_products
|
||||||
|
)
|
||||||
num = 1
|
num = 1
|
||||||
|
|
||||||
if (result is None) and (len(search_lst) > 1):
|
if (result is None) and (len(search_lst) > 1):
|
||||||
print('Interpreting input as "<number> <product>"')
|
print('Interpreting input as "<number> <product>"')
|
||||||
try:
|
try:
|
||||||
num = int(search_lst[0])
|
num = int(search_lst[0])
|
||||||
result = self.search_for_thing(" ".join(search_lst[1:]), permitted_things, add_nonexisting,
|
result = self.search_for_thing(
|
||||||
find_hidden_products)
|
" ".join(search_lst[1:]),
|
||||||
|
permitted_things,
|
||||||
|
add_nonexisting,
|
||||||
|
find_hidden_products,
|
||||||
|
)
|
||||||
# Her kan det legges inn en except ValueError,
|
# Her kan det legges inn en except ValueError,
|
||||||
# men da blir det fort mye plaging av brukeren
|
# men da blir det fort mye plaging av brukeren
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return result, num
|
return result, num
|
||||||
|
|
||||||
def search_for_thing(self, search_str, permitted_things=('user', 'product'),
|
def search_for_thing(
|
||||||
add_non_existing=(), find_hidden_products=True):
|
self,
|
||||||
search_fun = {'user': search_user,
|
search_str,
|
||||||
'product': search_product}
|
permitted_things=("user", "product"),
|
||||||
|
add_non_existing=(),
|
||||||
|
find_hidden_products=True,
|
||||||
|
):
|
||||||
|
search_fun = {"user": search_user, "product": search_product}
|
||||||
results = {}
|
results = {}
|
||||||
result_values = {}
|
result_values = {}
|
||||||
for thing in permitted_things:
|
for thing in permitted_things:
|
||||||
|
@ -287,8 +342,12 @@ class Menu(object):
|
||||||
result_values[thing] = self.search_result_value(results[thing])
|
result_values[thing] = self.search_result_value(results[thing])
|
||||||
selected_thing = argmax(result_values)
|
selected_thing = argmax(result_values)
|
||||||
if not results[selected_thing]:
|
if not results[selected_thing]:
|
||||||
thing_for_type = {'card': 'user', 'username': 'user',
|
thing_for_type = {
|
||||||
'bar_code': 'product', 'rfid': 'rfid'}
|
"card": "user",
|
||||||
|
"username": "user",
|
||||||
|
"bar_code": "product",
|
||||||
|
"rfid": "rfid",
|
||||||
|
}
|
||||||
type_guess = guess_data_type(search_str)
|
type_guess = guess_data_type(search_str)
|
||||||
if type_guess is not None and thing_for_type[type_guess] in add_non_existing:
|
if type_guess is not None and thing_for_type[type_guess] in add_non_existing:
|
||||||
return self.search_add(search_str)
|
return self.search_add(search_str)
|
||||||
|
@ -310,32 +369,39 @@ class Menu(object):
|
||||||
|
|
||||||
def search_add(self, string):
|
def search_add(self, string):
|
||||||
type_guess = guess_data_type(string)
|
type_guess = guess_data_type(string)
|
||||||
if type_guess == 'username':
|
if type_guess == "username":
|
||||||
print(f'"{string}" looks like a username, but no such user exists.')
|
print(f'"{string}" looks like a username, but no such user exists.')
|
||||||
if self.confirm(f'Create user {string}?'):
|
if self.confirm(f"Create user {string}?"):
|
||||||
user = User(string, None)
|
user = User(string, None)
|
||||||
self.session.add(user)
|
self.session.add(user)
|
||||||
return user
|
return user
|
||||||
return None
|
return None
|
||||||
if type_guess == 'card':
|
if type_guess == "card":
|
||||||
selector = Selector(f'"{string}" looks like a card number, but no user with that card number exists.',
|
selector = Selector(
|
||||||
[('create', f'Create user with card number {string}'),
|
f'"{string}" looks like a card number, but no user with that card number exists.',
|
||||||
('set', f'Set card number of an existing user to {string}')])
|
[
|
||||||
|
("create", f"Create user with card number {string}"),
|
||||||
|
("set", f"Set card number of an existing user to {string}"),
|
||||||
|
],
|
||||||
|
)
|
||||||
selection = selector.execute()
|
selection = selector.execute()
|
||||||
if selection == 'create':
|
if selection == "create":
|
||||||
username = self.input_str('Username for new user (should be same as PVV username)',
|
username = self.input_str(
|
||||||
User.name_re, (1, 10))
|
"Username for new user (should be same as PVV username)",
|
||||||
|
User.name_re,
|
||||||
|
(1, 10),
|
||||||
|
)
|
||||||
user = User(username, string)
|
user = User(username, string)
|
||||||
self.session.add(user)
|
self.session.add(user)
|
||||||
return user
|
return user
|
||||||
if selection == 'set':
|
if selection == "set":
|
||||||
user = self.input_user('User to set card number for')
|
user = self.input_user("User to set card number for")
|
||||||
old_card = user.card
|
old_card = user.card
|
||||||
user.card = string
|
user.card = string
|
||||||
print(f'Card number of {user.name} set to {string} (was {old_card})')
|
print(f"Card number of {user.name} set to {string} (was {old_card})")
|
||||||
return user
|
return user
|
||||||
return None
|
return None
|
||||||
if type_guess == 'bar_code':
|
if type_guess == "bar_code":
|
||||||
print(f'"{string}" looks like the bar code for a product, but no such product exists.')
|
print(f'"{string}" looks like the bar code for a product, but no such product exists.')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -356,13 +422,14 @@ class Menu(object):
|
||||||
return None
|
return None
|
||||||
limit = 9
|
limit = 9
|
||||||
if len(result) > limit:
|
if len(result) > limit:
|
||||||
select_header = f'{len(result):d} {thing}s matching "{search_str}"; showing first {limit:d}'
|
select_header = (
|
||||||
|
f'{len(result):d} {thing}s matching "{search_str}"; showing first {limit:d}'
|
||||||
|
)
|
||||||
select_items = result[:limit]
|
select_items = result[:limit]
|
||||||
else:
|
else:
|
||||||
select_header = f'{len(result):d} {thing}s matching "{search_str}"'
|
select_header = f'{len(result):d} {thing}s matching "{search_str}"'
|
||||||
select_items = result
|
select_items = result
|
||||||
selector = Selector(select_header, items=select_items,
|
selector = Selector(select_header, items=select_items, return_index=False)
|
||||||
return_index=False)
|
|
||||||
return selector.execute()
|
return selector.execute()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -377,11 +444,12 @@ class Menu(object):
|
||||||
print(self.header())
|
print(self.header())
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
self.input_str('.', end_prompt="")
|
self.input_str(".", end_prompt="")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def general_help():
|
def general_help():
|
||||||
print('''
|
print(
|
||||||
|
"""
|
||||||
DIBBLER HELP
|
DIBBLER HELP
|
||||||
|
|
||||||
The following commands are recognized (almost) everywhere:
|
The following commands are recognized (almost) everywhere:
|
||||||
|
@ -402,14 +470,15 @@ class Menu(object):
|
||||||
of money PVVVV owes the user. This value decreases with the
|
of money PVVVV owes the user. This value decreases with the
|
||||||
appropriate amount when you register a purchase, and you may increase
|
appropriate amount when you register a purchase, and you may increase
|
||||||
it by putting money in the box and using the "Adjust credit" menu.
|
it by putting money in the box and using the "Adjust credit" menu.
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
def local_help(self):
|
def local_help(self):
|
||||||
if self.help_text is None:
|
if self.help_text is None:
|
||||||
print('no help here')
|
print("no help here")
|
||||||
else:
|
else:
|
||||||
print('')
|
print("")
|
||||||
print(f'Help for {self.header()}:')
|
print(f"Help for {self.header()}:")
|
||||||
print(self.help_text)
|
print(self.help_text)
|
||||||
|
|
||||||
def execute(self, **kwargs):
|
def execute(self, **kwargs):
|
||||||
|
@ -431,7 +500,7 @@ class Menu(object):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
self.set_context(None)
|
self.set_context(None)
|
||||||
if len(self.items) == 0:
|
if len(self.items) == 0:
|
||||||
self.printc('(empty menu)')
|
self.printc("(empty menu)")
|
||||||
self.pause()
|
self.pause()
|
||||||
return None
|
return None
|
||||||
for i in range(len(self.items)):
|
for i in range(len(self.items)):
|
||||||
|
@ -452,37 +521,52 @@ class MessageMenu(Menu):
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
print('')
|
print("")
|
||||||
print(self.message)
|
print(self.message)
|
||||||
if self.pause_after_message:
|
if self.pause_after_message:
|
||||||
self.pause()
|
self.pause()
|
||||||
|
|
||||||
|
|
||||||
class ConfirmMenu(Menu):
|
class ConfirmMenu(Menu):
|
||||||
def __init__(self, prompt='confirm? ', end_prompt=": ", default=None, timeout=0):
|
def __init__(self, prompt="confirm? ", end_prompt=": ", default=None, timeout=0):
|
||||||
Menu.__init__(self, 'question', prompt=prompt, end_prompt=end_prompt,
|
Menu.__init__(
|
||||||
exit_disallowed_msg='Please answer yes or no')
|
self,
|
||||||
|
"question",
|
||||||
|
prompt=prompt,
|
||||||
|
end_prompt=end_prompt,
|
||||||
|
exit_disallowed_msg="Please answer yes or no",
|
||||||
|
)
|
||||||
self.default = default
|
self.default = default
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
options = {True: '[y]/n', False: 'y/[n]', None: 'y/n'}[self.default]
|
options = {True: "[y]/n", False: "y/[n]", None: "y/n"}[self.default]
|
||||||
while True:
|
while True:
|
||||||
result = self.input_str(f'{self.prompt} ({options})', end_prompt=": ", timeout=self.timeout)
|
result = self.input_str(
|
||||||
|
f"{self.prompt} ({options})", end_prompt=": ", timeout=self.timeout
|
||||||
|
)
|
||||||
result = result.lower().strip()
|
result = result.lower().strip()
|
||||||
if result in ['y', 'yes']:
|
if result in ["y", "yes"]:
|
||||||
return True
|
return True
|
||||||
elif result in ['n', 'no']:
|
elif result in ["n", "no"]:
|
||||||
return False
|
return False
|
||||||
elif self.default is not None and result == '':
|
elif self.default is not None and result == "":
|
||||||
return self.default
|
return self.default
|
||||||
else:
|
else:
|
||||||
print('Please answer yes or no')
|
print("Please answer yes or no")
|
||||||
|
|
||||||
|
|
||||||
class Selector(Menu):
|
class Selector(Menu):
|
||||||
def __init__(self, name, items=None, prompt='select', return_index=True, exit_msg=None, exit_confirm_msg=None,
|
def __init__(
|
||||||
help_text=None):
|
self,
|
||||||
|
name,
|
||||||
|
items=None,
|
||||||
|
prompt="select",
|
||||||
|
return_index=True,
|
||||||
|
exit_msg=None,
|
||||||
|
exit_confirm_msg=None,
|
||||||
|
help_text=None,
|
||||||
|
):
|
||||||
if items is None:
|
if items is None:
|
||||||
items = []
|
items = []
|
||||||
Menu.__init__(self, name, items, prompt, return_index=return_index, exit_msg=exit_msg)
|
Menu.__init__(self, name, items, prompt, return_index=return_index, exit_msg=exit_msg)
|
||||||
|
@ -495,9 +579,9 @@ class Selector(Menu):
|
||||||
|
|
||||||
def local_help(self):
|
def local_help(self):
|
||||||
if self.help_text is None:
|
if self.help_text is None:
|
||||||
print('This is a selection menu. Enter one of the listed numbers, or')
|
print("This is a selection menu. Enter one of the listed numbers, or")
|
||||||
print('\'exit\' to go out and do something else.')
|
print("'exit' to go out and do something else.")
|
||||||
else:
|
else:
|
||||||
print('')
|
print("")
|
||||||
print(f'Help for selector ({self.name}):')
|
print(f"Help for selector ({self.name}):")
|
||||||
print(self.help_text)
|
print(self.help_text)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
@ -10,8 +9,9 @@ from .buymenu import BuyMenu
|
||||||
from .faq import FAQMenu
|
from .faq import FAQMenu
|
||||||
from .helpermenus import Menu
|
from .helpermenus import Menu
|
||||||
|
|
||||||
faq_commands = ['faq']
|
faq_commands = ["faq"]
|
||||||
restart_commands = ['restart']
|
restart_commands = ["restart"]
|
||||||
|
|
||||||
|
|
||||||
def restart():
|
def restart():
|
||||||
# Does not work if the script is not executable, or if it was
|
# Does not work if the script is not executable, or if it was
|
||||||
|
@ -41,18 +41,24 @@ class MainMenu(Menu):
|
||||||
FAQMenu().execute()
|
FAQMenu().execute()
|
||||||
return True
|
return True
|
||||||
if result in restart_commands:
|
if result in restart_commands:
|
||||||
if self.confirm('Restart Dibbler?'):
|
if self.confirm("Restart Dibbler?"):
|
||||||
restart()
|
restart()
|
||||||
pass
|
pass
|
||||||
return True
|
return True
|
||||||
elif result == 'c':
|
elif result == "c":
|
||||||
os.system('echo -e "\033[' + str(random.randint(40, 49)) + ';' + str(random.randint(30, 37)) + ';5m"')
|
os.system(
|
||||||
os.system('clear')
|
'echo -e "\033['
|
||||||
|
+ str(random.randint(40, 49))
|
||||||
|
+ ";"
|
||||||
|
+ str(random.randint(30, 37))
|
||||||
|
+ ';5m"'
|
||||||
|
)
|
||||||
|
os.system("clear")
|
||||||
self.show_context()
|
self.show_context()
|
||||||
return True
|
return True
|
||||||
elif result == 'cs':
|
elif result == "cs":
|
||||||
os.system('echo -e "\033[0m"')
|
os.system('echo -e "\033[0m"')
|
||||||
os.system('clear')
|
os.system("clear")
|
||||||
self.show_context()
|
self.show_context()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -9,24 +9,21 @@ from .helpermenus import Menu, Selector
|
||||||
|
|
||||||
class TransferMenu(Menu):
|
class TransferMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Transfer credit between users',
|
Menu.__init__(self, "Transfer credit between users", uses_db=True)
|
||||||
uses_db=True)
|
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
amount = self.input_int('Transfer amount', 1, 100000)
|
amount = self.input_int("Transfer amount", 1, 100000)
|
||||||
self.set_context(f'Transferring {amount:d} kr', display=False)
|
self.set_context(f"Transferring {amount:d} kr", display=False)
|
||||||
user1 = self.input_user('From user')
|
user1 = self.input_user("From user")
|
||||||
self.add_to_context(f' from {user1.name}')
|
self.add_to_context(f" from {user1.name}")
|
||||||
user2 = self.input_user('To user')
|
user2 = self.input_user("To user")
|
||||||
self.add_to_context(f' to {user2.name}')
|
self.add_to_context(f" to {user2.name}")
|
||||||
comment = self.input_str('Comment')
|
comment = self.input_str("Comment")
|
||||||
self.add_to_context(f' (comment) {user2.name}')
|
self.add_to_context(f" (comment) {user2.name}")
|
||||||
|
|
||||||
t1 = Transaction(user1, amount,
|
t1 = Transaction(user1, amount, f'transfer to {user2.name} "{comment}"')
|
||||||
f'transfer to {user2.name} "{comment}"')
|
t2 = Transaction(user2, -amount, f'transfer from {user1.name} "{comment}"')
|
||||||
t2 = Transaction(user2, -amount,
|
|
||||||
f'transfer from {user1.name} "{comment}"')
|
|
||||||
t1.perform_transaction()
|
t1.perform_transaction()
|
||||||
t2.perform_transaction()
|
t2.perform_transaction()
|
||||||
self.session.add(t1)
|
self.session.add(t1)
|
||||||
|
@ -38,35 +35,43 @@ class TransferMenu(Menu):
|
||||||
print(f"User {user2}'s credit is now {user2.credit:d} kr")
|
print(f"User {user2}'s credit is now {user2.credit:d} kr")
|
||||||
print(f"Comment: {comment}")
|
print(f"Comment: {comment}")
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not perform transfer: {e}')
|
print(f"Could not perform transfer: {e}")
|
||||||
# self.pause()
|
# self.pause()
|
||||||
|
|
||||||
|
|
||||||
class ShowUserMenu(Menu):
|
class ShowUserMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Show user', uses_db=True)
|
Menu.__init__(self, "Show user", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
user = self.input_user('User name, card number or RFID')
|
user = self.input_user("User name, card number or RFID")
|
||||||
print(f'User name: {user.name}')
|
print(f"User name: {user.name}")
|
||||||
print(f'Card number: {user.card}')
|
print(f"Card number: {user.card}")
|
||||||
print(f'RFID: {user.rfid}')
|
print(f"RFID: {user.rfid}")
|
||||||
print(f'Credit: {user.credit} kr')
|
print(f"Credit: {user.credit} kr")
|
||||||
selector = Selector(f'What do you want to know about {user.name}?',
|
selector = Selector(
|
||||||
items=[('transactions', 'Recent transactions (List of last ' + str(
|
f"What do you want to know about {user.name}?",
|
||||||
config.getint('limits', 'user_recent_transaction_limit')) + ')'),
|
items=[
|
||||||
('products', f'Which products {user.name} has bought, and how many'),
|
(
|
||||||
('transactions-all', 'Everything (List of all transactions)')])
|
"transactions",
|
||||||
|
"Recent transactions (List of last "
|
||||||
|
+ str(config.getint("limits", "user_recent_transaction_limit"))
|
||||||
|
+ ")",
|
||||||
|
),
|
||||||
|
("products", f"Which products {user.name} has bought, and how many"),
|
||||||
|
("transactions-all", "Everything (List of all transactions)"),
|
||||||
|
],
|
||||||
|
)
|
||||||
what = selector.execute()
|
what = selector.execute()
|
||||||
if what == 'transactions':
|
if what == "transactions":
|
||||||
self.print_transactions(user, config.getint('limits', 'user_recent_transaction_limit'))
|
self.print_transactions(user, config.getint("limits", "user_recent_transaction_limit"))
|
||||||
elif what == 'products':
|
elif what == "products":
|
||||||
self.print_purchased_products(user)
|
self.print_purchased_products(user)
|
||||||
elif what == 'transactions-all':
|
elif what == "transactions-all":
|
||||||
self.print_transactions(user)
|
self.print_transactions(user)
|
||||||
else:
|
else:
|
||||||
print('What what?')
|
print("What what?")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def print_transactions(user, limit=None):
|
def print_transactions(user, limit=None):
|
||||||
|
@ -77,7 +82,7 @@ class ShowUserMenu(Menu):
|
||||||
string = f"{user.name}'s transactions ({num_trans:d}):\n"
|
string = f"{user.name}'s transactions ({num_trans:d}):\n"
|
||||||
else:
|
else:
|
||||||
string = f"{user.name}'s transactions ({num_trans:d}, showing only last {limit:d}):\n"
|
string = f"{user.name}'s transactions ({num_trans:d}, showing only last {limit:d}):\n"
|
||||||
for t in user.transactions[-1:-limit - 1:-1]:
|
for t in user.transactions[-1 : -limit - 1 : -1]:
|
||||||
string += f" * {t.time.isoformat(' ')}: {'in' if t.amount < 0 else 'out'} {abs(t.amount)} kr, "
|
string += f" * {t.time.isoformat(' ')}: {'in' if t.amount < 0 else 'out'} {abs(t.amount)} kr, "
|
||||||
if t.purchase:
|
if t.purchase:
|
||||||
products = []
|
products = []
|
||||||
|
@ -88,14 +93,14 @@ class ShowUserMenu(Menu):
|
||||||
amount = ""
|
amount = ""
|
||||||
product = f"{amount}{entry.product.name}"
|
product = f"{amount}{entry.product.name}"
|
||||||
products.append(product)
|
products.append(product)
|
||||||
string += 'purchase ('
|
string += "purchase ("
|
||||||
string += ', '.join(products)
|
string += ", ".join(products)
|
||||||
string += ')'
|
string += ")"
|
||||||
if t.penalty > 1:
|
if t.penalty > 1:
|
||||||
string += f' * {t.penalty:d}x penalty applied'
|
string += f" * {t.penalty:d}x penalty applied"
|
||||||
else:
|
else:
|
||||||
string += t.description
|
string += t.description
|
||||||
string += '\n'
|
string += "\n"
|
||||||
less(string)
|
less(string)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -108,54 +113,55 @@ class ShowUserMenu(Menu):
|
||||||
products.append((product, count))
|
products.append((product, count))
|
||||||
num_products = len(products)
|
num_products = len(products)
|
||||||
if num_products == 0:
|
if num_products == 0:
|
||||||
print('No products purchased yet')
|
print("No products purchased yet")
|
||||||
else:
|
else:
|
||||||
text = ''
|
text = ""
|
||||||
text += 'Products purchased:\n'
|
text += "Products purchased:\n"
|
||||||
for product, count in products:
|
for product, count in products:
|
||||||
text += f'{product.name:<47} {count:>3}\n'
|
text += f"{product.name:<47} {count:>3}\n"
|
||||||
less(text)
|
less(text)
|
||||||
|
|
||||||
|
|
||||||
class UserListMenu(Menu):
|
class UserListMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'User list', uses_db=True)
|
Menu.__init__(self, "User list", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
user_list = self.session.query(User).all()
|
user_list = self.session.query(User).all()
|
||||||
total_credit = self.session.query(sqlalchemy.func.sum(User.credit)).first()[0]
|
total_credit = self.session.query(sqlalchemy.func.sum(User.credit)).first()[0]
|
||||||
|
|
||||||
line_format = '%-12s | %6s\n'
|
line_format = "%-12s | %6s\n"
|
||||||
hline = '---------------------\n'
|
hline = "---------------------\n"
|
||||||
text = ''
|
text = ""
|
||||||
text += line_format % ('username', 'credit')
|
text += line_format % ("username", "credit")
|
||||||
text += hline
|
text += hline
|
||||||
for user in user_list:
|
for user in user_list:
|
||||||
text += line_format % (user.name, user.credit)
|
text += line_format % (user.name, user.credit)
|
||||||
text += hline
|
text += hline
|
||||||
text += line_format % ('total credit', total_credit)
|
text += line_format % ("total credit", total_credit)
|
||||||
less(text)
|
less(text)
|
||||||
|
|
||||||
|
|
||||||
class AdjustCreditMenu(Menu):
|
class AdjustCreditMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Adjust credit', uses_db=True)
|
Menu.__init__(self, "Adjust credit", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
user = self.input_user('User')
|
user = self.input_user("User")
|
||||||
print(f"User {user.name}'s credit is {user.credit:d} kr")
|
print(f"User {user.name}'s credit is {user.credit:d} kr")
|
||||||
self.set_context(f'Adjusting credit for user {user.name}', display=False)
|
self.set_context(f"Adjusting credit for user {user.name}", display=False)
|
||||||
print('(Note on sign convention: Enter a positive amount here if you have')
|
print("(Note on sign convention: Enter a positive amount here if you have")
|
||||||
print('added money to the PVVVV money box, a negative amount if you have')
|
print("added money to the PVVVV money box, a negative amount if you have")
|
||||||
print('taken money from it)')
|
print("taken money from it)")
|
||||||
amount = self.input_int('Add amount', -100000, 100000)
|
amount = self.input_int("Add amount", -100000, 100000)
|
||||||
print('(The "log message" will show up in the transaction history in the')
|
print('(The "log message" will show up in the transaction history in the')
|
||||||
print('"Show user" menu. It is not necessary to enter a message, but it')
|
print('"Show user" menu. It is not necessary to enter a message, but it')
|
||||||
print('might be useful to help you remember why you adjusted the credit)')
|
print("might be useful to help you remember why you adjusted the credit)")
|
||||||
description = self.input_str('Log message', length_range=(0, 50))
|
description = self.input_str("Log message", length_range=(0, 50))
|
||||||
if description == '':
|
if description == "":
|
||||||
description = 'manually adjusted credit'
|
description = "manually adjusted credit"
|
||||||
transaction = Transaction(user, -amount, description)
|
transaction = Transaction(user, -amount, description)
|
||||||
transaction.perform_transaction()
|
transaction.perform_transaction()
|
||||||
self.session.add(transaction)
|
self.session.add(transaction)
|
||||||
|
@ -163,40 +169,56 @@ class AdjustCreditMenu(Menu):
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
print(f"User {user.name}'s credit is now {user.credit:d} kr")
|
print(f"User {user.name}'s credit is now {user.credit:d} kr")
|
||||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||||
print(f'Could not store transaction: {e}')
|
print(f"Could not store transaction: {e}")
|
||||||
# self.pause()
|
# self.pause()
|
||||||
|
|
||||||
|
|
||||||
class ProductListMenu(Menu):
|
class ProductListMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Product list', uses_db=True)
|
Menu.__init__(self, "Product list", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
text = ''
|
text = ""
|
||||||
product_list = self.session.query(Product).filter(Product.hidden.is_(False)).order_by(Product.stock.desc())
|
product_list = (
|
||||||
|
self.session.query(Product)
|
||||||
|
.filter(Product.hidden.is_(False))
|
||||||
|
.order_by(Product.stock.desc())
|
||||||
|
)
|
||||||
total_value = 0
|
total_value = 0
|
||||||
for p in product_list:
|
for p in product_list:
|
||||||
total_value += p.price * p.stock
|
total_value += p.price * p.stock
|
||||||
line_format = '%-15s | %5s | %-' + str(Product.name_length) + 's | %5s \n'
|
line_format = "%-15s | %5s | %-" + str(Product.name_length) + "s | %5s \n"
|
||||||
text += line_format % ('bar code', 'price', 'name', 'stock')
|
text += line_format % ("bar code", "price", "name", "stock")
|
||||||
text += 78 * '-' + '\n'
|
text += 78 * "-" + "\n"
|
||||||
for p in product_list:
|
for p in product_list:
|
||||||
text += line_format % (p.bar_code, p.price, p.name, p.stock)
|
text += line_format % (p.bar_code, p.price, p.name, p.stock)
|
||||||
text += 78 * '-' + '\n'
|
text += 78 * "-" + "\n"
|
||||||
text += line_format % ('Total value', total_value, '', '',)
|
text += line_format % (
|
||||||
|
"Total value",
|
||||||
|
total_value,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
less(text)
|
less(text)
|
||||||
|
|
||||||
|
|
||||||
class ProductSearchMenu(Menu):
|
class ProductSearchMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Product search', uses_db=True)
|
Menu.__init__(self, "Product search", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
self.set_context('Enter (part of) product name or bar code')
|
self.set_context("Enter (part of) product name or bar code")
|
||||||
product = self.input_product()
|
product = self.input_product()
|
||||||
print('Result: %s, price: %d kr, bar code: %s, stock: %d, hidden: %s' % (product.name, product.price,
|
print(
|
||||||
product.bar_code, product.stock,
|
"Result: %s, price: %d kr, bar code: %s, stock: %d, hidden: %s"
|
||||||
("Y" if product.hidden else "N")))
|
% (
|
||||||
|
product.name,
|
||||||
|
product.price,
|
||||||
|
product.bar_code,
|
||||||
|
product.stock,
|
||||||
|
("Y" if product.hidden else "N"),
|
||||||
|
)
|
||||||
|
)
|
||||||
# self.pause()
|
# self.pause()
|
||||||
|
|
|
@ -9,17 +9,17 @@ from .helpermenus import Menu
|
||||||
|
|
||||||
class PrintLabelMenu(Menu):
|
class PrintLabelMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Print a label', uses_db=True)
|
Menu.__init__(self, "Print a label", uses_db=True)
|
||||||
self.help_text = '''
|
self.help_text = """
|
||||||
Prints out a product bar code on the printer
|
Prints out a product bar code on the printer
|
||||||
|
|
||||||
Put it up somewhere in the vicinity.
|
Put it up somewhere in the vicinity.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
|
|
||||||
thing = self.input_thing('Product/User')
|
thing = self.input_thing("Product/User")
|
||||||
|
|
||||||
if isinstance(thing, Product):
|
if isinstance(thing, Product):
|
||||||
if re.match(r"^[0-9]{13}$", thing.bar_code):
|
if re.match(r"^[0-9]{13}$", thing.bar_code):
|
||||||
|
@ -32,14 +32,14 @@ Put it up somewhere in the vicinity.
|
||||||
thing.bar_code,
|
thing.bar_code,
|
||||||
thing.name,
|
thing.name,
|
||||||
barcode_type=bar_type,
|
barcode_type=bar_type,
|
||||||
rotate=config.getboolean('printer', 'rotate'),
|
rotate=config.getboolean("printer", "rotate"),
|
||||||
printer_type="QL-700",
|
printer_type="QL-700",
|
||||||
label_type=config.get('printer', 'label_type'),
|
label_type=config.get("printer", "label_type"),
|
||||||
)
|
)
|
||||||
elif isinstance(thing, User):
|
elif isinstance(thing, User):
|
||||||
print_name_label(
|
print_name_label(
|
||||||
text=thing.name,
|
text=thing.name,
|
||||||
label_type=config.get('printer', 'label_type'),
|
label_type=config.get("printer", "label_type"),
|
||||||
rotate=config.getboolean('printer', 'rotate'),
|
rotate=config.getboolean("printer", "rotate"),
|
||||||
printer_type="QL-700"
|
printer_type="QL-700",
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,30 +6,40 @@ from dibbler.lib.statistikkHelpers import statisticsTextOnly
|
||||||
|
|
||||||
from .helpermenus import Menu
|
from .helpermenus import Menu
|
||||||
|
|
||||||
__all__ = ["ProductPopularityMenu", "ProductRevenueMenu", "BalanceMenu", "LoggedStatisticsMenu"]
|
__all__ = [
|
||||||
|
"ProductPopularityMenu",
|
||||||
|
"ProductRevenueMenu",
|
||||||
|
"BalanceMenu",
|
||||||
|
"LoggedStatisticsMenu",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ProductPopularityMenu(Menu):
|
class ProductPopularityMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Products by popularity', uses_db=True)
|
Menu.__init__(self, "Products by popularity", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
text = ''
|
text = ""
|
||||||
sub = \
|
sub = (
|
||||||
self.session.query(PurchaseEntry.product_id,
|
self.session.query(
|
||||||
func.sum(PurchaseEntry.amount).label('purchase_count')) \
|
PurchaseEntry.product_id,
|
||||||
.filter(PurchaseEntry.amount > 0).group_by(PurchaseEntry.product_id) \
|
func.sum(PurchaseEntry.amount).label("purchase_count"),
|
||||||
|
)
|
||||||
|
.filter(PurchaseEntry.amount > 0)
|
||||||
|
.group_by(PurchaseEntry.product_id)
|
||||||
.subquery()
|
.subquery()
|
||||||
product_list = \
|
)
|
||||||
self.session.query(Product, sub.c.purchase_count) \
|
product_list = (
|
||||||
.outerjoin((sub, Product.product_id == sub.c.product_id)) \
|
self.session.query(Product, sub.c.purchase_count)
|
||||||
.order_by(desc(sub.c.purchase_count)) \
|
.outerjoin((sub, Product.product_id == sub.c.product_id))
|
||||||
.filter(sub.c.purchase_count is not None) \
|
.order_by(desc(sub.c.purchase_count))
|
||||||
|
.filter(sub.c.purchase_count is not None)
|
||||||
.all()
|
.all()
|
||||||
line_format = '{0:10s} | {1:>45s}\n'
|
)
|
||||||
text += line_format.format('items sold', 'product')
|
line_format = "{0:10s} | {1:>45s}\n"
|
||||||
text += '-' * (31 + Product.name_length) + '\n'
|
text += line_format.format("items sold", "product")
|
||||||
|
text += "-" * (31 + Product.name_length) + "\n"
|
||||||
for product, number in product_list:
|
for product, number in product_list:
|
||||||
if number is None:
|
if number is None:
|
||||||
continue
|
continue
|
||||||
|
@ -39,64 +49,78 @@ class ProductPopularityMenu(Menu):
|
||||||
|
|
||||||
class ProductRevenueMenu(Menu):
|
class ProductRevenueMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Products by revenue', uses_db=True)
|
Menu.__init__(self, "Products by revenue", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
text = ''
|
text = ""
|
||||||
sub = \
|
sub = (
|
||||||
self.session.query(PurchaseEntry.product_id,
|
self.session.query(
|
||||||
func.sum(PurchaseEntry.amount).label('purchase_count')) \
|
PurchaseEntry.product_id,
|
||||||
.filter(PurchaseEntry.amount > 0).group_by(PurchaseEntry.product_id) \
|
func.sum(PurchaseEntry.amount).label("purchase_count"),
|
||||||
|
)
|
||||||
|
.filter(PurchaseEntry.amount > 0)
|
||||||
|
.group_by(PurchaseEntry.product_id)
|
||||||
.subquery()
|
.subquery()
|
||||||
product_list = \
|
)
|
||||||
self.session.query(Product, sub.c.purchase_count) \
|
product_list = (
|
||||||
.outerjoin((sub, Product.product_id == sub.c.product_id)) \
|
self.session.query(Product, sub.c.purchase_count)
|
||||||
.order_by(desc(sub.c.purchase_count * Product.price)) \
|
.outerjoin((sub, Product.product_id == sub.c.product_id))
|
||||||
.filter(sub.c.purchase_count is not None) \
|
.order_by(desc(sub.c.purchase_count * Product.price))
|
||||||
|
.filter(sub.c.purchase_count is not None)
|
||||||
.all()
|
.all()
|
||||||
line_format = '{0:7s} | {1:10s} | {2:6s} | {3:>45s}\n'
|
)
|
||||||
text += line_format.format('revenue', 'items sold', 'price', 'product')
|
line_format = "{0:7s} | {1:10s} | {2:6s} | {3:>45s}\n"
|
||||||
text += '-' * (31 + Product.name_length) + '\n'
|
text += line_format.format("revenue", "items sold", "price", "product")
|
||||||
|
text += "-" * (31 + Product.name_length) + "\n"
|
||||||
for product, number in product_list:
|
for product, number in product_list:
|
||||||
if number is None:
|
if number is None:
|
||||||
continue
|
continue
|
||||||
text += line_format.format(str(number * product.price), str(number), str(product.price), product.name)
|
text += line_format.format(
|
||||||
|
str(number * product.price),
|
||||||
|
str(number),
|
||||||
|
str(product.price),
|
||||||
|
product.name,
|
||||||
|
)
|
||||||
less(text)
|
less(text)
|
||||||
|
|
||||||
|
|
||||||
class BalanceMenu(Menu):
|
class BalanceMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Total balance of PVVVV', uses_db=True)
|
Menu.__init__(self, "Total balance of PVVVV", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
self.print_header()
|
self.print_header()
|
||||||
text = ''
|
text = ""
|
||||||
total_value = 0
|
total_value = 0
|
||||||
product_list = self.session.query(Product).filter(Product.stock > 0).all()
|
product_list = self.session.query(Product).filter(Product.stock > 0).all()
|
||||||
for p in product_list:
|
for p in product_list:
|
||||||
total_value += p.stock * p.price
|
total_value += p.stock * p.price
|
||||||
|
|
||||||
total_positive_credit = self.session.query(func.sum(User.credit)).filter(User.credit > 0).first()[0]
|
total_positive_credit = (
|
||||||
total_negative_credit = self.session.query(func.sum(User.credit)).filter(User.credit < 0).first()[0]
|
self.session.query(func.sum(User.credit)).filter(User.credit > 0).first()[0]
|
||||||
|
)
|
||||||
|
total_negative_credit = (
|
||||||
|
self.session.query(func.sum(User.credit)).filter(User.credit < 0).first()[0]
|
||||||
|
)
|
||||||
|
|
||||||
total_credit = total_positive_credit + total_negative_credit
|
total_credit = total_positive_credit + total_negative_credit
|
||||||
total_balance = total_value - total_credit
|
total_balance = total_value - total_credit
|
||||||
|
|
||||||
line_format = '%15s | %5d \n'
|
line_format = "%15s | %5d \n"
|
||||||
text += line_format % ('Total value', total_value)
|
text += line_format % ("Total value", total_value)
|
||||||
text += 24 * '-' + '\n'
|
text += 24 * "-" + "\n"
|
||||||
text += line_format % ('Positive credit', total_positive_credit)
|
text += line_format % ("Positive credit", total_positive_credit)
|
||||||
text += line_format % ('Negative credit', total_negative_credit)
|
text += line_format % ("Negative credit", total_negative_credit)
|
||||||
text += line_format % ('Total credit', total_credit)
|
text += line_format % ("Total credit", total_credit)
|
||||||
text += 24 * '-' + '\n'
|
text += 24 * "-" + "\n"
|
||||||
text += line_format % ('Total balance', total_balance)
|
text += line_format % ("Total balance", total_balance)
|
||||||
less(text)
|
less(text)
|
||||||
|
|
||||||
|
|
||||||
class LoggedStatisticsMenu(Menu):
|
class LoggedStatisticsMenu(Menu):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Menu.__init__(self, 'Statistics from log', uses_db=True)
|
Menu.__init__(self, "Statistics from log", uses_db=True)
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
statisticsTextOnly()
|
statisticsTextOnly()
|
||||||
|
|
|
@ -9,6 +9,7 @@ from sqlalchemy.orm.collections import (
|
||||||
InstrumentedSet,
|
InstrumentedSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
metadata = MetaData(
|
metadata = MetaData(
|
||||||
naming_convention={
|
naming_convention={
|
||||||
|
@ -16,7 +17,7 @@ class Base(DeclarativeBase):
|
||||||
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
||||||
"ck": "ck_%(table_name)s_`%(constraint_name)s`",
|
"ck": "ck_%(table_name)s_`%(constraint_name)s`",
|
||||||
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
||||||
"pk": "pk_%(table_name)s"
|
"pk": "pk_%(table_name)s",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,15 +27,18 @@ class Base(DeclarativeBase):
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
columns = ", ".join(
|
columns = ", ".join(
|
||||||
f"{k}={repr(v)}" for k, v in self.__dict__.items() if not any([
|
f"{k}={repr(v)}"
|
||||||
|
for k, v in self.__dict__.items()
|
||||||
|
if not any(
|
||||||
|
[
|
||||||
k.startswith("_"),
|
k.startswith("_"),
|
||||||
|
|
||||||
# Ensure that we don't try to print out the entire list of
|
# Ensure that we don't try to print out the entire list of
|
||||||
# relationships, which could create an infinite loop
|
# relationships, which could create an infinite loop
|
||||||
isinstance(v, Base),
|
isinstance(v, Base),
|
||||||
isinstance(v, InstrumentedList),
|
isinstance(v, InstrumentedList),
|
||||||
isinstance(v, InstrumentedSet),
|
isinstance(v, InstrumentedSet),
|
||||||
isinstance(v, InstrumentedDict),
|
isinstance(v, InstrumentedDict),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return f"<{self.__class__.__name__}({columns})>"
|
return f"<{self.__class__.__name__}({columns})>"
|
||||||
|
|
|
@ -13,12 +13,14 @@ from sqlalchemy.orm import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .Base import Base
|
from .Base import Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .PurchaseEntry import PurchaseEntry
|
from .PurchaseEntry import PurchaseEntry
|
||||||
from .UserProducts import UserProducts
|
from .UserProducts import UserProducts
|
||||||
|
|
||||||
|
|
||||||
class Product(Base):
|
class Product(Base):
|
||||||
__tablename__ = 'products'
|
__tablename__ = "products"
|
||||||
|
|
||||||
product_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
product_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
bar_code: Mapped[str] = mapped_column(String(13))
|
bar_code: Mapped[str] = mapped_column(String(13))
|
||||||
|
@ -34,7 +36,7 @@ class Product(Base):
|
||||||
name_re = r".+"
|
name_re = r".+"
|
||||||
name_length = 45
|
name_length = 45
|
||||||
|
|
||||||
def __init__(self, bar_code, name, price, stock=0, hidden = False):
|
def __init__(self, bar_code, name, price, stock=0, hidden=False):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.bar_code = bar_code
|
self.bar_code = bar_code
|
||||||
self.price = price
|
self.price = price
|
||||||
|
|
|
@ -22,14 +22,17 @@ from .Transaction import Transaction
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .PurchaseEntry import PurchaseEntry
|
from .PurchaseEntry import PurchaseEntry
|
||||||
|
|
||||||
|
|
||||||
class Purchase(Base):
|
class Purchase(Base):
|
||||||
__tablename__ = 'purchases'
|
__tablename__ = "purchases"
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
time: Mapped[datetime] = mapped_column(DateTime)
|
time: Mapped[datetime] = mapped_column(DateTime)
|
||||||
price: Mapped[int] = mapped_column(Integer)
|
price: Mapped[int] = mapped_column(Integer)
|
||||||
|
|
||||||
transactions: Mapped[set[Transaction]] = relationship(back_populates="purchase", order_by='Transaction.user_name')
|
transactions: Mapped[set[Transaction]] = relationship(
|
||||||
|
back_populates="purchase", order_by="Transaction.user_name"
|
||||||
|
)
|
||||||
entries: Mapped[set[PurchaseEntry]] = relationship(back_populates="purchase")
|
entries: Mapped[set[PurchaseEntry]] = relationship(back_populates="purchase")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -40,14 +43,14 @@ class Purchase(Base):
|
||||||
|
|
||||||
def price_per_transaction(self, round_up=True):
|
def price_per_transaction(self, round_up=True):
|
||||||
if round_up:
|
if round_up:
|
||||||
return int(math.ceil(float(self.price)/len(self.transactions)))
|
return int(math.ceil(float(self.price) / len(self.transactions)))
|
||||||
else:
|
else:
|
||||||
return int(math.floor(float(self.price)/len(self.transactions)))
|
return int(math.floor(float(self.price) / len(self.transactions)))
|
||||||
|
|
||||||
def set_price(self, round_up=True):
|
def set_price(self, round_up=True):
|
||||||
self.price = 0
|
self.price = 0
|
||||||
for entry in self.entries:
|
for entry in self.entries:
|
||||||
self.price += entry.amount*entry.product.price
|
self.price += entry.amount * entry.product.price
|
||||||
if len(self.transactions) > 0:
|
if len(self.transactions) > 0:
|
||||||
for t in self.transactions:
|
for t in self.transactions:
|
||||||
t.amount = self.price_per_transaction(round_up=round_up)
|
t.amount = self.price_per_transaction(round_up=round_up)
|
||||||
|
|
|
@ -17,8 +17,9 @@ if TYPE_CHECKING:
|
||||||
from .Product import Product
|
from .Product import Product
|
||||||
from .Purchase import Purchase
|
from .Purchase import Purchase
|
||||||
|
|
||||||
|
|
||||||
class PurchaseEntry(Base):
|
class PurchaseEntry(Base):
|
||||||
__tablename__ = 'purchase_entries'
|
__tablename__ = "purchase_entries"
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
amount: Mapped[int] = mapped_column(Integer)
|
amount: Mapped[int] = mapped_column(Integer)
|
||||||
|
@ -26,8 +27,8 @@ class PurchaseEntry(Base):
|
||||||
product_id: Mapped[int] = mapped_column(ForeignKey("products.product_id"))
|
product_id: Mapped[int] = mapped_column(ForeignKey("products.product_id"))
|
||||||
purchase_id: Mapped[int] = mapped_column(ForeignKey("purchases.id"))
|
purchase_id: Mapped[int] = mapped_column(ForeignKey("purchases.id"))
|
||||||
|
|
||||||
product: Mapped[Product] = relationship(lazy='joined')
|
product: Mapped[Product] = relationship(lazy="joined")
|
||||||
purchase: Mapped[Purchase] = relationship(lazy='joined')
|
purchase: Mapped[Purchase] = relationship(lazy="joined")
|
||||||
|
|
||||||
def __init__(self, purchase, product, amount):
|
def __init__(self, purchase, product, amount):
|
||||||
self.product = product
|
self.product = product
|
||||||
|
|
|
@ -21,8 +21,9 @@ if TYPE_CHECKING:
|
||||||
from .User import User
|
from .User import User
|
||||||
from .Purchase import Purchase
|
from .Purchase import Purchase
|
||||||
|
|
||||||
|
|
||||||
class Transaction(Base):
|
class Transaction(Base):
|
||||||
__tablename__ = 'transactions'
|
__tablename__ = "transactions"
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
|
|
||||||
|
@ -31,11 +32,11 @@ class Transaction(Base):
|
||||||
penalty: Mapped[int] = mapped_column(Integer)
|
penalty: Mapped[int] = mapped_column(Integer)
|
||||||
description: Mapped[str | None] = mapped_column(String(50))
|
description: Mapped[str | None] = mapped_column(String(50))
|
||||||
|
|
||||||
user_name: Mapped[str] = mapped_column(ForeignKey('users.name'))
|
user_name: Mapped[str] = mapped_column(ForeignKey("users.name"))
|
||||||
purchase_id: Mapped[int | None] = mapped_column(ForeignKey('purchases.id'))
|
purchase_id: Mapped[int | None] = mapped_column(ForeignKey("purchases.id"))
|
||||||
|
|
||||||
user: Mapped[User] = relationship(lazy='joined')
|
user: Mapped[User] = relationship(lazy="joined")
|
||||||
purchase: Mapped[Purchase] = relationship(lazy='joined')
|
purchase: Mapped[Purchase] = relationship(lazy="joined")
|
||||||
|
|
||||||
def __init__(self, user, amount=0, description=None, purchase=None, penalty=1):
|
def __init__(self, user, amount=0, description=None, purchase=None, penalty=1):
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
|
@ -12,12 +12,14 @@ from sqlalchemy.orm import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .Base import Base
|
from .Base import Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .UserProducts import UserProducts
|
from .UserProducts import UserProducts
|
||||||
from .Transaction import Transaction
|
from .Transaction import Transaction
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
__tablename__ = 'users'
|
__tablename__ = "users"
|
||||||
name: Mapped[str] = mapped_column(String(10), primary_key=True)
|
name: Mapped[str] = mapped_column(String(10), primary_key=True)
|
||||||
credit: Mapped[str] = mapped_column(Integer)
|
credit: Mapped[str] = mapped_column(Integer)
|
||||||
card: Mapped[str | None] = mapped_column(String(20))
|
card: Mapped[str | None] = mapped_column(String(20))
|
||||||
|
@ -32,10 +34,10 @@ class User(Base):
|
||||||
|
|
||||||
def __init__(self, name, card, rfid=None, credit=0):
|
def __init__(self, name, card, rfid=None, credit=0):
|
||||||
self.name = name
|
self.name = name
|
||||||
if card == '':
|
if card == "":
|
||||||
card = None
|
card = None
|
||||||
self.card = card
|
self.card = card
|
||||||
if rfid == '':
|
if rfid == "":
|
||||||
rfid = None
|
rfid = None
|
||||||
self.rfid = rfid
|
self.rfid = rfid
|
||||||
self.credit = credit
|
self.credit = credit
|
||||||
|
@ -44,4 +46,4 @@ class User(Base):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def is_anonymous(self):
|
def is_anonymous(self):
|
||||||
return self.card == '11122233'
|
return self.card == "11122233"
|
||||||
|
|
|
@ -17,10 +17,11 @@ if TYPE_CHECKING:
|
||||||
from .User import User
|
from .User import User
|
||||||
from .Product import Product
|
from .Product import Product
|
||||||
|
|
||||||
class UserProducts(Base):
|
|
||||||
__tablename__ = 'user_products'
|
|
||||||
|
|
||||||
user_name: Mapped[str] = mapped_column(ForeignKey('users.name'), primary_key=True)
|
class UserProducts(Base):
|
||||||
|
__tablename__ = "user_products"
|
||||||
|
|
||||||
|
user_name: Mapped[str] = mapped_column(ForeignKey("users.name"), primary_key=True)
|
||||||
product_id: Mapped[int] = mapped_column(ForeignKey("products.product_id"), primary_key=True)
|
product_id: Mapped[int] = mapped_column(ForeignKey("products.product_id"), primary_key=True)
|
||||||
|
|
||||||
count: Mapped[int] = mapped_column(Integer)
|
count: Mapped[int] = mapped_column(Integer)
|
||||||
|
@ -28,4 +29,3 @@ class UserProducts(Base):
|
||||||
|
|
||||||
user: Mapped[User] = relationship()
|
user: Mapped[User] = relationship()
|
||||||
product: Mapped[Product] = relationship()
|
product: Mapped[Product] = relationship()
|
||||||
|
|
||||||
|
|
|
@ -11,57 +11,69 @@ from ..menus import *
|
||||||
|
|
||||||
random.seed()
|
random.seed()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if not config.getboolean('general', 'stop_allowed'):
|
if not config.getboolean("general", "stop_allowed"):
|
||||||
signal.signal(signal.SIGQUIT, signal.SIG_IGN)
|
signal.signal(signal.SIGQUIT, signal.SIG_IGN)
|
||||||
|
|
||||||
if not config.getboolean('general', 'stop_allowed'):
|
if not config.getboolean("general", "stop_allowed"):
|
||||||
signal.signal(signal.SIGTSTP, signal.SIG_IGN)
|
signal.signal(signal.SIGTSTP, signal.SIG_IGN)
|
||||||
|
|
||||||
main = MainMenu('Dibbler main menu',
|
main = MainMenu(
|
||||||
items=[BuyMenu(),
|
"Dibbler main menu",
|
||||||
|
items=[
|
||||||
|
BuyMenu(),
|
||||||
ProductListMenu(),
|
ProductListMenu(),
|
||||||
ShowUserMenu(),
|
ShowUserMenu(),
|
||||||
UserListMenu(),
|
UserListMenu(),
|
||||||
AdjustCreditMenu(),
|
AdjustCreditMenu(),
|
||||||
TransferMenu(),
|
TransferMenu(),
|
||||||
AddStockMenu(),
|
AddStockMenu(),
|
||||||
Menu('Add/edit',
|
Menu(
|
||||||
items=[AddUserMenu(),
|
"Add/edit",
|
||||||
|
items=[
|
||||||
|
AddUserMenu(),
|
||||||
EditUserMenu(),
|
EditUserMenu(),
|
||||||
AddProductMenu(),
|
AddProductMenu(),
|
||||||
EditProductMenu(),
|
EditProductMenu(),
|
||||||
AdjustStockMenu(),
|
AdjustStockMenu(),
|
||||||
CleanupStockMenu(), ]),
|
CleanupStockMenu(),
|
||||||
|
],
|
||||||
|
),
|
||||||
ProductSearchMenu(),
|
ProductSearchMenu(),
|
||||||
Menu('Statistics',
|
Menu(
|
||||||
items=[ProductPopularityMenu(),
|
"Statistics",
|
||||||
|
items=[
|
||||||
|
ProductPopularityMenu(),
|
||||||
ProductRevenueMenu(),
|
ProductRevenueMenu(),
|
||||||
BalanceMenu(),
|
BalanceMenu(),
|
||||||
LoggedStatisticsMenu()]),
|
LoggedStatisticsMenu(),
|
||||||
FAQMenu(),
|
|
||||||
PrintLabelMenu()
|
|
||||||
],
|
],
|
||||||
exit_msg='happy happy joy joy',
|
),
|
||||||
exit_confirm_msg='Really quit Dibbler?')
|
FAQMenu(),
|
||||||
if not config.getboolean('general', 'quit_allowed'):
|
PrintLabelMenu(),
|
||||||
main.exit_disallowed_msg = 'You can check out any time you like, but you can never leave.'
|
],
|
||||||
|
exit_msg="happy happy joy joy",
|
||||||
|
exit_confirm_msg="Really quit Dibbler?",
|
||||||
|
)
|
||||||
|
if not config.getboolean("general", "quit_allowed"):
|
||||||
|
main.exit_disallowed_msg = "You can check out any time you like, but you can never leave."
|
||||||
while True:
|
while True:
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
main.execute()
|
main.execute()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('')
|
print("")
|
||||||
print('Interrupted.')
|
print("Interrupted.")
|
||||||
except:
|
except:
|
||||||
print('Something went wrong.')
|
print("Something went wrong.")
|
||||||
print(f'{sys.exc_info()[0]}: {sys.exc_info()[1]}')
|
print(f"{sys.exc_info()[0]}: {sys.exc_info()[1]}")
|
||||||
if config.getboolean('general', 'show_tracebacks'):
|
if config.getboolean("general", "show_tracebacks"):
|
||||||
traceback.print_tb(sys.exc_info()[2])
|
traceback.print_tb(sys.exc_info()[2])
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
print('Restarting main menu.')
|
print("Restarting main menu.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
|
@ -2,8 +2,10 @@
|
||||||
from dibbler.models import Base
|
from dibbler.models import Base
|
||||||
from dibbler.db import engine
|
from dibbler.db import engine
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
|
@ -3,14 +3,16 @@
|
||||||
from dibbler.db import Session
|
from dibbler.db import Session
|
||||||
from dibbler.models import User
|
from dibbler.models import User
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Start an SQL session
|
# Start an SQL session
|
||||||
session=Session()
|
session = Session()
|
||||||
# Let's find all users with a negative credit
|
# Let's find all users with a negative credit
|
||||||
slabbedasker=session.query(User).filter(User.credit<0).all()
|
slabbedasker = session.query(User).filter(User.credit < 0).all()
|
||||||
|
|
||||||
for slubbert in slabbedasker:
|
for slubbert in slabbedasker:
|
||||||
print(f"{slubbert.name}, {slubbert.credit}")
|
print(f"{slubbert.name}, {slubbert.credit}")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
|
@ -8,47 +8,55 @@ import matplotlib.dates as mdates
|
||||||
|
|
||||||
from dibbler.lib.statistikkHelpers import *
|
from dibbler.lib.statistikkHelpers import *
|
||||||
|
|
||||||
|
|
||||||
def getInputType():
|
def getInputType():
|
||||||
inp = 0
|
inp = 0
|
||||||
while not (inp == '1' or inp == '2' or inp == '3' or inp == '4'):
|
while not (inp == "1" or inp == "2" or inp == "3" or inp == "4"):
|
||||||
print('type 1 for user-statistics')
|
print("type 1 for user-statistics")
|
||||||
print('type 2 for product-statistics')
|
print("type 2 for product-statistics")
|
||||||
print('type 3 for global-statistics')
|
print("type 3 for global-statistics")
|
||||||
print('type 4 to enter loop-mode')
|
print("type 4 to enter loop-mode")
|
||||||
inp = input('')
|
inp = input("")
|
||||||
return int(inp)
|
return int(inp)
|
||||||
|
|
||||||
|
|
||||||
def getDateFile(date, n):
|
def getDateFile(date, n):
|
||||||
try:
|
try:
|
||||||
if n==0:
|
if n == 0:
|
||||||
inp = input('start date? (yyyy-mm-dd) ')
|
inp = input("start date? (yyyy-mm-dd) ")
|
||||||
elif n==-1:
|
elif n == -1:
|
||||||
inp = input('end date? (yyyy-mm-dd) ')
|
inp = input("end date? (yyyy-mm-dd) ")
|
||||||
year = inp.partition('-')
|
year = inp.partition("-")
|
||||||
month = year[2].partition('-')
|
month = year[2].partition("-")
|
||||||
return datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
return datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
||||||
except:
|
except:
|
||||||
print('invalid date, setting start start date')
|
print("invalid date, setting start start date")
|
||||||
if n==0:
|
if n == 0:
|
||||||
print('to date found on first line')
|
print("to date found on first line")
|
||||||
elif n==-1:
|
elif n == -1:
|
||||||
print('to date found on last line')
|
print("to date found on last line")
|
||||||
print(date)
|
print(date)
|
||||||
return datetime.date(int(date.partition('-')[0]), int(date.partition('-')[2].partition('-')[0]), int(date.partition('-')[2].partition('-')[2]))
|
return datetime.date(
|
||||||
|
int(date.partition("-")[0]),
|
||||||
|
int(date.partition("-")[2].partition("-")[0]),
|
||||||
|
int(date.partition("-")[2].partition("-")[2]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def dateToDateNumFile(date, startDate):
|
def dateToDateNumFile(date, startDate):
|
||||||
year = date.partition('-')
|
year = date.partition("-")
|
||||||
month = year[2].partition('-')
|
month = year[2].partition("-")
|
||||||
day = datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
day = datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
||||||
deltaDays = day-startDate
|
deltaDays = day - startDate
|
||||||
return int(deltaDays.days), day.weekday()
|
return int(deltaDays.days), day.weekday()
|
||||||
|
|
||||||
|
|
||||||
def getProducts(products):
|
def getProducts(products):
|
||||||
product = []
|
product = []
|
||||||
products = products.partition('¤')
|
products = products.partition("¤")
|
||||||
product.append(products[0])
|
product.append(products[0])
|
||||||
while (products[1]=='¤'):
|
while products[1] == "¤":
|
||||||
products = products[2].partition('¤')
|
products = products[2].partition("¤")
|
||||||
product.append(products[0])
|
product.append(products[0])
|
||||||
return product
|
return product
|
||||||
|
|
||||||
|
@ -56,49 +64,62 @@ def getProducts(products):
|
||||||
def piePlot(dictionary, n):
|
def piePlot(dictionary, n):
|
||||||
keys = []
|
keys = []
|
||||||
values = []
|
values = []
|
||||||
i=0
|
i = 0
|
||||||
for key in sorted(dictionary, key=dictionary.get, reverse=True):
|
for key in sorted(dictionary, key=dictionary.get, reverse=True):
|
||||||
values.append(dictionary[key])
|
values.append(dictionary[key])
|
||||||
if i<n:
|
if i < n:
|
||||||
keys.append(key)
|
keys.append(key)
|
||||||
i += 1
|
i += 1
|
||||||
else:
|
else:
|
||||||
keys.append('')
|
keys.append("")
|
||||||
plt.pie(values, labels=keys)
|
plt.pie(values, labels=keys)
|
||||||
|
|
||||||
|
|
||||||
def datePlot(array, dateLine):
|
def datePlot(array, dateLine):
|
||||||
if (not array == []):
|
if not array == []:
|
||||||
plt.bar(dateLine, array)
|
plt.bar(dateLine, array)
|
||||||
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b'))
|
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%b"))
|
||||||
|
|
||||||
|
|
||||||
def dayPlot(array, days):
|
def dayPlot(array, days):
|
||||||
if (not array == []):
|
if not array == []:
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
array[i]=array[i]*7.0/days
|
array[i] = array[i] * 7.0 / days
|
||||||
plt.bar(list(range(7)), array)
|
plt.bar(list(range(7)), array)
|
||||||
plt.xticks(list(range(7)),[' mon',' tue',' wed',' thu',' fri',' sat',' sun'])
|
plt.xticks(
|
||||||
|
list(range(7)),
|
||||||
|
[
|
||||||
|
" mon",
|
||||||
|
" tue",
|
||||||
|
" wed",
|
||||||
|
" thu",
|
||||||
|
" fri",
|
||||||
|
" sat",
|
||||||
|
" sun",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def graphPlot(array, dateLine):
|
def graphPlot(array, dateLine):
|
||||||
if (not array == []):
|
if not array == []:
|
||||||
plt.plot(dateLine, array)
|
plt.plot(dateLine, array)
|
||||||
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b'))
|
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%b"))
|
||||||
|
|
||||||
|
|
||||||
def plotUser(database, dateLine, user, n):
|
def plotUser(database, dateLine, user, n):
|
||||||
printUser(database, dateLine, user, n)
|
printUser(database, dateLine, user, n)
|
||||||
plt.subplot(221)
|
plt.subplot(221)
|
||||||
piePlot(database.personVareAntall[user], n)
|
piePlot(database.personVareAntall[user], n)
|
||||||
plt.xlabel('antall varer kjøpt gjengitt i antall')
|
plt.xlabel("antall varer kjøpt gjengitt i antall")
|
||||||
plt.subplot(222)
|
plt.subplot(222)
|
||||||
datePlot(database.personDatoVerdi[user], dateLine)
|
datePlot(database.personDatoVerdi[user], dateLine)
|
||||||
plt.xlabel('penger brukt over dato')
|
plt.xlabel("penger brukt over dato")
|
||||||
plt.subplot(223)
|
plt.subplot(223)
|
||||||
piePlot(database.personVareVerdi[user], n)
|
piePlot(database.personVareVerdi[user], n)
|
||||||
plt.xlabel('antall varer kjøpt gjengitt i verdi')
|
plt.xlabel("antall varer kjøpt gjengitt i verdi")
|
||||||
plt.subplot(224)
|
plt.subplot(224)
|
||||||
dayPlot(database.personUkedagVerdi[user], len(dateLine))
|
dayPlot(database.personUkedagVerdi[user], len(dateLine))
|
||||||
plt.xlabel('forbruk over ukedager')
|
plt.xlabel("forbruk over ukedager")
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,100 +127,105 @@ def plotProduct(database, dateLine, product, n):
|
||||||
printProduct(database, dateLine, product, n)
|
printProduct(database, dateLine, product, n)
|
||||||
plt.subplot(221)
|
plt.subplot(221)
|
||||||
piePlot(database.varePersonAntall[product], n)
|
piePlot(database.varePersonAntall[product], n)
|
||||||
plt.xlabel('personer som har handler produktet')
|
plt.xlabel("personer som har handler produktet")
|
||||||
plt.subplot(222)
|
plt.subplot(222)
|
||||||
datePlot(database.vareDatoAntall[product], dateLine)
|
datePlot(database.vareDatoAntall[product], dateLine)
|
||||||
plt.xlabel('antall produkter handlet per dag')
|
plt.xlabel("antall produkter handlet per dag")
|
||||||
#plt.subplot(223)
|
# plt.subplot(223)
|
||||||
plt.subplot(224)
|
plt.subplot(224)
|
||||||
dayPlot(database.vareUkedagAntall[product], len(dateLine))
|
dayPlot(database.vareUkedagAntall[product], len(dateLine))
|
||||||
plt.xlabel('antall over ukedager')
|
plt.xlabel("antall over ukedager")
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
def plotGlobal(database, dateLine, n):
|
def plotGlobal(database, dateLine, n):
|
||||||
printGlobal(database, dateLine, n)
|
printGlobal(database, dateLine, n)
|
||||||
plt.subplot(231)
|
plt.subplot(231)
|
||||||
piePlot(database.globalVareVerdi, n)
|
piePlot(database.globalVareVerdi, n)
|
||||||
plt.xlabel('varer kjøpt gjengitt som verdi')
|
plt.xlabel("varer kjøpt gjengitt som verdi")
|
||||||
plt.subplot(232)
|
plt.subplot(232)
|
||||||
datePlot(database.globalDatoForbruk, dateLine)
|
datePlot(database.globalDatoForbruk, dateLine)
|
||||||
plt.xlabel('forbruk over dato')
|
plt.xlabel("forbruk over dato")
|
||||||
plt.subplot(233)
|
plt.subplot(233)
|
||||||
graphPlot(database.pengebeholdning, dateLine)
|
graphPlot(database.pengebeholdning, dateLine)
|
||||||
plt.xlabel('pengebeholdning over tid (negativ verdi utgjør samlet kreditt)')
|
plt.xlabel("pengebeholdning over tid (negativ verdi utgjør samlet kreditt)")
|
||||||
plt.subplot(234)
|
plt.subplot(234)
|
||||||
piePlot(database.globalPersonForbruk, n)
|
piePlot(database.globalPersonForbruk, n)
|
||||||
plt.xlabel('penger brukt av personer')
|
plt.xlabel("penger brukt av personer")
|
||||||
plt.subplot(235)
|
plt.subplot(235)
|
||||||
dayPlot(database.globalUkedagForbruk, len(dateLine))
|
dayPlot(database.globalUkedagForbruk, len(dateLine))
|
||||||
plt.xlabel('forbruk over ukedager')
|
plt.xlabel("forbruk over ukedager")
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
def alt4menu(database, dateLine, useDatabase):
|
def alt4menu(database, dateLine, useDatabase):
|
||||||
n=10
|
n = 10
|
||||||
while 1:
|
while 1:
|
||||||
print('\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit')
|
print(
|
||||||
|
"\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
inp = input('')
|
inp = input("")
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
if inp == 'q':
|
if inp == "q":
|
||||||
break
|
break
|
||||||
elif inp == '1':
|
elif inp == "1":
|
||||||
if i=='0':
|
if i == "0":
|
||||||
user = input('input full username: ')
|
user = input("input full username: ")
|
||||||
else:
|
else:
|
||||||
user = getUser()
|
user = getUser()
|
||||||
plotUser(database, dateLine, user, n)
|
plotUser(database, dateLine, user, n)
|
||||||
elif inp == '2':
|
elif inp == "2":
|
||||||
if i=='0':
|
if i == "0":
|
||||||
product = input('input full product name: ')
|
product = input("input full product name: ")
|
||||||
else:
|
else:
|
||||||
product = getProduct()
|
product = getProduct()
|
||||||
plotProduct(database, dateLine, product, n)
|
plotProduct(database, dateLine, product, n)
|
||||||
elif inp == '3':
|
elif inp == "3":
|
||||||
plotGlobal(database, dateLine, n)
|
plotGlobal(database, dateLine, n)
|
||||||
elif inp == 'n':
|
elif inp == "n":
|
||||||
try:
|
try:
|
||||||
n=int(input('set number to show '));
|
n = int(input("set number to show "))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
inputType=getInputType()
|
inputType = getInputType()
|
||||||
i = input('0:fil, 1:database \n? ')
|
i = input("0:fil, 1:database \n? ")
|
||||||
if (inputType == 1):
|
if inputType == 1:
|
||||||
if i=='0':
|
if i == "0":
|
||||||
user = input('input full username: ')
|
user = input("input full username: ")
|
||||||
else:
|
else:
|
||||||
user = getUser()
|
user = getUser()
|
||||||
product = ''
|
product = ""
|
||||||
elif (inputType == 2):
|
elif inputType == 2:
|
||||||
if i=='0':
|
if i == "0":
|
||||||
product = input('input full product name: ')
|
product = input("input full product name: ")
|
||||||
else:
|
else:
|
||||||
product = getProduct()
|
product = getProduct()
|
||||||
user = ''
|
user = ""
|
||||||
else :
|
else:
|
||||||
product = ''
|
product = ""
|
||||||
user = ''
|
user = ""
|
||||||
if i=='0':
|
if i == "0":
|
||||||
inputFile=input('logfil? ')
|
inputFile = input("logfil? ")
|
||||||
if inputFile=='':
|
if inputFile == "":
|
||||||
inputFile='default.dibblerlog'
|
inputFile = "default.dibblerlog"
|
||||||
database, dateLine = buildDatabaseFromFile(inputFile, inputType, product, user)
|
database, dateLine = buildDatabaseFromFile(inputFile, inputType, product, user)
|
||||||
else:
|
else:
|
||||||
database, dateLine = buildDatabaseFromDb(inputType, product, user)
|
database, dateLine = buildDatabaseFromDb(inputType, product, user)
|
||||||
|
|
||||||
if (inputType==1):
|
if inputType == 1:
|
||||||
plotUser(database, dateLine, user, 10)
|
plotUser(database, dateLine, user, 10)
|
||||||
if (inputType==2):
|
if inputType == 2:
|
||||||
plotProduct(database, dateLine, product, 10)
|
plotProduct(database, dateLine, product, 10)
|
||||||
if (inputType==3):
|
if inputType == 3:
|
||||||
plotGlobal(database, dateLine, 10)
|
plotGlobal(database, dateLine, 10)
|
||||||
if (inputType==4):
|
if inputType == 4:
|
||||||
alt4menu(database, dateLine, i)
|
alt4menu(database, dateLine, i)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
Loading…
Reference in New Issue