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