Files
KPK/accountselect.py
2026-06-23 15:20:56 +02:00

1038 lines
39 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.metrics import dp
from kivy.logger import Logger
from dataclasses import dataclass
from kivy.uix.screenmanager import ScreenManager
from kivy.clock import Clock
from kivy.effects.scroll import ScrollEffect
from kivy.uix.popup import Popup
from kivy.uix.modalview import ModalView
from kivy.uix.screenmanager import Screen
from kivy.core.window import Window
import random
import numberpad
import data
import api_call
from closed_accountselect import ClosedAccountSelectScreen
from accountselect_base import BaseAccountSelectScreen
from ui_utils import ucet_ui_type, UCET_COLORS
from konstanty import *
# =====================================================
# DUMMY DATA simulace data.UcetSelect
# =====================================================
def make_dummy_ucty(n=40) -> list[data.UcetSelect]:
origins = ["Normal", "Zmena_Platby", "Storno"]
out = []
for i in range(n):
out.append(
data.UcetSelect(
ucislo=str(1000 + i),
id_kas="01",
stul=str(i + 1),
blocked_by="" if i % 7 else "02",
closed=False,
autor="TEST",
is_storno=None if i % 11 else "1234",
storno=None if i % 9 else "1222",
origin=random.choice(origins),
closed_at=None,
)
)
return out
def get_room_name(room):
return getattr(room, "room_name", None) or getattr(room, "name", "Room")
class FiscalCashOperationDialog(ModalView):
def __init__(self, *, operation, payments, printers, on_done, controller=None, **kwargs):
super().__init__(size_hint=(None, None), size=(dp(760), dp(520)), auto_dismiss=False, **kwargs)
self.operation = operation
self.payments = list(payments or [])
self.printers = list(printers or [])
self.on_done = on_done
self.controller = controller
self.selected_payment = self.payments[0] if self.payments else None
self.selected_printer = self.printers[0] if self.printers else None
self.amount_text = ""
self.payment_buttons = []
self.printer_buttons = []
root = BoxLayout(orientation="horizontal", spacing=dp(16), padding=dp(16))
left = BoxLayout(orientation="vertical", spacing=dp(10), size_hint=(1, 1))
right = BoxLayout(orientation="vertical", spacing=dp(8), size_hint=(None, 1), width=dp(230))
title = "Vklad" if operation == "manual_deposit" else "Výber"
left.add_widget(Label(text=title, font_size=dp(24), bold=True, size_hint_y=None, height=dp(42), halign="left"))
left.add_widget(Label(text="Spôsob platby:", bold=True, size_hint_y=None, height=dp(28), halign="left"))
pay_scroll = ScrollView(size_hint=(1, 1), do_scroll_y=True, bar_width=dp(14), scroll_type=["bars", "content"])
self.pay_grid = GridLayout(cols=2, spacing=dp(8), size_hint_y=None)
self.pay_grid.bind(minimum_height=self.pay_grid.setter("height"))
for payment in self.payments:
btn = Button(
text=str(getattr(payment, "name", "") or getattr(payment, "code", "")),
size_hint_y=None,
height=dp(52),
halign="left",
valign="middle",
)
btn.bind(size=lambda inst, *_: setattr(inst, "text_size", (inst.width - dp(16), None)))
btn.bind(on_press=lambda _, p=payment: self._select_payment(p))
self.payment_buttons.append((btn, payment))
self.pay_grid.add_widget(btn)
pay_scroll.add_widget(self.pay_grid)
left.add_widget(pay_scroll)
left.add_widget(Label(text="Výber tlačiarne:", bold=True, size_hint_y=None, height=dp(28), halign="left"))
self.printer_button = Button(
text="",
size_hint_y=None,
height=dp(54),
halign="left",
valign="middle",
)
self.printer_button.bind(size=lambda inst, *_: setattr(inst, "text_size", (inst.width - dp(18), None)))
self.printer_button.bind(on_press=lambda *_: self._open_printer_select())
left.add_widget(self.printer_button)
self.amount_label = Label(
text="0",
font_size=dp(28),
bold=True,
halign="right",
valign="middle",
size_hint_y=None,
height=dp(58),
)
self.amount_label.bind(size=lambda inst, *_: setattr(inst, "text_size", (inst.width - dp(14), inst.height)))
right.add_widget(self.amount_label)
keypad = GridLayout(cols=3, spacing=dp(1), size_hint=(1, 1))
for key in ("7", "8", "9", "4", "5", "6", "1", "2", "3", "0", ",", "OK"):
btn = Button(text="OK" if key == "OK" else key, font_size=dp(22))
if key == "OK":
btn.background_color = (0.08, 0.62, 0.58, 1)
btn.bind(on_press=lambda _, k=key: self._press(k))
keypad.add_widget(btn)
right.add_widget(keypad)
btn_cancel = Button(text="Zrušiť", size_hint_y=None, height=dp(52), background_color=(0.55, 0.18, 0.18, 1))
btn_cancel.bind(on_press=lambda *_: self.dismiss())
right.add_widget(btn_cancel)
root.add_widget(left)
root.add_widget(right)
self.add_widget(root)
self._refresh_selected()
self._refresh_amount()
def on_open(self):
Window.bind(on_key_down=self._on_key_down)
def on_dismiss(self):
Window.unbind(on_key_down=self._on_key_down)
def _select_payment(self, payment):
self.selected_payment = payment
self._refresh_selected()
self._refresh_amount()
def _select_printer(self, printer):
self.selected_printer = printer
self._refresh_selected()
def _printer_text(self, printer):
if not printer:
return "Nie je vybrana fiskalna tlaciaren"
return f"{getattr(printer, 'prn_no', '')} - {getattr(printer, 'prn_name', '')}".strip(" -")
def _open_printer_select(self):
modal = ModalView(size_hint=(None, None), size=(dp(460), dp(460)), auto_dismiss=False)
box = BoxLayout(orientation="vertical", spacing=dp(8), padding=dp(12))
box.add_widget(Label(text="Vyber tlaciarne", bold=True, size_hint_y=None, height=dp(38)))
scroll = ScrollView(do_scroll_y=True, bar_width=dp(14), scroll_type=["bars", "content"])
grid = GridLayout(cols=1, spacing=dp(6), size_hint_y=None)
grid.bind(minimum_height=grid.setter("height"))
for printer in self.printers:
btn = Button(
text=self._printer_text(printer),
size_hint_y=None,
height=dp(52),
halign="left",
valign="middle",
background_color=(0.08, 0.62, 0.58, 1) if printer is self.selected_printer else (0.36, 0.36, 0.36, 1),
)
btn.bind(size=lambda inst, *_: setattr(inst, "text_size", (inst.width - dp(16), None)))
def choose(_, p=printer):
self._select_printer(p)
modal.dismiss()
btn.bind(on_press=choose)
grid.add_widget(btn)
if not self.printers:
grid.add_widget(Label(text="Nie je dostupna fiskalna tlaciaren.", size_hint_y=None, height=dp(52)))
scroll.add_widget(grid)
box.add_widget(scroll)
close_btn = Button(text="Zrusit", size_hint_y=None, height=dp(52), background_color=(0.55, 0.18, 0.18, 1))
close_btn.bind(on_press=lambda *_: modal.dismiss())
box.add_widget(close_btn)
modal.add_widget(box)
modal.open()
def _refresh_selected(self):
for btn, payment in self.payment_buttons:
btn.background_color = (0.08, 0.62, 0.58, 1) if payment is self.selected_payment else (0.36, 0.36, 0.36, 1)
for btn, printer in self.printer_buttons:
btn.background_color = (0.08, 0.62, 0.58, 1) if printer is self.selected_printer else (0.36, 0.36, 0.36, 1)
if hasattr(self, "printer_button"):
self.printer_button.text = self._printer_text(self.selected_printer)
self.printer_button.background_color = (0.08, 0.62, 0.58, 1) if self.selected_printer else (0.55, 0.18, 0.18, 1)
def _unit(self):
return str(getattr(self.selected_payment, "unit", "") or "").strip()
def _refresh_amount(self):
amount = self.amount_text or "0"
self.amount_label.text = f"{amount} {self._unit()}".strip()
def _press(self, key):
if key == "OK":
self._accept()
return
if key in (",", "."):
if "," not in self.amount_text and "." not in self.amount_text:
self.amount_text = (self.amount_text or "0") + ","
elif key.isdigit():
if "," in self.amount_text:
decimals = self.amount_text.split(",", 1)[1]
if len(decimals) >= 2:
return
self.amount_text = key if self.amount_text == "0" else self.amount_text + key
self._refresh_amount()
def _backspace(self):
self.amount_text = self.amount_text[:-1]
self._refresh_amount()
def _amount_value(self):
try:
return round(float((self.amount_text or "0").replace(",", ".")), 2)
except Exception:
return 0.0
def _show_error(self, text):
if self.controller and hasattr(self.controller, "_popup_info"):
self.controller._popup_info("Vklad/Výber", text)
else:
Logger.warning(text)
def _accept(self):
amount = self._amount_value()
if not self.selected_payment:
self._show_error("Nie je vybraný spôsob platby.")
return
if not self.selected_printer:
self._show_error("Nie je vybraná fiskálna tlačiareň.")
return
if amount <= 0:
self._show_error("Zadaj sumu väčšiu ako nula.")
return
if self.on_done:
result = self.on_done(self.operation, amount, self.selected_payment, self.selected_printer)
if result is False:
return
self.dismiss()
def _on_key_down(self, window, keycode, scancode, codepoint, modifiers):
key = codepoint or (keycode[1] if isinstance(keycode, tuple) else keycode)
if key in ("escape", "ESC") or keycode == 27:
self.dismiss()
return True
if key in ("enter", "numenter", "ENTER") or keycode == 13:
self._accept()
return True
if key in ("backspace", "BACKSPACE") or keycode == 8:
self._backspace()
return True
if key in (",", ".") or str(key).isdigit():
self._press(str(key))
return True
return True
# =====================================================
# ACCOUNT SELECT SCREEN TEST
# =====================================================
class AccountSelectScreen(BaseAccountSelectScreen):
def __init__(
self,
*,
controller,
get_ucty,
on_select,
on_cancel=None,
on_logout=None,
on_bar=None,
on_info=None,
mode: str = "normal", # "normal" | "split"
only_opened: bool = False,
allow_manual: bool = True,
**kwargs,
):
super().__init__(**kwargs)
self.controller = controller
self.get_ucty = get_ucty
self.on_select = on_select
self.on_cancel = on_cancel
self.on_logout = on_logout
self.on_bar = on_bar
self.on_info = on_info
self.mode = mode
self.only_opened = only_opened
self.allow_manual = allow_manual
# režim horní části
self.view_mode = "open_table" # "open_table" | "room"
self.selected_room = None
if self.controller:
if self.controller.client_settings:
if self.controller.client_settings["room_name"]:
self.selected_room = self.controller.client_settings["room_name"]
self.view_mode="room"
# reference na horní widgety
self.scroll = None
self.grid = None
self.opened_view = None
self.map_view = None
self.btn_bar = None
self.btn_bar_default_color = None
self.btn_deposit = None
self.btn_withdrawal = None
self._bar_ucet = None
self._bar_has_items = False
root = BoxLayout(orientation="vertical", spacing=dp(10), padding=dp(10))
# =================================================
# HORNÍ ČÁST = DYNAMICKÝ KONTEJNER
# =================================================
self.top_holder = BoxLayout(size_hint=(1, 1))
root.add_widget(self.top_holder)
self._build_opened_view()
self._switch_top_view()
# =================================================
# SPODNÍ LIŠTA
# =================================================
bottom = BoxLayout(size_hint_y=None, height=dp(70), spacing=dp(10))
if self.allow_manual:
bottom.add_widget(Button(
text="Zadat stůl",
on_press=self._manual,
))
bottom.add_widget(Button(
text="Místnost",
on_press=self._open_room_menu,
))
if self.on_info:
bottom.add_widget(Button(
text="Menu",
on_press=self._open_main_menu,
))
if self.mode != "split":
btn_ops = Button(text="Účty")
btn_ops.bind(on_press=self._show_account_menu)
bottom.add_widget(btn_ops)
if self.mode == "split":
bottom.add_widget(Button(
text="Zrušiť presun",
background_color=(0.6, 0.2, 0.2, 1),
on_press=lambda *_: self._cancel(),
))
else:
self.btn_deposit = Button(
text="Vklad",
on_press=lambda *_: self._open_fiscal_cash_operation("manual_deposit"),
)
self.btn_withdrawal = Button(
text="Výber",
on_press=lambda *_: self._open_fiscal_cash_operation("manual_withdrawal"),
)
bottom.add_widget(self.btn_deposit)
bottom.add_widget(self.btn_withdrawal)
self.btn_bar = Button(
text="BAR",
on_press=lambda *_: self._open_bar(),
)
self.btn_bar_default_color = tuple(self.btn_bar.background_color)
bottom.add_widget(self.btn_bar)
bottom.add_widget(Button(
text="Odhlásit",
on_press=lambda *_: self.on_logout(),
))
bottom.add_widget(Button(
text="Konec",
on_press=lambda *_: App.get_running_app().stop(),
))
root.add_widget(bottom)
self.add_widget(root)
self._update_fiscal_operation_buttons()
def _truthy_setup_flag(self, value) -> bool:
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return value != 0
return str(value or "").strip().lower() in {"1", "true", "t", ".t.", "yes", "ano", "áno"}
def _is_fiskal_enabled(self) -> bool:
setup = getattr(self.controller, "setup", None) if self.controller else None
return self._truthy_setup_flag(getattr(setup, "is_fiskal", False))
def _update_fiscal_operation_buttons(self):
visible = self._is_fiskal_enabled()
for btn in (self.btn_deposit, self.btn_withdrawal):
if not btn:
continue
btn.disabled = not visible
btn.opacity = 1 if visible else 0
btn.size_hint_x = 1 if visible else None
btn.width = dp(100) if visible else 0
def _fiscal_payments(self):
source = list(getattr(self.controller, "_payments", []) or [])
if not source and self.controller and getattr(self.controller, "setup", None):
source = list(getattr(self.controller.setup, "platby", []) or [])
return [
payment for payment in source
if bool(getattr(payment, "fiscal", False))
]
def _fiscal_printers(self):
printers = list(getattr(self.controller, "_printers", []) or []) if self.controller else []
return [
printer for printer in printers
if str(getattr(printer, "cmd32_on", "") or "").strip().upper() == "FISKAL"
]
def _open_fiscal_cash_operation(self, operation):
if not self._is_fiskal_enabled():
return
payments = self._fiscal_payments()
printers = self._fiscal_printers()
if not payments:
self.controller._popup_info("Vklad/Výber", "Nie je dostupný žiadny fiskálny spôsob platby.")
return
if not printers:
self.controller._popup_info("Vklad/Výber", "Nie je dostupná žiadna fiskálna tlačiareň.")
return
FiscalCashOperationDialog(
operation=operation,
payments=payments,
printers=printers,
controller=self.controller,
on_done=self.controller.perform_fiscal_cash_operation,
).open()
# --- PROVIDER MAPY
def _get_room_provider(self):
if self.controller:
return self.controller.get_table_map_provider()
return None
# --- BUILD / SWITCH VIEW
def _build_opened_view(self):
self.scroll = ScrollView(
size_hint=(1, 1),
do_scroll_x=True,
do_scroll_y=True,
bar_width=dp(6),
)
self.scroll.effect_cls = ScrollEffect
self.grid = GridLayout(
cols=COLS,
spacing=SP,
padding=PAD,
size_hint=(None, None),
)
self.grid.bind(minimum_height=self.grid.setter("height"))
self.grid.width = COLS * BTN_W + (COLS - 1) * SP + 2 * PAD
self.scroll.add_widget(self.grid)
self.opened_view = self.scroll
def _build_map_view(self):
provider = self._get_room_provider()
if provider is None:
self.map_view = None
return
from mapa_stolu import RoomTableMapWidget
self.map_view = RoomTableMapWidget(
provider=provider,
on_select=self._select,
)
self.map_view.current_room = self.selected_room
def _switch_top_view(self):
self.top_holder.clear_widgets()
if self.view_mode == "open_table":
if self.opened_view is None:
self._build_opened_view()
self.top_holder.add_widget(self.opened_view)
return
if self.view_mode == "room":
if self.map_view is None:
self._build_map_view()
if self.map_view is not None:
self.map_view.current_room = self.selected_room
self.top_holder.add_widget(self.map_view)
# --- MENU MÍSTNOSTÍ
def get_room_name(room):
return getattr(room, "room_name", None) or getattr(room, "name", "Room")
def _open_room_menu(self, *_):
btn_h = dp(60)
spacing = dp(10)
padding = dp(12)
provider = self._get_room_provider()
rooms = []
if provider is not None:
try:
if hasattr(provider, "set_current_room"):
provider.set_current_room(self.selected_room if self.view_mode == "room" else None)
rooms = provider.get_rooms()
except Exception as e:
Logger.warning(f"AccountSelect: get_rooms failed: {e}")
# ===== ROOT (NEMÁ pevnou výšku!) =====
root = BoxLayout(
orientation="vertical",
spacing=spacing,
padding=padding,
)
modal = ModalView(
size_hint=(None, None),
size=(dp(420), dp(560)),
auto_dismiss=True,
)
# HORNÍ FIX OTEVŘENÉ STOLY
btn_opened = Button(
text="Otevřené stoly",
size_hint_y=None,
height=btn_h,
)
btn_opened.bind(on_press=lambda *_: self._select_room_mode(modal, None))
root.add_widget(btn_opened)
# SCROLL MÍSTNOSTI
scroll = ScrollView(
size_hint=(1, 1),
do_scroll_y=True,
do_scroll_x=False,
bar_width=dp(10),
)
room_list = BoxLayout(
orientation="vertical",
size_hint_y=None,
spacing=spacing,
)
room_list.bind(minimum_height=room_list.setter("height"))
for room in rooms:
name = get_room_name(room)
btn = Button(
text=name,
size_hint_y=None,
height=btn_h,
)
btn.bind(
on_press=lambda _, r=name: self._select_room_mode(modal, r)
)
room_list.add_widget(btn)
scroll.add_widget(room_list)
root.add_widget(scroll)
# SPODNÍ FIX EDITOR + ZPĚT
bottom = BoxLayout(
size_hint_y=None,
height=btn_h,
spacing=spacing,
)
btn_editor = Button(text="Editor")
btn_editor.bind(on_press=lambda *_: self._open_map_editor(modal))
btn_close = Button(text="Zavřít")
btn_close.bind(on_press=modal.dismiss)
bottom.add_widget(btn_editor)
bottom.add_widget(btn_close)
root.add_widget(bottom)
modal.add_widget(root)
modal.open()
def _select_room_mode(self, modal, room_name):
modal.dismiss()
if room_name is None:
self.view_mode = "open_table"
self.selected_room = None
else:
self.view_mode = "room"
self.selected_room = room_name
if self.controller.client_settings["room_name"] != self.selected_room :
self.controller.client_settings["room_name"] = self.selected_room
if self.selected_room:
self.controller.client_settings = api_call.save_clientsettings_API(self.controller.ctx, prn_no=self.controller.client_settings["prn_no"], room_name=self.controller.client_settings["room_name"])
else:
self.controller.client_settings = api_call.save_clientsettings_API(self.controller.ctx, prn_no=self.controller.client_settings["prn_no"], room_name="")
self.controller.default_room = self.selected_room
self._switch_top_view()
self.refresh()
# --- HLAVNE MENU
def _open_main_menu(self, *_):
btn_h = dp(56)
spacing = dp(9)
padding = dp(12)
items = [
("Správa do kuchyne", lambda modal: self._run_placeholder(modal, "Správa do kuchyne")),
("Uzávierka pokladne >", lambda modal: self._open_uzaverka_menu_from(modal)),
("Prezeranie spotreby", lambda modal: self._run_usage_report(modal)),
("Predaj alkoholu", lambda modal: self._run_placeholder(modal, "Predaj alkoholu")),
("Počet porcií", lambda modal: self._run_placeholder(modal, "Počet porcií")),
("Aktualizácia cenníka", lambda modal: self._run_placeholder(modal, "Aktualizácia cenníka")),
("O systéme", lambda modal: self._run_info(modal)),
("Zavrieť", lambda modal: modal.dismiss()),
]
content_h = len(items) * btn_h + (len(items) - 1) * spacing + 2 * padding
root = BoxLayout(
orientation="vertical",
spacing=spacing,
padding=padding,
size_hint_y=None,
height=content_h,
)
modal = ModalView(
size_hint=(None, None),
size=(dp(430), content_h),
auto_dismiss=True,
)
for text, callback in items:
btn = Button(text=text, size_hint_y=None, height=btn_h)
btn.bind(on_press=lambda _btn, cb=callback: cb(modal))
root.add_widget(btn)
modal.add_widget(root)
modal.open()
def _open_uzaverka_menu_from(self, parent_modal):
parent_modal.dismiss()
self._open_uzaverka_menu()
def _run_placeholder(self, modal, title: str):
modal.dismiss()
if self.controller:
self.controller._popup_info(title, "Funkcia zatiaľ nie je napojená.")
# --- UZÁVĚRKA MENU
def _open_uzaverka_menu(self, *_):
btn_h = dp(60)
spacing = dp(10)
padding = dp(12)
btn_count = 4
content_h = btn_count * btn_h + (btn_count - 1) * spacing + 2 * padding
root = BoxLayout(
orientation="vertical",
spacing=spacing,
padding=padding,
size_hint_y=None,
height=content_h,
)
modal = ModalView(
size_hint=(None, None),
size=(dp(420), content_h),
auto_dismiss=True,
)
btn_kopie = Button(
text="Kopie uzávěrky",
size_hint_y=None,
height=btn_h,
)
btn_daily = Button(
text="Uzávěrka",
size_hint_y=None,
height=btn_h,
)
btn_detail = Button(
text="Meziuzávěrka",
size_hint_y=None,
height=btn_h,
)
btn_close = Button(
text="Zavřít",
size_hint_y=None,
height=btn_h,
)
btn_kopie.bind(on_press=lambda *_: self._run_kopie(modal, "Kopie Uzávěrky"))
btn_daily.bind(on_press=lambda *_: self._run_uzaverka(modal, "Uzávěrka"))
btn_detail.bind(on_press=lambda *_: self._run_uzaverka(modal, "Meziuzávěrka"))
btn_close.bind(on_release=modal.dismiss)
root.add_widget(btn_kopie)
root.add_widget(btn_daily)
root.add_widget(btn_detail)
root.add_widget(btn_close)
modal.add_widget(root)
modal.open()
def _open_map_editor(self, modal):
modal.dismiss()
from mapa_stolu import MapaEditor
sm = self.manager # ScreenManager
# vytvoř editor screen
editor = MapaEditor(
controller=self.controller,
back_screen=self.name, # kam se vrátit
name="map_editor", # důležité!
)
# pokud už existuje → smaž (jinak se bude hromadit)
if sm.has_screen("map_editor"):
sm.remove_widget(sm.get_screen("map_editor"))
sm.add_widget(editor)
sm.current = "map_editor"
def _run_kopie(self, modal, operation):
modal.dismiss()
self.controller.handle_kopie_uzaverky()
return
def _run_uzaverka(self, modal, operation):
modal.dismiss()
if not self.controller:
return
self._open_closed_select(operation)
def _run_info(self, modal):
modal.dismiss()
if self.on_info:
self.on_info()
def _run_usage_report(self, modal):
modal.dismiss()
if self.controller and hasattr(self.controller, "handle_usage_report"):
self.controller.handle_usage_report()
elif self.controller:
self.controller._popup_info("Prezeranie spotreby", "Funkcia zatiaľ nie je napojená.")
# --- OTEVŘENÉ STOLY - BUTTON
def _bar_table_id(self):
if self.controller and hasattr(self.controller, "_bar_table_id"):
try:
return self.controller._bar_table_id()
except Exception as e:
Logger.warning(f"AccountSelect: bar table id failed: {e}")
return None
def _is_bar_ucet(self, u):
bar_stul = self._bar_table_id()
if not bar_stul:
return False
return str(getattr(u, "stul", "") or "").strip() == str(bar_stul).strip()
def _split_bar_ucty(self, ucty):
bar_stul = self._bar_table_id()
if not bar_stul:
self._bar_ucet = None
self._bar_has_items = False
self._update_bar_button()
return ucty
bar_stul = str(bar_stul).strip()
self._bar_ucet = next(
(u for u in ucty if str(getattr(u, "stul", "") or "").strip() == bar_stul),
None,
)
self._bar_has_items = self._resolve_bar_has_items(bar_stul)
self._update_bar_button()
return [u for u in ucty if str(getattr(u, "stul", "") or "").strip() != bar_stul]
def _resolve_bar_has_items(self, bar_stul):
if self._bar_ucet is None:
return False
try:
if abs(float(getattr(self._bar_ucet, "total_base_currency", 0) or 0)) > 0.0001:
return True
except Exception:
pass
if not self.controller or not hasattr(self.controller, "load_ucet_z_api"):
return False
try:
ucet = self.controller.load_ucet_z_api(bar_stul, block=False)
return bool(getattr(ucet, "poloz", None))
except Exception as e:
Logger.debug(f"AccountSelect: bar account detail refresh skipped: {e}")
return False
def _refresh_bar_status(self):
try:
ucty = self.get_ucty()
except Exception as e:
Logger.debug(f"AccountSelect: bar status refresh skipped: {e}")
return
if self.only_opened:
ucty = [u for u in ucty if not u.closed]
self._split_bar_ucty(ucty)
def _update_bar_button(self):
if not self.btn_bar:
return
color = self.btn_bar_default_color or (1, 1, 1, 1)
if self._bar_has_items:
color = UCET_COLORS.get("occupied", color)
self.btn_bar.background_color = color
def _create_ucet_button(self, u):
ui_type = ucet_ui_type(u)
color = UCET_COLORS.get(ui_type, (0.3, 0.3, 0.3, 1))
btn = Button(
text=self._format_ucet(u),
background_color=color,
halign="center",
valign="middle",
font_size=14,
size_hint=(None, None),
width=BTN_W,
height=BTN_H,
)
btn.text_size = (BTN_W, BTN_H)
btn.bind(on_press=lambda _, x=u.stul: self._select(x))
return btn
# --- MENU OPERACÍ PRO UZAVŘENÉ ÚČTY
def _show_account_menu(self, *_):
from closed_receipts import ClosedReceiptsScreen
sm = self.manager
if sm:
screen_name = "closed_receipts"
if sm.has_screen(screen_name):
sm.remove_widget(sm.get_screen(screen_name))
sm.add_widget(ClosedReceiptsScreen(
name=screen_name,
controller=self.controller,
back_screen=self.name,
))
sm.current = screen_name
return
BUTTON_H = dp(60)
SPACING = dp(8)
PADDING = dp(12)
W = dp(440)
items = [
("Zpět", None),
("Tisk kopie", "Tisk kopie"),
("Storno účtu", "Storno účtu"),
("Storno, vrácení na stůl", "Storno, vrácení na stůl"),
("Storno položek", "Storno položek"),
("Změna druhu platby", "Změna druhu platby"),
]
grid = GridLayout(
cols=1,
spacing=SPACING,
padding=PADDING,
size_hint_y=None,
)
modal = ModalView(
size_hint=(None, None),
size=(W, dp(10)),
auto_dismiss=True,
)
def select_op(op_text):
modal.dismiss()
if not op_text:
return
Logger.info(f"AccountSelect: selected closed-op={op_text}")
self._open_closed_select(op_text)
for text, op_text in items:
btn = Button(
text=text,
size_hint_y=None,
height=BUTTON_H,
)
btn.bind(on_press=lambda _, o=op_text: select_op(o))
grid.add_widget(btn)
content_h = len(items) * BUTTON_H + (len(items) - 1) * SPACING + 2 * PADDING
grid.height = content_h
modal.size = (W, min(content_h, dp(560)))
modal.add_widget(grid)
modal.open()
def _open_closed_select(self, operation_text: str):
from closed_accountselect import ClosedAccountSelectScreen
screen = ClosedAccountSelectScreen(
name="closed_select",
controller=self.controller,
operation=operation_text,
get_ucty=lambda: self.controller.load_closed_ucty(limit=500),
on_done=lambda op, ucet_id: self.controller.handle_closedaccountselect(op, ucet_id),
on_cancel=lambda: self._close_closed_select(),
)
sm = self.manager
if not sm:
return
if sm.has_screen("closed_select"):
sm.remove_widget(sm.get_screen("closed_select"))
sm.add_widget(screen)
sm.current = "closed_select"
def _apply_closed_operation(self, operation, stul):
if operation == "print_copy":
self.controller.print_copy(stul)
elif operation == "storno_ucet":
self.controller.storno_ucet(stul)
elif operation == "storno_polozek":
self.controller.open_storno_items(stul)
elif operation == "change_payment":
self.controller.change_payment_type(stul)
self._close_closed_select()
def _close_closed_select(self):
sm = self.manager
if not sm:
return
if sm.has_screen("closed_select"):
sm.remove_widget(sm.get_screen("closed_select"))
sm.current = self.name
def _print_copy(self):
self.controller.print_copy(self.selected_ucet.id)
def _storno_ucet(self):
self.controller.storno_ucet(self.selected_ucet.id)
def _open_storno_items(self):
self.controller.open_storno_items(self.selected_ucet.id)
def _change_payment_type(self):
self.controller.change_payment_type(self.selected_ucet.id)
# --- REFRESH
def _on_timer_refresh(self, dt):
Logger.warning("AccountSelect: TIMER TICK")
controller = self.controller
if not controller.allow_account_refresh():
return
try:
self.refresh()
except Exception as e:
Logger.debug(f"AccountSelect refresh skipped: {e}")
def _stop_refresh(self):
controller = self.controller
controller._allow_account_refresh = False
if getattr(self, "_refresh_ev", None):
self._refresh_ev.cancel()
self._refresh_ev = None
def on_leave(self):
Window.unbind(on_key_down=self._on_key_down)
if getattr(self, "_refresh_ev", None):
self._refresh_ev.cancel()
self._refresh_ev = None
def on_enter(self):
Window.unbind(on_key_down=self._on_key_down)
Window.bind(on_key_down=self._on_key_down)
self._update_fiscal_operation_buttons()
self.refresh()
Logger.info("AccountSelect: start auto refresh")
self.controller._allow_account_refresh = True
if not getattr(self, "_refresh_ev", None):
self._refresh_ev = Clock.schedule_interval(
self._on_timer_refresh, 5
)
def _normalize_key(self, keycode, text):
if text:
return text
if keycode == 27:
return "ESC"
if keycode == 13:
return "ENTER"
if keycode == 8:
return "BACKSPACE"
key = keycode[1] if isinstance(keycode, tuple) else keycode
return {
"enter": "ENTER",
"escape": "ESC",
"backspace": "BACKSPACE",
}.get(key, key)
def _on_key_down(self, window, keycode, scancode, codepoint, modifiers):
key = self._normalize_key(keycode, codepoint)
if self.controller and hasattr(self.controller, "dispatch_key"):
if self.controller.dispatch_key(key):
return True
if self.mode == "split" and key == "ESC":
self._cancel()
return True
return False
def refresh(self):
Logger.warning(f"AccountSelect: REFRESH CALLED mode={self.view_mode}")
self._update_fiscal_operation_buttons()
if self.view_mode == "open_table":
ucty = self.get_ucty()
if self.only_opened:
ucty = [u for u in ucty if not u.closed]
ucty = self._split_bar_ucty(ucty)
self._load_ucty(ucty)
return
if self.view_mode == "room":
self._refresh_bar_status()
if self.map_view is None:
self._build_map_view()
if self.map_view is not None:
self.map_view.current_room = self.selected_room
self.map_view.refresh()
return
def _format_ucet(self, u: data.UcetSelect) -> str:
lines = [f"STŮL {u.stul}"]
if u.blocked_by:
lines.append(f"Blocked by {u.blocked_by}")
else:
lines.append(ucet_ui_type(u).upper())
return "\n".join(lines)
# --- AKCE
def _select(self, stul: str):
Logger.info(f"ACCOUNT SELECT → stul={stul} mode={self.mode} view={self.view_mode}")
self.on_select(stul)
def _cancel(self):
if self.on_cancel:
self.on_cancel()
def _open_bar(self):
if self.on_bar:
self.on_bar()
else:
self._cancel()
def _manual(self, *_):
pad = numberpad.NumberPad(
mode="number",
max_len=5,
allow_fraction=False,
on_accept=self._manual_accept,
on_cancel=self._manual_cancel,
)
self._manual_popup = Popup(
title="Zadat číslo stolu",
content=pad,
size_hint=(None, None),
size=(dp(360), dp(420)),
auto_dismiss=False,
)
self._manual_popup.open()
def _manual_accept(self, value: str):
if getattr(self, "_manual_popup", None):
self._manual_popup.dismiss()
self._manual_popup = None
self.on_select(value)
def _manual_cancel(self):
if getattr(self, "_manual_popup", None):
self._manual_popup.dismiss()
self._manual_popup = None
# --- TEST APP ---------------------------
class AccountSelectTestApp(App):
def build(self):
sm = ScreenManager()
#ostry provoz
#screen = AccountSelectScreen(
# name="account",
# get_ucty=lambda: controller.load_stoly_API(closed=False),
# on_open_table=lambda stul: controller.open_table(stul),
# on_logout=self.controller.logout_user,
# )
#testovaci provoz
screen = AccountSelectScreen(
name="account",
get_ucty=lambda: make_dummy_ucty(80),
on_logout=lambda: Logger.info("LOGOUT (test)"),
on_cancel=lambda: Logger.info("CANCEL (test)"),
on_select=lambda stul: Logger.info("SELECT STUL {stul}"),
mode="normal",
only_opened=False,
allow_manual=True,
)
sm.add_widget(screen)
sm.current = "account"
return sm
if __name__ == "__main__":
AccountSelectTestApp().run()