Restructure project #3

Merged
h7x4 merged 10 commits from restructure-project into master 2023-09-02 21:18:04 +02:00
30 changed files with 1514 additions and 1124 deletions
Showing only changes of commit c219fa61ba - Show all commits

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -8,411 +8,507 @@ 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
globalUkedagForbruk = [] globalVareAntall = {} # globalVareAntall[Oreo] == 3
globalDatoVarer = [] globalVareVerdi = {} # globalVareVerdi[Oreo] == 30 #[kr]
globalDatoForbruk = [] globalPersonAntall = {} # globalPersonAntall[trygvrad] == 3
pengebeholdning = [] globalPersonForbruk = {} # globalPersonVerdi == 30 #[kr]
globalUkedagForbruk = []
globalDatoVarer = []
globalDatoForbruk = []
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()
database.vareUkedagAntall.clear() database.vareUkedagAntall.clear()
database.personVareAntall.clear() database.personVareAntall.clear()
database.personVareVerdi.clear() database.personVareVerdi.clear()
database.personDatoVerdi.clear() database.personDatoVerdi.clear()
database.personUkedagVerdi.clear() database.personUkedagVerdi.clear()
database.personPosTransactions.clear() database.personPosTransactions.clear()
database.personNegTransactions.clear() database.personNegTransactions.clear()
database.globalVareAntall.clear() database.globalVareAntall.clear()
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)
if inputLine.product not in database.vareDatoAntall: ):
database.vareDatoAntall[inputLine.product] = [0]*(inputLine.numberOfDays+1) database.varePersonAntall[inputLine.product][inputLine.user] = (
database.vareDatoAntall[inputLine.product][inputLine.dateNum] += 1 database.varePersonAntall[inputLine.product].setdefault(inputLine.user, 0) + 1
if inputLine.product not in database.vareUkedagAntall: )
database.vareUkedagAntall[inputLine.product] = [0]*7 if inputLine.product not in database.vareDatoAntall:
database.vareUkedagAntall[inputLine.product][inputLine.weekday] += 1 database.vareDatoAntall[inputLine.product] = [0] * (inputLine.numberOfDays + 1)
#fyller inn for personer database.vareDatoAntall[inputLine.product][inputLine.dateNum] += 1
if (inputLine.inputUser=='') or (inputLine.inputUser==inputLine.user): if inputLine.product not in database.vareUkedagAntall:
if not inputLine.product == '': database.vareUkedagAntall[inputLine.product] = [0] * 7
database.personVareAntall[inputLine.user][inputLine.product] = database.personVareAntall[inputLine.user].setdefault(inputLine.product,0) + 1 database.vareUkedagAntall[inputLine.product][inputLine.weekday] += 1
database.personVareVerdi[inputLine.user][inputLine.product] = database.personVareVerdi[inputLine.user].setdefault(inputLine.product,0) + inputLine.price # fyller inn for personer
if inputLine.user not in database.personDatoVerdi: if (inputLine.inputUser == "") or (inputLine.inputUser == inputLine.user):
database.personDatoVerdi[inputLine.user] = [0]*(inputLine.numberOfDays+1) if not inputLine.product == "":
database.personDatoVerdi[inputLine.user][inputLine.dateNum] += inputLine.price database.personVareAntall[inputLine.user][inputLine.product] = (
if inputLine.user not in database.personUkedagVerdi: database.personVareAntall[inputLine.user].setdefault(inputLine.product, 0) + 1
database.personUkedagVerdi[inputLine.user] = [0]*7 )
database.personUkedagVerdi[inputLine.user][inputLine.weekday] += inputLine.price database.personVareVerdi[inputLine.user][inputLine.product] = (
#fyller inn delt statistikk (genereres uansett) database.personVareVerdi[inputLine.user].setdefault(inputLine.product, 0)
if (inputLine.product==''): + inputLine.price
if (inputLine.price>0): )
database.personPosTransactions[inputLine.user] = database.personPosTransactions.setdefault(inputLine.user,0) + inputLine.price if inputLine.user not in database.personDatoVerdi:
else: database.personDatoVerdi[inputLine.user] = [0] * (inputLine.numberOfDays + 1)
database.personNegTransactions[inputLine.user] = database.personNegTransactions.setdefault(inputLine.user,0) + inputLine.price database.personDatoVerdi[inputLine.user][inputLine.dateNum] += inputLine.price
elif not (inputLine.inputType==1): if inputLine.user not in database.personUkedagVerdi:
database.globalVareAntall[inputLine.product] = database.globalVareAntall.setdefault(inputLine.product,0) + 1 database.personUkedagVerdi[inputLine.user] = [0] * 7
database.globalVareVerdi[inputLine.product] = database.globalVareVerdi.setdefault(inputLine.product,0) + inputLine.price database.personUkedagVerdi[inputLine.user][inputLine.weekday] += inputLine.price
# fyller inn delt statistikk (genereres uansett)
if inputLine.product == "":
if inputLine.price > 0:
database.personPosTransactions[inputLine.user] = (
database.personPosTransactions.setdefault(inputLine.user, 0) + inputLine.price
)
else:
database.personNegTransactions[inputLine.user] = (
database.personNegTransactions.setdefault(inputLine.user, 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
if (inputLine.inputType == 3) or (inputLine.inputType == 4):
database.pengebeholdning[inputLine.dateNum] += inputLine.price
if not (inputLine.product == ""):
database.globalPersonAntall[inputLine.user] = (
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.globalDatoForbruk[inputLine.dateNum] += inputLine.price
database.globalUkedagForbruk[inputLine.weekday] += inputLine.price
return database
#fyller inn for global statistikk
if (inputLine.inputType==3) or (inputLine.inputType==4):
database.pengebeholdning[inputLine.dateNum] += inputLine.price
if not (inputLine.product==''):
database.globalPersonAntall[inputLine.user] = 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.globalDatoForbruk[inputLine.dateNum] += inputLine.price
database.globalUkedagForbruk[inputLine.weekday] += inputLine.price
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=" ")
f = open("default.dibblerlog", "w")
line_format = "%s|%s|%s|%s|%s|%s\n"
transaction_list = session.query(Transaction).all()
for transaction in transaction_list:
if transaction.purchase:
products = "¤".join([ent.product.name for ent in transaction.purchase.entries])
description = ""
else:
products = ""
description = transaction.description
line = line_format % (
"purchase",
transaction.time,
products,
transaction.user.name,
transaction.amount,
transaction.description,
)
f.write(line.encode("utf8"))
session.close()
f.close
# bygg database.pengebeholdning
if (inputType == 3) or (inputType == 4):
for i in range(inputLine.numberOfDays + 1):
if i > 0:
database.pengebeholdning[i] += database.pengebeholdning[i - 1]
# bygg dateLine
day = datetime.timedelta(days=1)
dateLine = []
dateLine.append(startDate)
for n in range(inputLine.numberOfDays):
dateLine.append(startDate + n * day)
print("done")
return database, dateLine
print('saving as default.dibblerlog...', end=' ')
f=open('default.dibblerlog','w')
line_format = '%s|%s|%s|%s|%s|%s\n'
transaction_list = session.query(Transaction).all()
for transaction in transaction_list:
if transaction.purchase:
products = '¤'.join([ent.product.name for ent in transaction.purchase.entries])
description = ''
else:
products = ''
description = transaction.description
line = line_format % ('purchase', transaction.time, products, transaction.user.name, transaction.amount, transaction.description)
f.write(line.encode('utf8'))
session.close()
f.close
#bygg database.pengebeholdning
if (inputType==3) or (inputType==4):
for i in range(inputLine.numberOfDays+1):
if i > 0:
database.pengebeholdning[i] +=database.pengebeholdning[i-1]
#bygg dateLine
day=datetime.timedelta(days=1)
dateLine=[]
dateLine.append(startDate)
for n in range(inputLine.numberOfDays):
dateLine.append(startDate+n*day)
print('done')
return database, dateLine
def buildDatabaseFromFile(inputFile, inputType, inputProduct, inputUser): def buildDatabaseFromFile(inputFile, 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)? ")
f=open(inputFile) 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):
database.globalDatoVarer = [0] * (inputLine.numberOfDays + 1)
database.globalDatoForbruk = [0] * (inputLine.numberOfDays + 1)
database.globalUkedagForbruk = [0] * 7
database.pengebeholdning = [0] * (inputLine.numberOfDays + 1)
for linje in fileLines:
if not (linje[0] == "#") and not (linje == "\n"):
# henter dateNum, products, user, price
restDel = linje.partition("|")
restDel = restDel[2].partition(" ")
inputLine.dateNum, inputLine.weekday = dateToDateNumFile(restDel[0], startDate)
if inputLine.dateNum < 0 or inputLine.dateNum > (inputLine.numberOfDays):
continue
restDel = restDel[2].partition("|")
restDel = restDel[2].partition("|")
products = restDel[0]
restDel = restDel[2].partition("|")
inputLine.user = restDel[0]
inputLine.price = int(restDel[2].partition("|")[0])
for inputLine.product in getProducts(products):
database = addLineToDatabase(database, inputLine)
inputLine.price = 0
# bygg database.pengebeholdning
if (inputType == 3) or (inputType == 4):
for i in range(inputLine.numberOfDays + 1):
if i > 0:
database.pengebeholdning[i] += database.pengebeholdning[i - 1]
# bygg dateLine
day = datetime.timedelta(days=1)
dateLine = []
dateLine.append(startDate)
for n in range(inputLine.numberOfDays):
dateLine.append(startDate + n * day)
return database, dateLine
if (inputType==3) or (inputType==4):
database.globalDatoVarer = [0]*(inputLine.numberOfDays+1)
database.globalDatoForbruk = [0]*(inputLine.numberOfDays+1)
database.globalUkedagForbruk = [0]*7
database.pengebeholdning = [0]*(inputLine.numberOfDays+1)
for linje in fileLines:
if not (linje[0]=='#') and not (linje=='\n') :
#henter dateNum, products, user, price
restDel = linje.partition('|')
restDel = restDel[2].partition(' ')
inputLine.dateNum, inputLine.weekday = dateToDateNumFile(restDel[0], startDate)
if inputLine.dateNum<0 or inputLine.dateNum>(inputLine.numberOfDays):
continue
restDel=restDel[2].partition('|')
restDel=restDel[2].partition('|')
products = restDel[0]
restDel=restDel[2].partition('|')
inputLine.user=restDel[0]
inputLine.price=int(restDel[2].partition('|')[0])
for inputLine.product in getProducts(products):
database=addLineToDatabase(database, inputLine )
inputLine.price = 0;
#bygg database.pengebeholdning
if (inputType==3) or (inputType==4):
for i in range(inputLine.numberOfDays+1):
if i > 0:
database.pengebeholdning[i] +=database.pengebeholdning[i-1]
#bygg dateLine
day=datetime.timedelta(days=1)
dateLine=[]
dateLine.append(startDate)
for n in range(inputLine.numberOfDays):
dateLine.append(startDate+n*day)
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': )
break inp = input("")
elif inp == '1': if inp == "q":
try: break
printUser(database, dateLine, getUser(), n) elif inp == "1":
except: try:
print('\n\nSomething is not right, (last date prior to first date?)') printUser(database, dateLine, getUser(), n)
elif inp == '2': except:
try: print("\n\nSomething is not right, (last date prior to first date?)")
printProduct(database, dateLine, getProduct(), n) elif inp == "2":
except: try:
print('\n\nSomething is not right, (last date prior to first date?)') printProduct(database, dateLine, getProduct(), n)
elif inp == '3': except:
try: print("\n\nSomething is not right, (last date prior to first date?)")
printGlobal(database, dateLine, n) elif inp == "3":
except: try:
print('\n\nSomething is not right, (last date prior to first date?)') printGlobal(database, dateLine, n)
elif inp == 'n': except:
n=int(input('set number to show ')); print("\n\nSomething is not right, (last date prior to first date?)")
elif inp == "n":
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)

