Tests and helpers

This commit is contained in:
2026-06-02 15:25:32 +02:00
parent faf5d440f3
commit d249c6a013
9 changed files with 372 additions and 121 deletions
+133 -109
View File
@@ -1,6 +1,7 @@
# type: ignore
## type: ignore
import kivy
from kivy.app import App
from kivy.graphics import Color, RoundedRectangle
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
@@ -10,7 +11,7 @@ from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from kivy.core.window import Window
from kivy.graphics import Color, RoundedRectangle
from utils.helpers import format_currency, format_date, validate_pin, parse_price
class RoundedButton(Button):
@@ -104,45 +105,57 @@ class ConfirmPopup(Popup):
class NumpadPopup(Popup):
def __init__(self, title, is_password=False, is_float=False, on_submit=None, **kwargs):
def __init__(
self, title, is_password=False, is_float=False, on_submit=None, **kwargs
):
self.is_password = is_password
self.is_float = is_float
self.on_submit = on_submit
self.entered_text = ""
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
self.display = Label(
text="PIN eingeben:" if is_password else "Betrag eingeben:",
font_size='20sp',
font_size="20sp",
bold=True,
size_hint_y=0.15,
halign='center',
valign='middle'
halign="center",
valign="middle",
)
self.display.bind(size=self.display.setter('text_size'))
self.display.bind(size=self.display.setter("text_size"))
content.add_widget(self.display)
grid = GridLayout(cols=3, spacing=10, size_hint_y=0.6)
buttons = [
'1', '2', '3',
'4', '5', '6',
'7', '8', '9',
',' if is_float else 'C', '0', ''
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"," if is_float else "C",
"0",
"",
]
for btn_text in buttons:
btn = RoundedButton(
text=btn_text,
btn_color=[0.15, 0.15, 0.17, 1] if btn_text in ['C', '', ','] else [0.0, 0.65, 0.57, 1]
btn_color=[0.15, 0.15, 0.17, 1]
if btn_text in ["C", "", ","]
else [0.0, 0.65, 0.57, 1],
)
btn.bind(on_release=self.on_key_press)
grid.add_widget(btn)
content.add_widget(grid)
actions = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=0.25)
actions = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.25)
ok_btn = RoundedButton(text="OK", btn_color=[0.0, 0.65, 0.57, 1])
cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1])
@@ -154,10 +167,10 @@ class NumpadPopup(Popup):
title=title,
content=content,
size_hint=(0.85, 0.75),
title_align='center',
title_size='18sp',
title_align="center",
title_size="18sp",
background_color=[0.08, 0.08, 0.09, 0.95],
**kwargs
**kwargs,
)
ok_btn.bind(on_release=self.submit)
@@ -165,20 +178,22 @@ class NumpadPopup(Popup):
def on_key_press(self, instance):
key = instance.text
if key == '':
if key == "":
self.entered_text = self.entered_text[:-1]
elif key == 'C':
elif key == "C":
self.entered_text = ""
elif key == ',':
if ',' not in self.entered_text:
self.entered_text += ','
elif key == ",":
if "," not in self.entered_text:
self.entered_text += ","
else:
self.entered_text += key
self.update_display()
def update_display(self):
if not self.entered_text:
self.display.text = "PIN eingeben:" if self.is_password else "Betrag eingeben:"
self.display.text = (
"PIN eingeben:" if self.is_password else "Betrag eingeben:"
)
else:
if self.is_password:
self.display.text = "*" * len(self.entered_text)
@@ -197,34 +212,36 @@ class KeyboardPopup(Popup):
self.on_submit = on_submit
self.entered_text = ""
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
self.display = Label(
text="Eingeben:" if not is_password else "****",
font_size='20sp',
font_size="20sp",
bold=True,
size_hint_y=0.15,
halign='center',
valign='middle'
halign="center",
valign="middle",
)
self.display.bind(size=self.display.setter('text_size'))
self.display.bind(size=self.display.setter("text_size"))
content.add_widget(self.display)
kbd_layout = BoxLayout(orientation='vertical', spacing=5, size_hint_y=0.6)
kbd_layout = BoxLayout(orientation="vertical", spacing=5, size_hint_y=0.6)
rows = [
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
['q', 'w', 'e', 'r', 't', 'z', 'u', 'i', 'o', 'p'],
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
['y', 'x', 'c', 'v', 'b', 'n', 'm', '', 'C']
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
["q", "w", "e", "r", "t", "z", "u", "i", "o", "p"],
["a", "s", "d", "f", "g", "h", "j", "k", "l"],
["y", "x", "c", "v", "b", "n", "m", "", "C"],
]
for row in rows:
row_box = BoxLayout(orientation='horizontal', spacing=5)
row_box = BoxLayout(orientation="horizontal", spacing=5)
for char in row:
btn = RoundedButton(
text=char,
btn_color=[0.15, 0.15, 0.17, 1] if char in ['C', ''] else [0.0, 0.65, 0.57, 1]
btn_color=[0.15, 0.15, 0.17, 1]
if char in ["C", ""]
else [0.0, 0.65, 0.57, 1],
)
btn.bind(on_release=self.on_key_press)
row_box.add_widget(btn)
@@ -232,7 +249,7 @@ class KeyboardPopup(Popup):
content.add_widget(kbd_layout)
actions = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=0.25)
actions = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.25)
ok_btn = RoundedButton(text="OK", btn_color=[0.0, 0.65, 0.57, 1])
cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1])
@@ -244,10 +261,10 @@ class KeyboardPopup(Popup):
title=title,
content=content,
size_hint=(0.95, 0.8),
title_align='center',
title_size='18sp',
title_align="center",
title_size="18sp",
background_color=[0.08, 0.08, 0.09, 0.95],
**kwargs
**kwargs,
)
ok_btn.bind(on_release=self.submit)
@@ -255,9 +272,9 @@ class KeyboardPopup(Popup):
def on_key_press(self, instance):
key = instance.text
if key == '':
if key == "":
self.entered_text = self.entered_text[:-1]
elif key == 'C':
elif key == "C":
self.entered_text = ""
else:
self.entered_text += key
@@ -338,7 +355,7 @@ class AdjustDebtPopup(Popup):
title="Betrag eingeben",
is_password=False,
is_float=True,
on_submit=self.on_amount_submitted
on_submit=self.on_amount_submitted,
)
popup.open()
@@ -362,16 +379,16 @@ class CreateUserPopup(Popup):
def __init__(self, on_create, **kwargs):
self.on_create = on_create
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
info = Label(
text="Name des neuen Benutzers eingeben:",
font_size='16sp',
font_size="16sp",
size_hint_y=0.25,
halign='center',
valign='middle'
halign="center",
valign="middle",
)
info.bind(size=info.setter('text_size'))
info.bind(size=info.setter("text_size"))
content.add_widget(info)
self.name_input = TextInput(
@@ -384,7 +401,7 @@ class CreateUserPopup(Popup):
foreground_color=[1, 1, 1, 1],
cursor_color=[1, 1, 1, 1],
size_hint_y=0.3,
padding=[10, 10]
padding=[10, 10],
)
self.name_input.bind(focus=self.trigger_keyboard)
content.add_widget(self.name_input)
@@ -417,7 +434,7 @@ class CreateUserPopup(Popup):
popup = KeyboardPopup(
title="Name eingeben",
is_password=False,
on_submit=self.on_name_submitted
on_submit=self.on_name_submitted,
)
popup.open()
@@ -435,21 +452,23 @@ class CreateUserPopup(Popup):
class EditUserPopup(Popup):
def __init__(self, user_id, current_name, current_pin, on_save, on_delete, **kwargs):
def __init__(
self, user_id, current_name, current_pin, on_save, on_delete, **kwargs
):
self.user_id = user_id
self.on_save = on_save
self.on_delete = on_delete
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
info = Label(
text="Benutzer bearbeiten:",
font_size='16sp',
font_size="16sp",
size_hint_y=0.15,
halign='center',
valign='middle'
halign="center",
valign="middle",
)
info.bind(size=info.setter('text_size'))
info.bind(size=info.setter("text_size"))
content.add_widget(info)
content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left"))
@@ -463,7 +482,7 @@ class EditUserPopup(Popup):
foreground_color=[1, 1, 1, 1],
cursor_color=[1, 1, 1, 1],
size_hint_y=0.15,
padding=[10, 10]
padding=[10, 10],
)
self.name_input.bind(focus=self.trigger_keyboard)
content.add_widget(self.name_input)
@@ -479,7 +498,7 @@ class EditUserPopup(Popup):
foreground_color=[1, 1, 1, 1],
cursor_color=[1, 1, 1, 1],
size_hint_y=0.15,
padding=[10, 10]
padding=[10, 10],
)
self.pin_input.bind(focus=self.trigger_numpad)
content.add_widget(self.pin_input)
@@ -515,7 +534,7 @@ class EditUserPopup(Popup):
popup = KeyboardPopup(
title="Name eingeben",
is_password=False,
on_submit=self.on_name_submitted
on_submit=self.on_name_submitted,
)
popup.open()
@@ -526,9 +545,7 @@ class EditUserPopup(Popup):
if value:
instance.focus = False
popup = NumpadPopup(
title="PIN eingeben",
is_password=False,
on_submit=self.on_pin_submitted
title="PIN eingeben", is_password=False, on_submit=self.on_pin_submitted
)
popup.open()
@@ -543,7 +560,9 @@ class EditUserPopup(Popup):
err.open()
return
if len(pin) < 4:
err = MessagePopup(title="Fehler", message="Die PIN must mindestens 4 Ziffern lang sein!")
err = MessagePopup(
title="Fehler", message="Die PIN must mindestens 4 Ziffern lang sein!"
)
err.open()
return
self.dismiss()
@@ -558,16 +577,16 @@ class CreateItemPopup(Popup):
def __init__(self, on_create, **kwargs):
self.on_create = on_create
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
info = Label(
text="Neuen Artikel hinzufügen:",
font_size='16sp',
font_size="16sp",
size_hint_y=0.15,
halign='center',
valign='middle'
halign="center",
valign="middle",
)
info.bind(size=info.setter('text_size'))
info.bind(size=info.setter("text_size"))
content.add_widget(info)
content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left"))
@@ -581,7 +600,7 @@ class CreateItemPopup(Popup):
foreground_color=[1, 1, 1, 1],
cursor_color=[1, 1, 1, 1],
size_hint_y=0.15,
padding=[10, 10]
padding=[10, 10],
)
self.name_input.bind(focus=self.trigger_keyboard)
content.add_widget(self.name_input)
@@ -597,7 +616,7 @@ class CreateItemPopup(Popup):
foreground_color=[1, 1, 1, 1],
cursor_color=[1, 1, 1, 1],
size_hint_y=0.15,
padding=[10, 10]
padding=[10, 10],
)
self.price_input.bind(focus=self.trigger_numpad)
content.add_widget(self.price_input)
@@ -630,7 +649,7 @@ class CreateItemPopup(Popup):
popup = KeyboardPopup(
title="Artikelname eingeben",
is_password=False,
on_submit=self.on_name_submitted
on_submit=self.on_name_submitted,
)
popup.open()
@@ -644,7 +663,7 @@ class CreateItemPopup(Popup):
title="Preis eingeben",
is_password=False,
is_float=True,
on_submit=self.on_price_submitted
on_submit=self.on_price_submitted,
)
popup.open()
@@ -672,21 +691,23 @@ class CreateItemPopup(Popup):
class EditItemPopup(Popup):
def __init__(self, item_id, current_name, current_price, on_save, on_delete, **kwargs):
def __init__(
self, item_id, current_name, current_price, on_save, on_delete, **kwargs
):
self.item_id = item_id
self.on_save = on_save
self.on_delete = on_delete
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
info = Label(
text="Artikel bearbeiten:",
font_size='16sp',
font_size="16sp",
size_hint_y=0.15,
halign='center',
valign='middle'
halign="center",
valign="middle",
)
info.bind(size=info.setter('text_size'))
info.bind(size=info.setter("text_size"))
content.add_widget(info)
content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left"))
@@ -700,7 +721,7 @@ class EditItemPopup(Popup):
foreground_color=[1, 1, 1, 1],
cursor_color=[1, 1, 1, 1],
size_hint_y=0.15,
padding=[10, 10]
padding=[10, 10],
)
self.name_input.bind(focus=self.trigger_keyboard)
content.add_widget(self.name_input)
@@ -716,7 +737,7 @@ class EditItemPopup(Popup):
foreground_color=[1, 1, 1, 1],
cursor_color=[1, 1, 1, 1],
size_hint_y=0.15,
padding=[10, 10]
padding=[10, 10],
)
self.price_input.bind(focus=self.trigger_numpad)
content.add_widget(self.price_input)
@@ -752,7 +773,7 @@ class EditItemPopup(Popup):
popup = KeyboardPopup(
title="Artikelname eingeben",
is_password=False,
on_submit=self.on_name_submitted
on_submit=self.on_name_submitted,
)
popup.open()
@@ -766,7 +787,7 @@ class EditItemPopup(Popup):
title="Preis eingeben",
is_password=False,
is_float=True,
on_submit=self.on_price_submitted
on_submit=self.on_price_submitted,
)
popup.open()
@@ -847,7 +868,7 @@ class TransactionRow(BoxLayout):
self.bind(pos=self.update_rect, size=self.update_rect)
date_lbl = Label(
text=date[:10],
text=format_date(date),
font_size="14sp",
size_hint_x=0.25,
halign="left",
@@ -1015,27 +1036,29 @@ class UserSelectionScreen(StyledScreen):
popup = NumpadPopup(
title=f"PIN einrichten: {user.name}",
is_password=True,
on_submit=lambda pin: self.setup_pin_confirm(user_id, pin)
on_submit=lambda pin: self.setup_pin_confirm(user_id, pin),
)
popup.open()
else:
popup = NumpadPopup(
title=f"PIN eingeben: {user.name}",
is_password=True,
on_submit=lambda pin: self.verify_pin(user_id, pin)
on_submit=lambda pin: self.verify_pin(user_id, pin),
)
popup.open()
def setup_pin_confirm(self, user_id, pin):
if len(pin) < 4:
err = MessagePopup(title="Fehler", message="Die PIN muss mindestens 4 Ziffern lang sein!")
err = MessagePopup(
title="Fehler", message="Die PIN muss mindestens 4 Ziffern lang sein!"
)
err.open()
return
popup = NumpadPopup(
title="PIN bestätigen",
is_password=True,
on_submit=lambda confirm_pin: self.save_new_pin(user_id, pin, confirm_pin)
on_submit=lambda confirm_pin: self.save_new_pin(user_id, pin, confirm_pin),
)
popup.open()
@@ -1048,7 +1071,7 @@ class UserSelectionScreen(StyledScreen):
app = App.get_running_app()
app.tracker.set_user_pin(user_id, pin)
app.current_user = user_id
self.manager.current = 'user_menu'
self.manager.current = "user_menu"
def verify_pin(self, user_id, pin):
app = App.get_running_app()
@@ -1056,7 +1079,7 @@ class UserSelectionScreen(StyledScreen):
if user.pin == pin:
app.current_user = user_id
self.manager.current = 'user_menu'
self.manager.current = "user_menu"
else:
err = MessagePopup(title="Fehler", message="Falsche PIN!")
err.open()
@@ -1125,7 +1148,7 @@ class UserMenuScreen(StyledScreen):
popup = NumpadPopup(
title="Aktuelle PIN eingeben",
is_password=True,
on_submit=lambda pin: self.verify_current_pin(pin)
on_submit=lambda pin: self.verify_current_pin(pin),
)
popup.open()
@@ -1137,7 +1160,7 @@ class UserMenuScreen(StyledScreen):
popup = NumpadPopup(
title="Neue PIN eingeben",
is_password=True,
on_submit=lambda new_pin: self.setup_new_pin(new_pin)
on_submit=lambda new_pin: self.setup_new_pin(new_pin),
)
popup.open()
else:
@@ -1146,14 +1169,16 @@ class UserMenuScreen(StyledScreen):
def setup_new_pin(self, new_pin):
if len(new_pin) < 4:
err = MessagePopup(title="Fehler", message="Die PIN muss mindestens 4 Ziffern lang sein!")
err = MessagePopup(
title="Fehler", message="Die PIN muss mindestens 4 Ziffern lang sein!"
)
err.open()
return
popup = NumpadPopup(
title="Neue PIN bestätigen",
is_password=True,
on_submit=lambda confirm_pin: self.save_changed_pin(new_pin, confirm_pin)
on_submit=lambda confirm_pin: self.save_changed_pin(new_pin, confirm_pin),
)
popup.open()
@@ -1374,7 +1399,7 @@ class AdminPasswordScreen(StyledScreen):
popup = KeyboardPopup(
title="Admin-Passwort eingeben",
is_password=True,
on_submit=self.on_password_submitted
on_submit=self.on_password_submitted,
)
popup.open()
@@ -1494,7 +1519,6 @@ class AdminMenuScreen(StyledScreen):
self.manager.current = "main_menu"
class AdminAdjustDebtScreen(StyledScreen):
def __init__(self, **kwargs):
super(AdminAdjustDebtScreen, self).__init__(
@@ -1666,8 +1690,8 @@ class AdminHistoryScreen(StyledScreen):
user = app.tracker.get_user_by_id(t.user_id)
user_name = user.name if user else "Gelöschter Benutzer"
if t.item_id == "adjustment":
item_name = "Anpassung"
if t.item_id in ("adjustment", "Abbuchung"):
item_name = "Abbuchung"
else:
item = app.tracker.get_items().get(t.item_id)
item_name = item.name if item else "Gelöschter Artikel"
@@ -1695,7 +1719,7 @@ class AdminManageUsersScreen(StyledScreen):
size_hint=(0.7, None),
height="50dp",
pos_hint={"center_x": 0.5},
btn_color=[0.0, 0.65, 0.57, 1]
btn_color=[0.0, 0.65, 0.57, 1],
)
btn_add.bind(on_release=self.open_create_user_popup)
self.content_layout.add_widget(btn_add)
@@ -1738,6 +1762,7 @@ class AdminManageUsersScreen(StyledScreen):
def create_user(self, name):
import uuid
app = App.get_running_app()
user_id = f"user_{uuid.uuid4().hex[:8]}"
res = app.tracker.add_user(user_id, name)
@@ -1745,15 +1770,12 @@ class AdminManageUsersScreen(StyledScreen):
if res["status"] == "success":
msg = MessagePopup(
title="Erfolg",
message=f"Benutzer {name} erfolgreich erstellt!\n(Standard-PIN: 4242)"
message=f"Benutzer {name} erfolgreich erstellt!\n(Standard-PIN: 4242)",
)
msg.open()
self.refresh_users()
else:
msg = MessagePopup(
title="Fehler",
message=f"Fehler: {res['message']}"
)
msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}")
msg.open()
def open_edit_user_popup(self, user_id, name, pin):
@@ -1762,7 +1784,7 @@ class AdminManageUsersScreen(StyledScreen):
current_name=name,
current_pin=pin,
on_save=self.save_user,
on_delete=self.confirm_delete_user
on_delete=self.confirm_delete_user,
)
popup.open()
@@ -1785,7 +1807,7 @@ class AdminManageUsersScreen(StyledScreen):
popup = ConfirmPopup(
title="Benutzer löschen",
message=f"Möchtest du den Benutzer {user.name} wirklich unwiderruflich löschen?",
on_confirm=lambda: self.delete_user(user_id)
on_confirm=lambda: self.delete_user(user_id),
)
popup.open()
@@ -1815,7 +1837,7 @@ class AdminManageItemsScreen(StyledScreen):
size_hint=(0.7, None),
height="50dp",
pos_hint={"center_x": 0.5},
btn_color=[0.0, 0.65, 0.57, 1]
btn_color=[0.0, 0.65, 0.57, 1],
)
btn_add.bind(on_release=self.open_create_item_popup)
self.content_layout.add_widget(btn_add)
@@ -1858,12 +1880,15 @@ class AdminManageItemsScreen(StyledScreen):
def create_item(self, name, price):
import uuid
app = App.get_running_app()
item_id = f"item_{uuid.uuid4().hex[:8]}"
res = app.tracker.add_item(item_id, name, price)
if res["status"] == "success":
msg = MessagePopup(title="Erfolg", message=f"Artikel {name} erfolgreich erstellt!")
msg = MessagePopup(
title="Erfolg", message=f"Artikel {name} erfolgreich erstellt!"
)
msg.open()
self.refresh_items()
else:
@@ -1876,7 +1901,7 @@ class AdminManageItemsScreen(StyledScreen):
current_name=name,
current_price=price,
on_save=self.save_item,
on_delete=self.confirm_delete_item
on_delete=self.confirm_delete_item,
)
popup.open()
@@ -1900,7 +1925,7 @@ class AdminManageItemsScreen(StyledScreen):
popup = ConfirmPopup(
title="Artikel löschen",
message=f"Möchtest du den Artikel {item.name} wirklich unwiderruflich löschen?",
on_confirm=lambda: self.delete_item(item_id)
on_confirm=lambda: self.delete_item(item_id),
)
popup.open()
@@ -1946,7 +1971,6 @@ class KuehlschrankApp(App):
return sm
class KivyUI:
def __init__(self, tracker):
self.tracker = tracker