1038 lines
39 KiB
Python
1038 lines
39 KiB
Python
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()
|