View File

@ -13,38 +13,33 @@ 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()
elif args.subcommand == 'create-db': loop.main()
import dibbler.subcommands.makedb as makedb
makedb.main()
elif args.subcommand == 'slabbedasker': elif args.subcommand == "create-db":
import dibbler.subcommands.slabbedasker as slabbedasker import dibbler.subcommands.makedb as makedb
slabbedasker.main()
makedb.main()
elif args.subcommand == "slabbedasker":
import dibbler.subcommands.slabbedasker as slabbedasker
slabbedasker.main()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -21,8 +21,8 @@ from .miscmenus import (
) )
from .printermenu import PrintLabelMenu from .printermenu import PrintLabelMenu
from .stats import ( from .stats import (
ProductPopularityMenu, ProductPopularityMenu,
ProductRevenueMenu, ProductRevenueMenu,
BalanceMenu, BalanceMenu,
LoggedStatisticsMenu LoggedStatisticsMenu,
) )

View File

@ -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 price: {product.price}" if old_price != product.price else "", f"New stock for {product.name}: {product.stock:d}",
"- Removed hidden status" if old_hidden != product.hidden else "") f"- New price: {product.price}" if old_price != product.price 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}")

View File

@ -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,20 +171,22 @@ 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:
print("") print("")
return True return True
def complete_input(self): def complete_input(self):
@ -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

