Tests and helpers
This commit is contained in:
+10
-4
@@ -1,5 +1,6 @@
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from core.transaction import Transaction
|
from core.transaction import Transaction
|
||||||
|
from utils.helpers import validate_pin, parse_price
|
||||||
|
|
||||||
|
|
||||||
class Tracker:
|
class Tracker:
|
||||||
@@ -70,7 +71,7 @@ class Tracker:
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from core.transaction import Transaction
|
from core.transaction import Transaction
|
||||||
|
|
||||||
transaction = Transaction(str(uuid4()), user_id, "adjustment", -amount)
|
transaction = Transaction(str(uuid4()), user_id, "Abbuchung", -amount)
|
||||||
self.transactions.append(transaction)
|
self.transactions.append(transaction)
|
||||||
self.data_manager.save_transactions(self.transactions)
|
self.data_manager.save_transactions(self.transactions)
|
||||||
|
|
||||||
@@ -97,12 +98,15 @@ class Tracker:
|
|||||||
def set_user_pin(self, user_id, pin):
|
def set_user_pin(self, user_id, pin):
|
||||||
if user_id not in self.users:
|
if user_id not in self.users:
|
||||||
return {"status": "error", "message": "User not found"}
|
return {"status": "error", "message": "User not found"}
|
||||||
|
if not validate_pin(pin):
|
||||||
|
return {"status": "error", "message": "Invalid PIN format"}
|
||||||
self.users[user_id].pin = pin
|
self.users[user_id].pin = pin
|
||||||
self.data_manager.save_users(self.users)
|
self.data_manager.save_users(self.users)
|
||||||
return {"status": "success", "message": "PIN updated successfully"}
|
return {"status": "success", "message": "PIN updated successfully"}
|
||||||
|
|
||||||
def add_user(self, user_id, name):
|
def add_user(self, user_id, name):
|
||||||
from core.user import User
|
from core.user import User
|
||||||
|
|
||||||
if user_id in self.users:
|
if user_id in self.users:
|
||||||
return {"status": "error", "message": "User ID already exists"}
|
return {"status": "error", "message": "User ID already exists"}
|
||||||
self.users[user_id] = User(user_id, name, "4242")
|
self.users[user_id] = User(user_id, name, "4242")
|
||||||
@@ -123,6 +127,8 @@ class Tracker:
|
|||||||
def edit_user(self, user_id, name, pin=None):
|
def edit_user(self, user_id, name, pin=None):
|
||||||
if user_id not in self.users:
|
if user_id not in self.users:
|
||||||
return {"status": "error", "message": "User not found"}
|
return {"status": "error", "message": "User not found"}
|
||||||
|
if pin is not None and not validate_pin(pin):
|
||||||
|
return {"status": "error", "message": "Invalid PIN format"}
|
||||||
self.users[user_id].name = name
|
self.users[user_id].name = name
|
||||||
if pin is not None:
|
if pin is not None:
|
||||||
self.users[user_id].pin = pin
|
self.users[user_id].pin = pin
|
||||||
@@ -131,10 +137,11 @@ class Tracker:
|
|||||||
|
|
||||||
def add_item(self, item_id, name, price):
|
def add_item(self, item_id, name, price):
|
||||||
from core.item import Item
|
from core.item import Item
|
||||||
|
|
||||||
if item_id in self.items:
|
if item_id in self.items:
|
||||||
return {"status": "error", "message": "Item ID already exists"}
|
return {"status": "error", "message": "Item ID already exists"}
|
||||||
try:
|
try:
|
||||||
price_val = float(price)
|
price_val = parse_price(str(price))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return {"status": "error", "message": "Invalid price value"}
|
return {"status": "error", "message": "Invalid price value"}
|
||||||
self.items[item_id] = Item(item_id, name, price_val)
|
self.items[item_id] = Item(item_id, name, price_val)
|
||||||
@@ -153,11 +160,10 @@ class Tracker:
|
|||||||
if item_id not in self.items:
|
if item_id not in self.items:
|
||||||
return {"status": "error", "message": "Item not found"}
|
return {"status": "error", "message": "Item not found"}
|
||||||
try:
|
try:
|
||||||
price_val = float(price)
|
price_val = parse_price(str(price))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return {"status": "error", "message": "Invalid price value"}
|
return {"status": "error", "message": "Invalid price value"}
|
||||||
self.items[item_id].name = name
|
self.items[item_id].name = name
|
||||||
self.items[item_id].price = price_val
|
self.items[item_id].price = price_val
|
||||||
self.data_manager.save_items(self.items)
|
self.data_manager.save_items(self.items)
|
||||||
return {"status": "success", "message": f"Item {name} updated successfully"}
|
return {"status": "success", "message": f"Item {name} updated successfully"}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import unittest
|
||||||
|
from utils.helpers import format_currency, format_date, validate_pin, parse_price
|
||||||
|
|
||||||
|
class TestHelpers(unittest.TestCase):
|
||||||
|
def test_format_currency_german(self):
|
||||||
|
self.assertEqual(format_currency(2.50), "2,50 €")
|
||||||
|
self.assertEqual(format_currency(1234.567), "1234,57 €")
|
||||||
|
self.assertEqual(format_currency(0.0), "0,00 €")
|
||||||
|
|
||||||
|
def test_format_currency_non_german(self):
|
||||||
|
self.assertEqual(format_currency(2.50, symbol="$", is_german=False), "$2.50")
|
||||||
|
self.assertEqual(format_currency(0.99, symbol="£", is_german=False), "£0.99")
|
||||||
|
|
||||||
|
def test_format_date(self):
|
||||||
|
self.assertEqual(format_date("2026-06-02T15:00:00.000000"), "02.06.2026")
|
||||||
|
self.assertEqual(format_date("2026-12-25T18:30:00"), "25.12.2026")
|
||||||
|
self.assertEqual(format_date("invalid-date-format"), "invalid-da")
|
||||||
|
|
||||||
|
def test_validate_pin(self):
|
||||||
|
self.assertTrue(validate_pin("1234"))
|
||||||
|
self.assertTrue(validate_pin("4242"))
|
||||||
|
self.assertTrue(validate_pin("123456"))
|
||||||
|
self.assertFalse(validate_pin("123")) # too short
|
||||||
|
self.assertFalse(validate_pin("123a")) # non-numeric
|
||||||
|
self.assertFalse(validate_pin("")) # empty
|
||||||
|
|
||||||
|
def test_parse_price(self):
|
||||||
|
self.assertEqual(parse_price("2.50"), 2.50)
|
||||||
|
self.assertEqual(parse_price("2,50"), 2.50)
|
||||||
|
self.assertEqual(parse_price(" 0,99 "), 0.99)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
parse_price("-2.50") # negative
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
parse_price("abc") # non-numeric
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import unittest
|
||||||
|
from core.item import Item
|
||||||
|
|
||||||
|
class TestItem(unittest.TestCase):
|
||||||
|
def test_item_initialization(self):
|
||||||
|
item = Item("coffee", "Coffee", 2.50)
|
||||||
|
self.assertEqual(item.id, "coffee")
|
||||||
|
self.assertEqual(item.name, "Coffee")
|
||||||
|
self.assertEqual(item.price, 2.50)
|
||||||
|
|
||||||
|
def test_item_price_conversion(self):
|
||||||
|
item = Item("tea", "Tea", "1.80")
|
||||||
|
self.assertEqual(item.price, 1.80)
|
||||||
|
self.assertIsInstance(item.price, float)
|
||||||
|
|
||||||
|
def test_to_json(self):
|
||||||
|
item = Item("sandwich", "Sandwich", 5.00)
|
||||||
|
expected = {
|
||||||
|
"id": "sandwich",
|
||||||
|
"name": "Sandwich",
|
||||||
|
"price": 5.00
|
||||||
|
}
|
||||||
|
self.assertEqual(item.to_json(), expected)
|
||||||
|
|
||||||
|
def test_from_json(self):
|
||||||
|
data = {
|
||||||
|
"id": "sandwich",
|
||||||
|
"name": "Sandwich",
|
||||||
|
"price": 5.00
|
||||||
|
}
|
||||||
|
item = Item.from_json(data)
|
||||||
|
self.assertEqual(item.id, "sandwich")
|
||||||
|
self.assertEqual(item.name, "Sandwich")
|
||||||
|
self.assertEqual(item.price, 5.00)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+19
-7
@@ -6,7 +6,6 @@ from core.item import Item
|
|||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class TestTracker(unittest.TestCase):
|
class TestTracker(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.test_dir = "test_data"
|
self.test_dir = "test_data"
|
||||||
@@ -24,7 +23,6 @@ class TestTracker(unittest.TestCase):
|
|||||||
shutil.rmtree(self.test_dir)
|
shutil.rmtree(self.test_dir)
|
||||||
|
|
||||||
def test_adjust_debt_new_user(self):
|
def test_adjust_debt_new_user(self):
|
||||||
# Verify adjust_debt does not raise KeyError on uninitialized user debt
|
|
||||||
res = self.tracker.adjust_debt("test_user", 2.0, "admin123")
|
res = self.tracker.adjust_debt("test_user", 2.0, "admin123")
|
||||||
self.assertEqual(res["status"], "success")
|
self.assertEqual(res["status"], "success")
|
||||||
self.assertEqual(self.tracker.get_user_debt("test_user"), -2.0)
|
self.assertEqual(self.tracker.get_user_debt("test_user"), -2.0)
|
||||||
@@ -35,15 +33,12 @@ class TestTracker(unittest.TestCase):
|
|||||||
self.assertEqual(self.tracker.get_user_debt("test_user"), 2.50)
|
self.assertEqual(self.tracker.get_user_debt("test_user"), 2.50)
|
||||||
self.assertEqual(len(self.tracker.transactions), 1)
|
self.assertEqual(len(self.tracker.transactions), 1)
|
||||||
|
|
||||||
def test_default_pin(self):
|
|
||||||
user = User("new_user", "New User")
|
|
||||||
self.assertEqual(user.pin, "4242")
|
|
||||||
|
|
||||||
def test_add_user(self):
|
def test_add_user(self):
|
||||||
res = self.tracker.add_user("test_user_2", "Test Bob")
|
res = self.tracker.add_user("test_user_2", "Test Bob")
|
||||||
self.assertEqual(res["status"], "success")
|
self.assertEqual(res["status"], "success")
|
||||||
self.assertEqual(self.tracker.users["test_user_2"].name, "Test Bob")
|
self.assertEqual(self.tracker.users["test_user_2"].name, "Test Bob")
|
||||||
self.assertEqual(self.tracker.users["test_user_2"].pin, "4242")
|
self.assertEqual(self.tracker.users["test_user_2"].pin, "4242")
|
||||||
|
|
||||||
def test_remove_user(self):
|
def test_remove_user(self):
|
||||||
res = self.tracker.remove_user("test_user")
|
res = self.tracker.remove_user("test_user")
|
||||||
self.assertEqual(res["status"], "success")
|
self.assertEqual(res["status"], "success")
|
||||||
@@ -56,12 +51,26 @@ class TestTracker(unittest.TestCase):
|
|||||||
self.assertEqual(self.tracker.users["test_user"].name, "Updated Name")
|
self.assertEqual(self.tracker.users["test_user"].name, "Updated Name")
|
||||||
self.assertEqual(self.tracker.users["test_user"].pin, "9999")
|
self.assertEqual(self.tracker.users["test_user"].pin, "9999")
|
||||||
|
|
||||||
|
def test_set_user_pin_validation(self):
|
||||||
|
res = self.tracker.set_user_pin("test_user", "invalid_pin")
|
||||||
|
self.assertEqual(res["status"], "error")
|
||||||
|
self.assertEqual(self.tracker.users["test_user"].pin, "4242") # Unchanged
|
||||||
|
|
||||||
|
res2 = self.tracker.set_user_pin("test_user", "1234")
|
||||||
|
self.assertEqual(res2["status"], "success")
|
||||||
|
self.assertEqual(self.tracker.users["test_user"].pin, "1234")
|
||||||
|
|
||||||
def test_add_item(self):
|
def test_add_item(self):
|
||||||
res = self.tracker.add_item("new_item", "Donut", 1.80)
|
res = self.tracker.add_item("new_item", "Donut", 1.80)
|
||||||
self.assertEqual(res["status"], "success")
|
self.assertEqual(res["status"], "success")
|
||||||
self.assertEqual(self.tracker.items["new_item"].name, "Donut")
|
self.assertEqual(self.tracker.items["new_item"].name, "Donut")
|
||||||
self.assertEqual(self.tracker.items["new_item"].price, 1.80)
|
self.assertEqual(self.tracker.items["new_item"].price, 1.80)
|
||||||
|
|
||||||
|
def test_add_item_invalid_price(self):
|
||||||
|
res = self.tracker.add_item("invalid_item", "Cake", "-2.50")
|
||||||
|
self.assertEqual(res["status"], "error")
|
||||||
|
self.assertNotIn("invalid_item", self.tracker.items)
|
||||||
|
|
||||||
def test_remove_item(self):
|
def test_remove_item(self):
|
||||||
res = self.tracker.remove_item("test_item")
|
res = self.tracker.remove_item("test_item")
|
||||||
self.assertEqual(res["status"], "success")
|
self.assertEqual(res["status"], "success")
|
||||||
@@ -73,7 +82,10 @@ class TestTracker(unittest.TestCase):
|
|||||||
self.assertEqual(self.tracker.items["test_item"].name, "Premium Coffee")
|
self.assertEqual(self.tracker.items["test_item"].name, "Premium Coffee")
|
||||||
self.assertEqual(self.tracker.items["test_item"].price, 3.20)
|
self.assertEqual(self.tracker.items["test_item"].price, 3.20)
|
||||||
|
|
||||||
|
def test_edit_item_invalid_price(self):
|
||||||
|
res = self.tracker.edit_item("test_item", "Premium Coffee", "not_a_number")
|
||||||
|
self.assertEqual(res["status"], "error")
|
||||||
|
self.assertEqual(self.tracker.items["test_item"].price, 2.50) # Unchanged
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import unittest
|
||||||
|
from core.transaction import Transaction
|
||||||
|
|
||||||
|
|
||||||
|
class TestTransaction(unittest.TestCase):
|
||||||
|
def test_transaction_initialization_with_timestamp(self):
|
||||||
|
timestamp = "2026-06-02T15:00:00.000000"
|
||||||
|
t = Transaction("tx_1", "user_1", "item_1", 2.50, timestamp)
|
||||||
|
self.assertEqual(t.id, "tx_1")
|
||||||
|
self.assertEqual(t.user_id, "user_1")
|
||||||
|
self.assertEqual(t.item_id, "item_1")
|
||||||
|
self.assertEqual(t.amount, 2.50)
|
||||||
|
self.assertEqual(t.timestamp, timestamp)
|
||||||
|
|
||||||
|
def test_transaction_auto_timestamp(self):
|
||||||
|
t = Transaction("tx_2", "user_2", "item_2", 1.50)
|
||||||
|
self.assertIsNotNone(t.timestamp)
|
||||||
|
# Check if timestamp format is valid ISO string (at least parses or contains dates)
|
||||||
|
self.assertIn("-", t.timestamp)
|
||||||
|
self.assertIn("T", t.timestamp)
|
||||||
|
|
||||||
|
def test_to_json(self):
|
||||||
|
timestamp = "2026-06-02T15:00:00"
|
||||||
|
t = Transaction("tx_1", "user_1", "item_1", 2.50, timestamp)
|
||||||
|
expected = {
|
||||||
|
"id": "tx_1",
|
||||||
|
"user_id": "user_1",
|
||||||
|
"item_id": "item_1",
|
||||||
|
"amount": 2.50,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
}
|
||||||
|
self.assertEqual(t.to_json(), expected)
|
||||||
|
|
||||||
|
def test_from_json(self):
|
||||||
|
data = {
|
||||||
|
"id": "tx_1",
|
||||||
|
"user_id": "user_1",
|
||||||
|
"item_id": "item_1",
|
||||||
|
"amount": 2.50,
|
||||||
|
"timestamp": "2026-06-02T15:00:00",
|
||||||
|
}
|
||||||
|
t = Transaction.from_json(data)
|
||||||
|
self.assertEqual(t.id, "tx_1")
|
||||||
|
self.assertEqual(t.user_id, "user_1")
|
||||||
|
self.assertEqual(t.item_id, "item_1")
|
||||||
|
self.assertEqual(t.amount, 2.50)
|
||||||
|
self.assertEqual(t.timestamp, "2026-06-02T15:00:00")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import unittest
|
||||||
|
from core.user import User
|
||||||
|
|
||||||
|
class TestUser(unittest.TestCase):
|
||||||
|
def test_default_values(self):
|
||||||
|
user = User("alice", "Alice")
|
||||||
|
self.assertEqual(user.id, "alice")
|
||||||
|
self.assertEqual(user.name, "Alice")
|
||||||
|
self.assertEqual(user.pin, "4242")
|
||||||
|
self.assertEqual(user.debt, 0.0)
|
||||||
|
|
||||||
|
def test_custom_pin(self):
|
||||||
|
user = User("bob", "Bob", "1234")
|
||||||
|
self.assertEqual(user.pin, "1234")
|
||||||
|
|
||||||
|
def test_to_json(self):
|
||||||
|
user = User("charlie", "Charlie", "9999")
|
||||||
|
user.debt = 12.50
|
||||||
|
expected = {
|
||||||
|
"id": "charlie",
|
||||||
|
"name": "Charlie",
|
||||||
|
"debt": 12.50,
|
||||||
|
"pin": "9999"
|
||||||
|
}
|
||||||
|
self.assertEqual(user.to_json(), expected)
|
||||||
|
|
||||||
|
def test_from_json(self):
|
||||||
|
data = {
|
||||||
|
"id": "charlie",
|
||||||
|
"name": "Charlie",
|
||||||
|
"debt": 12.50,
|
||||||
|
"pin": "9999"
|
||||||
|
}
|
||||||
|
user = User.from_json(data)
|
||||||
|
self.assertEqual(user.id, "charlie")
|
||||||
|
self.assertEqual(user.name, "Charlie")
|
||||||
|
self.assertEqual(user.debt, 12.50)
|
||||||
|
self.assertEqual(user.pin, "9999")
|
||||||
|
|
||||||
|
def test_from_json_missing_pin(self):
|
||||||
|
data = {
|
||||||
|
"id": "charlie",
|
||||||
|
"name": "Charlie",
|
||||||
|
"debt": 12.50
|
||||||
|
}
|
||||||
|
user = User.from_json(data)
|
||||||
|
self.assertEqual(user.pin, "4242")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -364,7 +364,7 @@ class CLI:
|
|||||||
for t in sorted(transactions, key=lambda x: x.timestamp, reverse=True):
|
for t in sorted(transactions, key=lambda x: x.timestamp, reverse=True):
|
||||||
user = self.tracker.get_user_by_id(t.user_id)
|
user = self.tracker.get_user_by_id(t.user_id)
|
||||||
item = self.tracker.get_items().get(t.item_id)
|
item = self.tracker.get_items().get(t.item_id)
|
||||||
item_name = item.name if item else ("Adjustment" if t.item_id == "adjustment" else "Deleted Item")
|
item_name = item.name if item else ("Abbuchung" if t.item_id in ("adjustment", "Abbuchung") else "Deleted Item")
|
||||||
print(
|
print(
|
||||||
f"{t.timestamp[:10]} | {user.name:.<20} {item_name:.<15} ${t.amount:.2f}"
|
f"{t.timestamp[:10]} | {user.name:.<20} {item_name:.<15} ${t.amount:.2f}"
|
||||||
)
|
)
|
||||||
|
|||||||
+133
-109
@@ -1,6 +1,7 @@
|
|||||||
# type: ignore
|
## type: ignore
|
||||||
import kivy
|
import kivy
|
||||||
from kivy.app import App
|
from kivy.app import App
|
||||||
|
from kivy.graphics import Color, RoundedRectangle
|
||||||
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
|
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
|
||||||
from kivy.uix.boxlayout import BoxLayout
|
from kivy.uix.boxlayout import BoxLayout
|
||||||
from kivy.uix.gridlayout import GridLayout
|
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.textinput import TextInput
|
||||||
from kivy.uix.popup import Popup
|
from kivy.uix.popup import Popup
|
||||||
from kivy.core.window import Window
|
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):
|
class RoundedButton(Button):
|
||||||
@@ -104,45 +105,57 @@ class ConfirmPopup(Popup):
|
|||||||
|
|
||||||
|
|
||||||
class NumpadPopup(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_password = is_password
|
||||||
self.is_float = is_float
|
self.is_float = is_float
|
||||||
self.on_submit = on_submit
|
self.on_submit = on_submit
|
||||||
self.entered_text = ""
|
self.entered_text = ""
|
||||||
|
|
||||||
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
|
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
|
||||||
|
|
||||||
self.display = Label(
|
self.display = Label(
|
||||||
text="PIN eingeben:" if is_password else "Betrag eingeben:",
|
text="PIN eingeben:" if is_password else "Betrag eingeben:",
|
||||||
font_size='20sp',
|
font_size="20sp",
|
||||||
bold=True,
|
bold=True,
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
halign='center',
|
halign="center",
|
||||||
valign='middle'
|
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)
|
content.add_widget(self.display)
|
||||||
|
|
||||||
grid = GridLayout(cols=3, spacing=10, size_hint_y=0.6)
|
grid = GridLayout(cols=3, spacing=10, size_hint_y=0.6)
|
||||||
|
|
||||||
buttons = [
|
buttons = [
|
||||||
'1', '2', '3',
|
"1",
|
||||||
'4', '5', '6',
|
"2",
|
||||||
'7', '8', '9',
|
"3",
|
||||||
',' if is_float else 'C', '0', '⌫'
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
"," if is_float else "C",
|
||||||
|
"0",
|
||||||
|
"⌫",
|
||||||
]
|
]
|
||||||
|
|
||||||
for btn_text in buttons:
|
for btn_text in buttons:
|
||||||
btn = RoundedButton(
|
btn = RoundedButton(
|
||||||
text=btn_text,
|
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)
|
btn.bind(on_release=self.on_key_press)
|
||||||
grid.add_widget(btn)
|
grid.add_widget(btn)
|
||||||
|
|
||||||
content.add_widget(grid)
|
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])
|
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])
|
cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1])
|
||||||
|
|
||||||
@@ -154,10 +167,10 @@ class NumpadPopup(Popup):
|
|||||||
title=title,
|
title=title,
|
||||||
content=content,
|
content=content,
|
||||||
size_hint=(0.85, 0.75),
|
size_hint=(0.85, 0.75),
|
||||||
title_align='center',
|
title_align="center",
|
||||||
title_size='18sp',
|
title_size="18sp",
|
||||||
background_color=[0.08, 0.08, 0.09, 0.95],
|
background_color=[0.08, 0.08, 0.09, 0.95],
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
ok_btn.bind(on_release=self.submit)
|
ok_btn.bind(on_release=self.submit)
|
||||||
@@ -165,20 +178,22 @@ class NumpadPopup(Popup):
|
|||||||
|
|
||||||
def on_key_press(self, instance):
|
def on_key_press(self, instance):
|
||||||
key = instance.text
|
key = instance.text
|
||||||
if key == '⌫':
|
if key == "⌫":
|
||||||
self.entered_text = self.entered_text[:-1]
|
self.entered_text = self.entered_text[:-1]
|
||||||
elif key == 'C':
|
elif key == "C":
|
||||||
self.entered_text = ""
|
self.entered_text = ""
|
||||||
elif key == ',':
|
elif key == ",":
|
||||||
if ',' not in self.entered_text:
|
if "," not in self.entered_text:
|
||||||
self.entered_text += ','
|
self.entered_text += ","
|
||||||
else:
|
else:
|
||||||
self.entered_text += key
|
self.entered_text += key
|
||||||
self.update_display()
|
self.update_display()
|
||||||
|
|
||||||
def update_display(self):
|
def update_display(self):
|
||||||
if not self.entered_text:
|
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:
|
else:
|
||||||
if self.is_password:
|
if self.is_password:
|
||||||
self.display.text = "*" * len(self.entered_text)
|
self.display.text = "*" * len(self.entered_text)
|
||||||
@@ -197,34 +212,36 @@ class KeyboardPopup(Popup):
|
|||||||
self.on_submit = on_submit
|
self.on_submit = on_submit
|
||||||
self.entered_text = ""
|
self.entered_text = ""
|
||||||
|
|
||||||
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
|
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
|
||||||
|
|
||||||
self.display = Label(
|
self.display = Label(
|
||||||
text="Eingeben:" if not is_password else "****",
|
text="Eingeben:" if not is_password else "****",
|
||||||
font_size='20sp',
|
font_size="20sp",
|
||||||
bold=True,
|
bold=True,
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
halign='center',
|
halign="center",
|
||||||
valign='middle'
|
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)
|
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 = [
|
rows = [
|
||||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
|
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
|
||||||
['q', 'w', 'e', 'r', 't', 'z', 'u', 'i', 'o', 'p'],
|
["q", "w", "e", "r", "t", "z", "u", "i", "o", "p"],
|
||||||
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
|
["a", "s", "d", "f", "g", "h", "j", "k", "l"],
|
||||||
['y', 'x', 'c', 'v', 'b', 'n', 'm', '⌫', 'C']
|
["y", "x", "c", "v", "b", "n", "m", "⌫", "C"],
|
||||||
]
|
]
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
row_box = BoxLayout(orientation='horizontal', spacing=5)
|
row_box = BoxLayout(orientation="horizontal", spacing=5)
|
||||||
for char in row:
|
for char in row:
|
||||||
btn = RoundedButton(
|
btn = RoundedButton(
|
||||||
text=char,
|
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)
|
btn.bind(on_release=self.on_key_press)
|
||||||
row_box.add_widget(btn)
|
row_box.add_widget(btn)
|
||||||
@@ -232,7 +249,7 @@ class KeyboardPopup(Popup):
|
|||||||
|
|
||||||
content.add_widget(kbd_layout)
|
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])
|
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])
|
cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1])
|
||||||
|
|
||||||
@@ -244,10 +261,10 @@ class KeyboardPopup(Popup):
|
|||||||
title=title,
|
title=title,
|
||||||
content=content,
|
content=content,
|
||||||
size_hint=(0.95, 0.8),
|
size_hint=(0.95, 0.8),
|
||||||
title_align='center',
|
title_align="center",
|
||||||
title_size='18sp',
|
title_size="18sp",
|
||||||
background_color=[0.08, 0.08, 0.09, 0.95],
|
background_color=[0.08, 0.08, 0.09, 0.95],
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
ok_btn.bind(on_release=self.submit)
|
ok_btn.bind(on_release=self.submit)
|
||||||
@@ -255,9 +272,9 @@ class KeyboardPopup(Popup):
|
|||||||
|
|
||||||
def on_key_press(self, instance):
|
def on_key_press(self, instance):
|
||||||
key = instance.text
|
key = instance.text
|
||||||
if key == '⌫':
|
if key == "⌫":
|
||||||
self.entered_text = self.entered_text[:-1]
|
self.entered_text = self.entered_text[:-1]
|
||||||
elif key == 'C':
|
elif key == "C":
|
||||||
self.entered_text = ""
|
self.entered_text = ""
|
||||||
else:
|
else:
|
||||||
self.entered_text += key
|
self.entered_text += key
|
||||||
@@ -338,7 +355,7 @@ class AdjustDebtPopup(Popup):
|
|||||||
title="Betrag eingeben",
|
title="Betrag eingeben",
|
||||||
is_password=False,
|
is_password=False,
|
||||||
is_float=True,
|
is_float=True,
|
||||||
on_submit=self.on_amount_submitted
|
on_submit=self.on_amount_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -362,16 +379,16 @@ class CreateUserPopup(Popup):
|
|||||||
def __init__(self, on_create, **kwargs):
|
def __init__(self, on_create, **kwargs):
|
||||||
self.on_create = on_create
|
self.on_create = on_create
|
||||||
|
|
||||||
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
|
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
|
||||||
|
|
||||||
info = Label(
|
info = Label(
|
||||||
text="Name des neuen Benutzers eingeben:",
|
text="Name des neuen Benutzers eingeben:",
|
||||||
font_size='16sp',
|
font_size="16sp",
|
||||||
size_hint_y=0.25,
|
size_hint_y=0.25,
|
||||||
halign='center',
|
halign="center",
|
||||||
valign='middle'
|
valign="middle",
|
||||||
)
|
)
|
||||||
info.bind(size=info.setter('text_size'))
|
info.bind(size=info.setter("text_size"))
|
||||||
content.add_widget(info)
|
content.add_widget(info)
|
||||||
|
|
||||||
self.name_input = TextInput(
|
self.name_input = TextInput(
|
||||||
@@ -384,7 +401,7 @@ class CreateUserPopup(Popup):
|
|||||||
foreground_color=[1, 1, 1, 1],
|
foreground_color=[1, 1, 1, 1],
|
||||||
cursor_color=[1, 1, 1, 1],
|
cursor_color=[1, 1, 1, 1],
|
||||||
size_hint_y=0.3,
|
size_hint_y=0.3,
|
||||||
padding=[10, 10]
|
padding=[10, 10],
|
||||||
)
|
)
|
||||||
self.name_input.bind(focus=self.trigger_keyboard)
|
self.name_input.bind(focus=self.trigger_keyboard)
|
||||||
content.add_widget(self.name_input)
|
content.add_widget(self.name_input)
|
||||||
@@ -417,7 +434,7 @@ class CreateUserPopup(Popup):
|
|||||||
popup = KeyboardPopup(
|
popup = KeyboardPopup(
|
||||||
title="Name eingeben",
|
title="Name eingeben",
|
||||||
is_password=False,
|
is_password=False,
|
||||||
on_submit=self.on_name_submitted
|
on_submit=self.on_name_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -435,21 +452,23 @@ class CreateUserPopup(Popup):
|
|||||||
|
|
||||||
|
|
||||||
class EditUserPopup(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.user_id = user_id
|
||||||
self.on_save = on_save
|
self.on_save = on_save
|
||||||
self.on_delete = on_delete
|
self.on_delete = on_delete
|
||||||
|
|
||||||
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
|
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
|
||||||
|
|
||||||
info = Label(
|
info = Label(
|
||||||
text="Benutzer bearbeiten:",
|
text="Benutzer bearbeiten:",
|
||||||
font_size='16sp',
|
font_size="16sp",
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
halign='center',
|
halign="center",
|
||||||
valign='middle'
|
valign="middle",
|
||||||
)
|
)
|
||||||
info.bind(size=info.setter('text_size'))
|
info.bind(size=info.setter("text_size"))
|
||||||
content.add_widget(info)
|
content.add_widget(info)
|
||||||
|
|
||||||
content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left"))
|
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],
|
foreground_color=[1, 1, 1, 1],
|
||||||
cursor_color=[1, 1, 1, 1],
|
cursor_color=[1, 1, 1, 1],
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
padding=[10, 10]
|
padding=[10, 10],
|
||||||
)
|
)
|
||||||
self.name_input.bind(focus=self.trigger_keyboard)
|
self.name_input.bind(focus=self.trigger_keyboard)
|
||||||
content.add_widget(self.name_input)
|
content.add_widget(self.name_input)
|
||||||
@@ -479,7 +498,7 @@ class EditUserPopup(Popup):
|
|||||||
foreground_color=[1, 1, 1, 1],
|
foreground_color=[1, 1, 1, 1],
|
||||||
cursor_color=[1, 1, 1, 1],
|
cursor_color=[1, 1, 1, 1],
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
padding=[10, 10]
|
padding=[10, 10],
|
||||||
)
|
)
|
||||||
self.pin_input.bind(focus=self.trigger_numpad)
|
self.pin_input.bind(focus=self.trigger_numpad)
|
||||||
content.add_widget(self.pin_input)
|
content.add_widget(self.pin_input)
|
||||||
@@ -515,7 +534,7 @@ class EditUserPopup(Popup):
|
|||||||
popup = KeyboardPopup(
|
popup = KeyboardPopup(
|
||||||
title="Name eingeben",
|
title="Name eingeben",
|
||||||
is_password=False,
|
is_password=False,
|
||||||
on_submit=self.on_name_submitted
|
on_submit=self.on_name_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -526,9 +545,7 @@ class EditUserPopup(Popup):
|
|||||||
if value:
|
if value:
|
||||||
instance.focus = False
|
instance.focus = False
|
||||||
popup = NumpadPopup(
|
popup = NumpadPopup(
|
||||||
title="PIN eingeben",
|
title="PIN eingeben", is_password=False, on_submit=self.on_pin_submitted
|
||||||
is_password=False,
|
|
||||||
on_submit=self.on_pin_submitted
|
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -543,7 +560,9 @@ class EditUserPopup(Popup):
|
|||||||
err.open()
|
err.open()
|
||||||
return
|
return
|
||||||
if len(pin) < 4:
|
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()
|
err.open()
|
||||||
return
|
return
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
@@ -558,16 +577,16 @@ class CreateItemPopup(Popup):
|
|||||||
def __init__(self, on_create, **kwargs):
|
def __init__(self, on_create, **kwargs):
|
||||||
self.on_create = on_create
|
self.on_create = on_create
|
||||||
|
|
||||||
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
|
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
|
||||||
|
|
||||||
info = Label(
|
info = Label(
|
||||||
text="Neuen Artikel hinzufügen:",
|
text="Neuen Artikel hinzufügen:",
|
||||||
font_size='16sp',
|
font_size="16sp",
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
halign='center',
|
halign="center",
|
||||||
valign='middle'
|
valign="middle",
|
||||||
)
|
)
|
||||||
info.bind(size=info.setter('text_size'))
|
info.bind(size=info.setter("text_size"))
|
||||||
content.add_widget(info)
|
content.add_widget(info)
|
||||||
|
|
||||||
content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left"))
|
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],
|
foreground_color=[1, 1, 1, 1],
|
||||||
cursor_color=[1, 1, 1, 1],
|
cursor_color=[1, 1, 1, 1],
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
padding=[10, 10]
|
padding=[10, 10],
|
||||||
)
|
)
|
||||||
self.name_input.bind(focus=self.trigger_keyboard)
|
self.name_input.bind(focus=self.trigger_keyboard)
|
||||||
content.add_widget(self.name_input)
|
content.add_widget(self.name_input)
|
||||||
@@ -597,7 +616,7 @@ class CreateItemPopup(Popup):
|
|||||||
foreground_color=[1, 1, 1, 1],
|
foreground_color=[1, 1, 1, 1],
|
||||||
cursor_color=[1, 1, 1, 1],
|
cursor_color=[1, 1, 1, 1],
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
padding=[10, 10]
|
padding=[10, 10],
|
||||||
)
|
)
|
||||||
self.price_input.bind(focus=self.trigger_numpad)
|
self.price_input.bind(focus=self.trigger_numpad)
|
||||||
content.add_widget(self.price_input)
|
content.add_widget(self.price_input)
|
||||||
@@ -630,7 +649,7 @@ class CreateItemPopup(Popup):
|
|||||||
popup = KeyboardPopup(
|
popup = KeyboardPopup(
|
||||||
title="Artikelname eingeben",
|
title="Artikelname eingeben",
|
||||||
is_password=False,
|
is_password=False,
|
||||||
on_submit=self.on_name_submitted
|
on_submit=self.on_name_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -644,7 +663,7 @@ class CreateItemPopup(Popup):
|
|||||||
title="Preis eingeben",
|
title="Preis eingeben",
|
||||||
is_password=False,
|
is_password=False,
|
||||||
is_float=True,
|
is_float=True,
|
||||||
on_submit=self.on_price_submitted
|
on_submit=self.on_price_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -672,21 +691,23 @@ class CreateItemPopup(Popup):
|
|||||||
|
|
||||||
|
|
||||||
class EditItemPopup(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.item_id = item_id
|
||||||
self.on_save = on_save
|
self.on_save = on_save
|
||||||
self.on_delete = on_delete
|
self.on_delete = on_delete
|
||||||
|
|
||||||
content = BoxLayout(orientation='vertical', padding=15, spacing=15)
|
content = BoxLayout(orientation="vertical", padding=15, spacing=15)
|
||||||
|
|
||||||
info = Label(
|
info = Label(
|
||||||
text="Artikel bearbeiten:",
|
text="Artikel bearbeiten:",
|
||||||
font_size='16sp',
|
font_size="16sp",
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
halign='center',
|
halign="center",
|
||||||
valign='middle'
|
valign="middle",
|
||||||
)
|
)
|
||||||
info.bind(size=info.setter('text_size'))
|
info.bind(size=info.setter("text_size"))
|
||||||
content.add_widget(info)
|
content.add_widget(info)
|
||||||
|
|
||||||
content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left"))
|
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],
|
foreground_color=[1, 1, 1, 1],
|
||||||
cursor_color=[1, 1, 1, 1],
|
cursor_color=[1, 1, 1, 1],
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
padding=[10, 10]
|
padding=[10, 10],
|
||||||
)
|
)
|
||||||
self.name_input.bind(focus=self.trigger_keyboard)
|
self.name_input.bind(focus=self.trigger_keyboard)
|
||||||
content.add_widget(self.name_input)
|
content.add_widget(self.name_input)
|
||||||
@@ -716,7 +737,7 @@ class EditItemPopup(Popup):
|
|||||||
foreground_color=[1, 1, 1, 1],
|
foreground_color=[1, 1, 1, 1],
|
||||||
cursor_color=[1, 1, 1, 1],
|
cursor_color=[1, 1, 1, 1],
|
||||||
size_hint_y=0.15,
|
size_hint_y=0.15,
|
||||||
padding=[10, 10]
|
padding=[10, 10],
|
||||||
)
|
)
|
||||||
self.price_input.bind(focus=self.trigger_numpad)
|
self.price_input.bind(focus=self.trigger_numpad)
|
||||||
content.add_widget(self.price_input)
|
content.add_widget(self.price_input)
|
||||||
@@ -752,7 +773,7 @@ class EditItemPopup(Popup):
|
|||||||
popup = KeyboardPopup(
|
popup = KeyboardPopup(
|
||||||
title="Artikelname eingeben",
|
title="Artikelname eingeben",
|
||||||
is_password=False,
|
is_password=False,
|
||||||
on_submit=self.on_name_submitted
|
on_submit=self.on_name_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -766,7 +787,7 @@ class EditItemPopup(Popup):
|
|||||||
title="Preis eingeben",
|
title="Preis eingeben",
|
||||||
is_password=False,
|
is_password=False,
|
||||||
is_float=True,
|
is_float=True,
|
||||||
on_submit=self.on_price_submitted
|
on_submit=self.on_price_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -847,7 +868,7 @@ class TransactionRow(BoxLayout):
|
|||||||
self.bind(pos=self.update_rect, size=self.update_rect)
|
self.bind(pos=self.update_rect, size=self.update_rect)
|
||||||
|
|
||||||
date_lbl = Label(
|
date_lbl = Label(
|
||||||
text=date[:10],
|
text=format_date(date),
|
||||||
font_size="14sp",
|
font_size="14sp",
|
||||||
size_hint_x=0.25,
|
size_hint_x=0.25,
|
||||||
halign="left",
|
halign="left",
|
||||||
@@ -1015,27 +1036,29 @@ class UserSelectionScreen(StyledScreen):
|
|||||||
popup = NumpadPopup(
|
popup = NumpadPopup(
|
||||||
title=f"PIN einrichten: {user.name}",
|
title=f"PIN einrichten: {user.name}",
|
||||||
is_password=True,
|
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()
|
popup.open()
|
||||||
else:
|
else:
|
||||||
popup = NumpadPopup(
|
popup = NumpadPopup(
|
||||||
title=f"PIN eingeben: {user.name}",
|
title=f"PIN eingeben: {user.name}",
|
||||||
is_password=True,
|
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()
|
popup.open()
|
||||||
|
|
||||||
def setup_pin_confirm(self, user_id, pin):
|
def setup_pin_confirm(self, user_id, pin):
|
||||||
if len(pin) < 4:
|
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()
|
err.open()
|
||||||
return
|
return
|
||||||
|
|
||||||
popup = NumpadPopup(
|
popup = NumpadPopup(
|
||||||
title="PIN bestätigen",
|
title="PIN bestätigen",
|
||||||
is_password=True,
|
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()
|
popup.open()
|
||||||
|
|
||||||
@@ -1048,7 +1071,7 @@ class UserSelectionScreen(StyledScreen):
|
|||||||
app = App.get_running_app()
|
app = App.get_running_app()
|
||||||
app.tracker.set_user_pin(user_id, pin)
|
app.tracker.set_user_pin(user_id, pin)
|
||||||
app.current_user = user_id
|
app.current_user = user_id
|
||||||
self.manager.current = 'user_menu'
|
self.manager.current = "user_menu"
|
||||||
|
|
||||||
def verify_pin(self, user_id, pin):
|
def verify_pin(self, user_id, pin):
|
||||||
app = App.get_running_app()
|
app = App.get_running_app()
|
||||||
@@ -1056,7 +1079,7 @@ class UserSelectionScreen(StyledScreen):
|
|||||||
|
|
||||||
if user.pin == pin:
|
if user.pin == pin:
|
||||||
app.current_user = user_id
|
app.current_user = user_id
|
||||||
self.manager.current = 'user_menu'
|
self.manager.current = "user_menu"
|
||||||
else:
|
else:
|
||||||
err = MessagePopup(title="Fehler", message="Falsche PIN!")
|
err = MessagePopup(title="Fehler", message="Falsche PIN!")
|
||||||
err.open()
|
err.open()
|
||||||
@@ -1125,7 +1148,7 @@ class UserMenuScreen(StyledScreen):
|
|||||||
popup = NumpadPopup(
|
popup = NumpadPopup(
|
||||||
title="Aktuelle PIN eingeben",
|
title="Aktuelle PIN eingeben",
|
||||||
is_password=True,
|
is_password=True,
|
||||||
on_submit=lambda pin: self.verify_current_pin(pin)
|
on_submit=lambda pin: self.verify_current_pin(pin),
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -1137,7 +1160,7 @@ class UserMenuScreen(StyledScreen):
|
|||||||
popup = NumpadPopup(
|
popup = NumpadPopup(
|
||||||
title="Neue PIN eingeben",
|
title="Neue PIN eingeben",
|
||||||
is_password=True,
|
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()
|
popup.open()
|
||||||
else:
|
else:
|
||||||
@@ -1146,14 +1169,16 @@ class UserMenuScreen(StyledScreen):
|
|||||||
|
|
||||||
def setup_new_pin(self, new_pin):
|
def setup_new_pin(self, new_pin):
|
||||||
if len(new_pin) < 4:
|
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()
|
err.open()
|
||||||
return
|
return
|
||||||
|
|
||||||
popup = NumpadPopup(
|
popup = NumpadPopup(
|
||||||
title="Neue PIN bestätigen",
|
title="Neue PIN bestätigen",
|
||||||
is_password=True,
|
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()
|
popup.open()
|
||||||
|
|
||||||
@@ -1374,7 +1399,7 @@ class AdminPasswordScreen(StyledScreen):
|
|||||||
popup = KeyboardPopup(
|
popup = KeyboardPopup(
|
||||||
title="Admin-Passwort eingeben",
|
title="Admin-Passwort eingeben",
|
||||||
is_password=True,
|
is_password=True,
|
||||||
on_submit=self.on_password_submitted
|
on_submit=self.on_password_submitted,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -1494,7 +1519,6 @@ class AdminMenuScreen(StyledScreen):
|
|||||||
self.manager.current = "main_menu"
|
self.manager.current = "main_menu"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AdminAdjustDebtScreen(StyledScreen):
|
class AdminAdjustDebtScreen(StyledScreen):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(AdminAdjustDebtScreen, self).__init__(
|
super(AdminAdjustDebtScreen, self).__init__(
|
||||||
@@ -1666,8 +1690,8 @@ class AdminHistoryScreen(StyledScreen):
|
|||||||
user = app.tracker.get_user_by_id(t.user_id)
|
user = app.tracker.get_user_by_id(t.user_id)
|
||||||
user_name = user.name if user else "Gelöschter Benutzer"
|
user_name = user.name if user else "Gelöschter Benutzer"
|
||||||
|
|
||||||
if t.item_id == "adjustment":
|
if t.item_id in ("adjustment", "Abbuchung"):
|
||||||
item_name = "Anpassung"
|
item_name = "Abbuchung"
|
||||||
else:
|
else:
|
||||||
item = app.tracker.get_items().get(t.item_id)
|
item = app.tracker.get_items().get(t.item_id)
|
||||||
item_name = item.name if item else "Gelöschter Artikel"
|
item_name = item.name if item else "Gelöschter Artikel"
|
||||||
@@ -1695,7 +1719,7 @@ class AdminManageUsersScreen(StyledScreen):
|
|||||||
size_hint=(0.7, None),
|
size_hint=(0.7, None),
|
||||||
height="50dp",
|
height="50dp",
|
||||||
pos_hint={"center_x": 0.5},
|
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)
|
btn_add.bind(on_release=self.open_create_user_popup)
|
||||||
self.content_layout.add_widget(btn_add)
|
self.content_layout.add_widget(btn_add)
|
||||||
@@ -1738,6 +1762,7 @@ class AdminManageUsersScreen(StyledScreen):
|
|||||||
|
|
||||||
def create_user(self, name):
|
def create_user(self, name):
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
app = App.get_running_app()
|
app = App.get_running_app()
|
||||||
user_id = f"user_{uuid.uuid4().hex[:8]}"
|
user_id = f"user_{uuid.uuid4().hex[:8]}"
|
||||||
res = app.tracker.add_user(user_id, name)
|
res = app.tracker.add_user(user_id, name)
|
||||||
@@ -1745,15 +1770,12 @@ class AdminManageUsersScreen(StyledScreen):
|
|||||||
if res["status"] == "success":
|
if res["status"] == "success":
|
||||||
msg = MessagePopup(
|
msg = MessagePopup(
|
||||||
title="Erfolg",
|
title="Erfolg",
|
||||||
message=f"Benutzer {name} erfolgreich erstellt!\n(Standard-PIN: 4242)"
|
message=f"Benutzer {name} erfolgreich erstellt!\n(Standard-PIN: 4242)",
|
||||||
)
|
)
|
||||||
msg.open()
|
msg.open()
|
||||||
self.refresh_users()
|
self.refresh_users()
|
||||||
else:
|
else:
|
||||||
msg = MessagePopup(
|
msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}")
|
||||||
title="Fehler",
|
|
||||||
message=f"Fehler: {res['message']}"
|
|
||||||
)
|
|
||||||
msg.open()
|
msg.open()
|
||||||
|
|
||||||
def open_edit_user_popup(self, user_id, name, pin):
|
def open_edit_user_popup(self, user_id, name, pin):
|
||||||
@@ -1762,7 +1784,7 @@ class AdminManageUsersScreen(StyledScreen):
|
|||||||
current_name=name,
|
current_name=name,
|
||||||
current_pin=pin,
|
current_pin=pin,
|
||||||
on_save=self.save_user,
|
on_save=self.save_user,
|
||||||
on_delete=self.confirm_delete_user
|
on_delete=self.confirm_delete_user,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -1785,7 +1807,7 @@ class AdminManageUsersScreen(StyledScreen):
|
|||||||
popup = ConfirmPopup(
|
popup = ConfirmPopup(
|
||||||
title="Benutzer löschen",
|
title="Benutzer löschen",
|
||||||
message=f"Möchtest du den Benutzer {user.name} wirklich unwiderruflich 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()
|
popup.open()
|
||||||
|
|
||||||
@@ -1815,7 +1837,7 @@ class AdminManageItemsScreen(StyledScreen):
|
|||||||
size_hint=(0.7, None),
|
size_hint=(0.7, None),
|
||||||
height="50dp",
|
height="50dp",
|
||||||
pos_hint={"center_x": 0.5},
|
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)
|
btn_add.bind(on_release=self.open_create_item_popup)
|
||||||
self.content_layout.add_widget(btn_add)
|
self.content_layout.add_widget(btn_add)
|
||||||
@@ -1858,12 +1880,15 @@ class AdminManageItemsScreen(StyledScreen):
|
|||||||
|
|
||||||
def create_item(self, name, price):
|
def create_item(self, name, price):
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
app = App.get_running_app()
|
app = App.get_running_app()
|
||||||
item_id = f"item_{uuid.uuid4().hex[:8]}"
|
item_id = f"item_{uuid.uuid4().hex[:8]}"
|
||||||
res = app.tracker.add_item(item_id, name, price)
|
res = app.tracker.add_item(item_id, name, price)
|
||||||
|
|
||||||
if res["status"] == "success":
|
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()
|
msg.open()
|
||||||
self.refresh_items()
|
self.refresh_items()
|
||||||
else:
|
else:
|
||||||
@@ -1876,7 +1901,7 @@ class AdminManageItemsScreen(StyledScreen):
|
|||||||
current_name=name,
|
current_name=name,
|
||||||
current_price=price,
|
current_price=price,
|
||||||
on_save=self.save_item,
|
on_save=self.save_item,
|
||||||
on_delete=self.confirm_delete_item
|
on_delete=self.confirm_delete_item,
|
||||||
)
|
)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
@@ -1900,7 +1925,7 @@ class AdminManageItemsScreen(StyledScreen):
|
|||||||
popup = ConfirmPopup(
|
popup = ConfirmPopup(
|
||||||
title="Artikel löschen",
|
title="Artikel löschen",
|
||||||
message=f"Möchtest du den Artikel {item.name} wirklich unwiderruflich 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()
|
popup.open()
|
||||||
|
|
||||||
@@ -1946,7 +1971,6 @@ class KuehlschrankApp(App):
|
|||||||
return sm
|
return sm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class KivyUI:
|
class KivyUI:
|
||||||
def __init__(self, tracker):
|
def __init__(self, tracker):
|
||||||
self.tracker = tracker
|
self.tracker = tracker
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
def format_currency(amount: float, symbol: str = "€", is_german: bool = True) -> str:
|
||||||
|
"""Formats a numeric amount to a currency string representation."""
|
||||||
|
if is_german:
|
||||||
|
return f"{amount:.2f}".replace(".", ",") + f" {symbol}"
|
||||||
|
else:
|
||||||
|
return f"{symbol}{amount:.2f}"
|
||||||
|
|
||||||
|
def format_date(iso_timestamp: str) -> str:
|
||||||
|
"""Converts ISO timestamp to German date format DD.MM.YYYY."""
|
||||||
|
try:
|
||||||
|
date_part = iso_timestamp.split("T")[0]
|
||||||
|
parts = date_part.split("-")
|
||||||
|
if len(parts) == 3 and all(p.isdigit() for p in parts) and len(parts[0]) == 4:
|
||||||
|
return f"{parts[2]}.{parts[1]}.{parts[0]}"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return iso_timestamp[:10]
|
||||||
|
|
||||||
|
|
||||||
|
def validate_pin(pin: str) -> bool:
|
||||||
|
"""Validates that a PIN consists of at least 4 numeric digits."""
|
||||||
|
return bool(re.match(r"^\d{4,}$", pin))
|
||||||
|
|
||||||
|
def parse_price(price_str: str) -> float:
|
||||||
|
"""Parses a string containing a price (handling comma and dot) to a float.
|
||||||
|
Raises ValueError if invalid.
|
||||||
|
"""
|
||||||
|
clean_str = price_str.replace(",", ".").strip()
|
||||||
|
price = float(clean_str)
|
||||||
|
if price < 0:
|
||||||
|
raise ValueError("Price cannot be negative")
|
||||||
|
return price
|
||||||
Reference in New Issue
Block a user