View File

@ -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

View File

@ -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.
''')] """,
),
]

View File

@ -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__(
return_index=True, self,
exit_msg=None, exit_confirm_msg=None, exit_disallowed_msg=None, name,
help_text=None, uses_db=False): items=None,
prompt=None,
end_prompt="> ",
return_index=True,
exit_msg=None,
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)

View File

@ -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

View File

@ -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()

View File

@ -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",
) )

View File

@ -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"),
.subquery() )
product_list = \ .filter(PurchaseEntry.amount > 0)
self.session.query(Product, sub.c.purchase_count) \ .group_by(PurchaseEntry.product_id)
.outerjoin((sub, Product.product_id == sub.c.product_id)) \ .subquery()
.order_by(desc(sub.c.purchase_count)) \ )
.filter(sub.c.purchase_count is not None) \ product_list = (
.all() self.session.query(Product, sub.c.purchase_count)
line_format = '{0:10s} | {1:>45s}\n' .outerjoin((sub, Product.product_id == sub.c.product_id))
text += line_format.format('items sold', 'product') .order_by(desc(sub.c.purchase_count))
text += '-' * (31 + Product.name_length) + '\n' .filter(sub.c.purchase_count is not None)
.all()
)
line_format = "{0:10s} | {1:>45s}\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"),
.subquery() )
product_list = \ .filter(PurchaseEntry.amount > 0)
self.session.query(Product, sub.c.purchase_count) \ .group_by(PurchaseEntry.product_id)
.outerjoin((sub, Product.product_id == sub.c.product_id)) \ .subquery()
.order_by(desc(sub.c.purchase_count * Product.price)) \ )
.filter(sub.c.purchase_count is not None) \ product_list = (
.all() self.session.query(Product, sub.c.purchase_count)
line_format = '{0:7s} | {1:10s} | {2:6s} | {3:>45s}\n' .outerjoin((sub, Product.product_id == sub.c.product_id))
text += line_format.format('revenue', 'items sold', 'price', 'product') .order_by(desc(sub.c.purchase_count * Product.price))
text += '-' * (31 + Product.name_length) + '\n' .filter(sub.c.purchase_count is not None)
.all()
)
line_format = "{0:7s} | {1:10s} | {2:6s} | {3:>45s}\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()

View File

@ -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)}"
k.startswith("_"), for k, v in self.__dict__.items()
if not any(
# Ensure that we don't try to print out the entire list of [
# relationships, which could create an infinite loop k.startswith("_"),
isinstance(v, Base), # Ensure that we don't try to print out the entire list of
isinstance(v, InstrumentedList), # relationships, which could create an infinite loop
isinstance(v, InstrumentedSet), isinstance(v, Base),
isinstance(v, InstrumentedDict), isinstance(v, InstrumentedList),
]) isinstance(v, InstrumentedSet),
isinstance(v, InstrumentedDict),
]
)
) )
return f"<{self.__class__.__name__}({columns})>" return f"<{self.__class__.__name__}({columns})>"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

@ -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",
ProductListMenu(), items=[
ShowUserMenu(), BuyMenu(),
UserListMenu(), ProductListMenu(),
AdjustCreditMenu(), ShowUserMenu(),
TransferMenu(), UserListMenu(),
AddStockMenu(), AdjustCreditMenu(),
Menu('Add/edit', TransferMenu(),
items=[AddUserMenu(), AddStockMenu(),
EditUserMenu(), Menu(
AddProductMenu(), "Add/edit",
EditProductMenu(), items=[
AdjustStockMenu(), AddUserMenu(),
CleanupStockMenu(), ]), EditUserMenu(),
ProductSearchMenu(), AddProductMenu(),
Menu('Statistics', EditProductMenu(),
items=[ProductPopularityMenu(), AdjustStockMenu(),
ProductRevenueMenu(), CleanupStockMenu(),
BalanceMenu(), ],
LoggedStatisticsMenu()]), ),
FAQMenu(), ProductSearchMenu(),
PrintLabelMenu() Menu(
], "Statistics",
exit_msg='happy happy joy joy', items=[
exit_confirm_msg='Really quit Dibbler?') ProductPopularityMenu(),
if not config.getboolean('general', 'quit_allowed'): ProductRevenueMenu(),
main.exit_disallowed_msg = 'You can check out any time you like, but you can never leave.' BalanceMenu(),
LoggedStatisticsMenu(),
],
),
FAQMenu(),
PrintLabelMenu(),
],
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()

View File

@ -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()

View File

@ -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__':
main() if __name__ == "__main__":
main()

View File

@ -8,198 +8,224 @@ 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])
while (products[1]=='¤'):
products = products[2].partition('¤')
product.append(products[0]) product.append(products[0])
return product while products[1] == "¤":
products = products[2].partition("¤")
product.append(products[0])
return product
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()
def plotProduct(database, dateLine, product, n): 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(
try: "\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit"
inp = input('') )
except: try:
continue inp = input("")
if inp == 'q': except:
break continue
elif inp == '1': if inp == "q":
if i=='0': break
user = input('input full username: ') elif inp == "1":
else: if i == "0":
user = getUser() user = input("input full username: ")
plotUser(database, dateLine, user, n) else:
elif inp == '2': user = getUser()
if i=='0': plotUser(database, dateLine, user, n)
product = input('input full product name: ') elif inp == "2":
else: if i == "0":
product = getProduct() product = input("input full product name: ")
plotProduct(database, dateLine, product, n) else:
elif inp == '3': product = getProduct()
plotGlobal(database, dateLine, n) plotProduct(database, dateLine, product, n)
elif inp == 'n': elif inp == "3":
try: plotGlobal(database, dateLine, n)
n=int(input('set number to show ')); elif inp == "n":
except: try:
pass n = int(input("set number to show "))
except:
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:
user = getUser()
product = ""
elif inputType == 2:
if i == "0":
product = input("input full product name: ")
else:
product = getProduct()
user = ""
else: else:
user = getUser() product = ""
product = '' user = ""
elif (inputType == 2): if i == "0":
if i=='0': inputFile = input("logfil? ")
product = input('input full product name: ') if inputFile == "":
inputFile = "default.dibblerlog"
database, dateLine = buildDatabaseFromFile(inputFile, inputType, product, user)
else: else:
product = getProduct() database, dateLine = buildDatabaseFromDb(inputType, product, user)
user = ''
else :
product = ''
user = ''
if i=='0':
inputFile=input('logfil? ')
if inputFile=='':
inputFile='default.dibblerlog'
database, dateLine = buildDatabaseFromFile(inputFile, inputType, product, user)
else:
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__':
main() if __name__ == "__main__":
main()