648 lines
25 KiB
Python
648 lines
25 KiB
Python
import calendar
|
|
from datetime import datetime, date
|
|
|
|
from kivy.clock import Clock
|
|
from kivy.metrics import dp
|
|
from kivy.uix.boxlayout import BoxLayout
|
|
from kivy.uix.button import Button
|
|
from kivy.uix.checkbox import CheckBox
|
|
from kivy.uix.gridlayout import GridLayout
|
|
from kivy.uix.label import Label
|
|
from kivy.uix.popup import Popup
|
|
from kivy.uix.screenmanager import Screen
|
|
from kivy.uix.scrollview import ScrollView
|
|
from kivy.uix.textinput import TextInput
|
|
from kivy.logger import Logger
|
|
from kivy.core.window import Window
|
|
|
|
import data
|
|
from numberpad import NumberPad
|
|
|
|
|
|
class ClosedReceiptsScreen(Screen):
|
|
def __init__(self, *, controller, back_screen="account_select", **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.controller = controller
|
|
self.back_screen = back_screen
|
|
self.receipts: list[data.UcetSelect] = []
|
|
self.filtered: list[data.UcetSelect] = []
|
|
self.selected_summary: data.UcetSelect | None = None
|
|
self.selected_ucet: data.Ucet | None = None
|
|
self.selected_quantities: dict[str, float] = {}
|
|
self.current_only = True
|
|
self._keyboard_bound = False
|
|
|
|
root = BoxLayout(orientation="vertical", spacing=dp(6), padding=dp(8))
|
|
|
|
top = BoxLayout(size_hint_y=None, height=dp(48), spacing=dp(6))
|
|
btn_back = Button(text="Zavriet", size_hint_x=None, width=dp(120))
|
|
btn_back.bind(on_press=lambda *_: self._go_back())
|
|
self.search = TextInput(
|
|
hint_text="Hladat ucet, stol, platbu, casnika...",
|
|
multiline=False,
|
|
font_size=dp(16),
|
|
padding=(dp(10), dp(10), dp(10), dp(8)),
|
|
)
|
|
self.search.bind(text=lambda *_: self._apply_filters())
|
|
self.date_filter = TextInput(
|
|
hint_text="Den",
|
|
multiline=False,
|
|
size_hint_x=None,
|
|
width=dp(120),
|
|
readonly=True,
|
|
font_size=dp(16),
|
|
padding=(dp(10), dp(10), dp(10), dp(8)),
|
|
)
|
|
self.date_filter.bind(text=lambda *_: self._apply_filters())
|
|
btn_date = Button(text="...", size_hint_x=None, width=dp(48))
|
|
btn_date.bind(on_press=lambda *_: self._open_calendar())
|
|
btn_clear_date = Button(text="X", size_hint_x=None, width=dp(42))
|
|
btn_clear_date.bind(on_press=lambda *_: setattr(self.date_filter, "text", ""))
|
|
current_wrap = BoxLayout(size_hint_x=None, width=dp(170), spacing=dp(4))
|
|
self.chk_current = CheckBox(active=True, size_hint_x=None, width=dp(36))
|
|
self.chk_current.bind(active=lambda _w, active: self._set_current_only(active))
|
|
current_wrap.add_widget(self.chk_current)
|
|
current_wrap.add_widget(Label(text="Aktualne ucty", halign="left"))
|
|
btn_refresh = Button(text="Obnovit", size_hint_x=None, width=dp(120))
|
|
btn_refresh.bind(on_press=lambda *_: self.refresh())
|
|
top.add_widget(btn_back)
|
|
top.add_widget(self.search)
|
|
top.add_widget(self.date_filter)
|
|
top.add_widget(btn_date)
|
|
top.add_widget(btn_clear_date)
|
|
top.add_widget(current_wrap)
|
|
top.add_widget(btn_refresh)
|
|
root.add_widget(top)
|
|
|
|
body = BoxLayout(spacing=dp(8))
|
|
left = BoxLayout(orientation="vertical", size_hint_x=0.62, spacing=dp(4))
|
|
header = GridLayout(cols=7, size_hint_y=None, height=dp(34), spacing=dp(2))
|
|
for title in ("Stav", "Ucet", "Datum", "Stol", "Platby", "Suma", "Casnik"):
|
|
header.add_widget(Label(text=f"[b]{title}[/b]", markup=True, halign="left"))
|
|
left.add_widget(header)
|
|
self.list_grid = GridLayout(cols=1, spacing=dp(2), size_hint_y=None)
|
|
self.list_grid.bind(minimum_height=self.list_grid.setter("height"))
|
|
list_scroll = ScrollView(do_scroll_x=False, do_scroll_y=True, bar_width=dp(14))
|
|
list_scroll.add_widget(self.list_grid)
|
|
left.add_widget(list_scroll)
|
|
body.add_widget(left)
|
|
|
|
right = BoxLayout(orientation="vertical", size_hint_x=0.38, spacing=dp(6))
|
|
self.detail_title = Label(
|
|
text="Vyber ucet",
|
|
size_hint_y=None,
|
|
height=dp(36),
|
|
font_size=dp(20),
|
|
bold=True,
|
|
)
|
|
self.detail_subtitle = Label(text="", size_hint_y=None, height=dp(28))
|
|
right.add_widget(self.detail_title)
|
|
right.add_widget(self.detail_subtitle)
|
|
self.detail_grid = GridLayout(cols=1, spacing=dp(2), size_hint_y=None)
|
|
self.detail_grid.bind(minimum_height=self.detail_grid.setter("height"))
|
|
detail_scroll = ScrollView(do_scroll_x=False, do_scroll_y=True, bar_width=dp(14))
|
|
detail_scroll.add_widget(self.detail_grid)
|
|
right.add_widget(detail_scroll)
|
|
|
|
actions = GridLayout(cols=2, spacing=dp(6), size_hint_y=None, height=dp(148))
|
|
self.btn_storno = self._action_button("Stornovat ucet", self._storno)
|
|
self.btn_return_table = self._action_button("Storno a presun na stol", self._return_to_table)
|
|
self.btn_copy = self._action_button("Tlac kopie", self._print_copy)
|
|
self.btn_change_pay = self._action_button("Zmenit platby", self._change_payment)
|
|
self.btn_tip = self._action_button("Zadanie TIPu", self._edit_tip)
|
|
self.btn_select_all = self._action_button("Vybrat vsetko", self._select_all_items)
|
|
for btn in (
|
|
self.btn_storno,
|
|
self.btn_return_table,
|
|
self.btn_copy,
|
|
self.btn_change_pay,
|
|
self.btn_tip,
|
|
self.btn_select_all,
|
|
):
|
|
actions.add_widget(btn)
|
|
right.add_widget(actions)
|
|
body.add_widget(right)
|
|
|
|
root.add_widget(body)
|
|
self.add_widget(root)
|
|
self._refresh_actions()
|
|
|
|
def on_enter(self):
|
|
self._bind_keyboard()
|
|
self.refresh()
|
|
|
|
def on_leave(self):
|
|
self._unbind_keyboard()
|
|
|
|
def _bind_keyboard(self):
|
|
if self._keyboard_bound:
|
|
return
|
|
Window.unbind(on_key_down=self._on_key_down)
|
|
Window.bind(on_key_down=self._on_key_down)
|
|
self._keyboard_bound = True
|
|
|
|
def _unbind_keyboard(self):
|
|
Window.unbind(on_key_down=self._on_key_down)
|
|
self._keyboard_bound = False
|
|
|
|
def _normalize_key(self, keycode, text):
|
|
if text:
|
|
return text
|
|
if keycode == 27:
|
|
return "ESC"
|
|
key = keycode[1] if isinstance(keycode, tuple) else keycode
|
|
return {
|
|
"escape": "ESC",
|
|
"esc": "ESC",
|
|
}.get(key, key)
|
|
|
|
def _on_key_down(self, window, keycode, scancode, codepoint, modifiers):
|
|
key = self._normalize_key(keycode, codepoint)
|
|
if key == "ESC":
|
|
self._go_back()
|
|
return True
|
|
return False
|
|
|
|
def _action_button(self, text, callback):
|
|
btn = Button(text=text)
|
|
btn.bind(on_press=lambda *_: callback())
|
|
return btn
|
|
|
|
def _set_current_only(self, active: bool):
|
|
self.current_only = bool(active)
|
|
self.refresh()
|
|
|
|
def _go_back(self):
|
|
if self.manager:
|
|
self.manager.current = self.back_screen
|
|
|
|
def refresh(self):
|
|
try:
|
|
self.receipts = self.controller.load_closed_ucty(
|
|
limit=2000,
|
|
onlynonclsrep=self.current_only,
|
|
)
|
|
except Exception as exc:
|
|
Logger.exception("ClosedReceipts: load failed")
|
|
self.controller._popup_info("Ucty", f"Ucty sa nepodarilo nacitat:\n{exc}")
|
|
self.receipts = []
|
|
self.selected_summary = None
|
|
self.selected_ucet = None
|
|
self.selected_quantities = {}
|
|
self._apply_filters()
|
|
self._render_detail()
|
|
|
|
def _apply_filters(self):
|
|
text = (self.search.text or "").strip().lower()
|
|
date_text = (self.date_filter.text or "").strip()
|
|
result = []
|
|
for row in self.receipts:
|
|
if not self.current_only and not getattr(row, "c_uzaverka", None):
|
|
continue
|
|
if self.current_only and getattr(row, "c_uzaverka", None):
|
|
continue
|
|
if date_text and self._date_key(getattr(row, "closed_at", "")) != self._normalize_date(date_text):
|
|
continue
|
|
if text and text not in self._summary_search_text(row):
|
|
continue
|
|
result.append(row)
|
|
self.filtered = result
|
|
self._render_list()
|
|
|
|
def _summary_search_text(self, row) -> str:
|
|
fields = [
|
|
getattr(row, "ucislo", ""),
|
|
getattr(row, "closed_at", ""),
|
|
getattr(row, "stul", ""),
|
|
getattr(row, "payments_text", ""),
|
|
getattr(row, "status_text", ""),
|
|
getattr(row, "autor", ""),
|
|
getattr(row, "origin", ""),
|
|
f"{getattr(row, 'total_base_currency', 0) or 0:.2f}",
|
|
]
|
|
return " ".join(str(x or "") for x in fields).lower()
|
|
|
|
def _normalize_date(self, value: str) -> str:
|
|
value = (value or "").strip()
|
|
if not value:
|
|
return ""
|
|
for fmt in ("%d.%m.%Y", "%d.%m.%y", "%Y-%m-%d", "%y%m%d"):
|
|
try:
|
|
return datetime.strptime(value, fmt).strftime("%Y-%m-%d")
|
|
except Exception:
|
|
pass
|
|
return value
|
|
|
|
def _date_key(self, value: str) -> str:
|
|
text = str(value or "").strip()
|
|
if not text:
|
|
return ""
|
|
candidates = [
|
|
text,
|
|
text[:19],
|
|
text[:16],
|
|
text[:15],
|
|
text[:13],
|
|
text[:10],
|
|
text[:8],
|
|
text[:6],
|
|
]
|
|
formats = (
|
|
"%y%m%d %H:%M:%S",
|
|
"%y%m%d %H:%M",
|
|
"%Y-%m-%d %H:%M:%S",
|
|
"%Y-%m-%d %H:%M",
|
|
"%d.%m.%Y %H:%M:%S",
|
|
"%d.%m.%Y %H:%M",
|
|
"%Y-%m-%d",
|
|
"%d.%m.%Y",
|
|
"%y%m%d",
|
|
)
|
|
for candidate in candidates:
|
|
for fmt in formats:
|
|
try:
|
|
return datetime.strptime(candidate, fmt).strftime("%Y-%m-%d")
|
|
except Exception:
|
|
pass
|
|
if len(text) >= 6 and text[:6].isdigit():
|
|
try:
|
|
return datetime.strptime(text[:6], "%y%m%d").strftime("%Y-%m-%d")
|
|
except Exception:
|
|
pass
|
|
return text[:10]
|
|
|
|
def _open_calendar(self):
|
|
selected = self._parse_date_filter() or date.today()
|
|
month = date(selected.year, selected.month, 1)
|
|
popup = Popup(title="Vyber datumu", size_hint=(None, None), size=(dp(430), dp(430)))
|
|
root = BoxLayout(orientation="vertical", spacing=dp(6), padding=dp(8))
|
|
|
|
def render(target_month):
|
|
root.clear_widgets()
|
|
top = BoxLayout(size_hint_y=None, height=dp(46), spacing=dp(6))
|
|
btn_prev = Button(text="<", size_hint_x=None, width=dp(54))
|
|
title = Label(text=target_month.strftime("%m/%Y"), bold=True)
|
|
btn_next = Button(text=">", size_hint_x=None, width=dp(54))
|
|
top.add_widget(btn_prev)
|
|
top.add_widget(title)
|
|
top.add_widget(btn_next)
|
|
root.add_widget(top)
|
|
|
|
days = GridLayout(cols=7, spacing=dp(3), size_hint_y=None, height=dp(34))
|
|
for name in ("Po", "Ut", "St", "Stv", "Pi", "So", "Ne"):
|
|
days.add_widget(Label(text=name, bold=True))
|
|
root.add_widget(days)
|
|
|
|
grid = GridLayout(cols=7, spacing=dp(3))
|
|
cal = calendar.Calendar(firstweekday=0)
|
|
for week in cal.monthdayscalendar(target_month.year, target_month.month):
|
|
for day in week:
|
|
if not day:
|
|
grid.add_widget(Label(text=""))
|
|
continue
|
|
current = date(target_month.year, target_month.month, day)
|
|
btn = Button(text=str(day))
|
|
if current == selected:
|
|
btn.background_color = (0.20, 0.55, 0.75, 1)
|
|
btn.bind(on_press=lambda _btn, d=current: self._set_calendar_date(popup, d))
|
|
grid.add_widget(btn)
|
|
root.add_widget(grid)
|
|
|
|
bottom = BoxLayout(size_hint_y=None, height=dp(46), spacing=dp(6))
|
|
btn_today = Button(text="Dnes")
|
|
btn_clear = Button(text="Bez datumu")
|
|
btn_cancel = Button(text="Zrusit")
|
|
btn_today.bind(on_press=lambda *_: self._set_calendar_date(popup, date.today()))
|
|
btn_clear.bind(on_press=lambda *_: self._clear_calendar_date(popup))
|
|
btn_cancel.bind(on_press=lambda *_: popup.dismiss())
|
|
bottom.add_widget(btn_today)
|
|
bottom.add_widget(btn_clear)
|
|
bottom.add_widget(btn_cancel)
|
|
root.add_widget(bottom)
|
|
|
|
prev_month = date(target_month.year - (1 if target_month.month == 1 else 0), 12 if target_month.month == 1 else target_month.month - 1, 1)
|
|
next_month = date(target_month.year + (1 if target_month.month == 12 else 0), 1 if target_month.month == 12 else target_month.month + 1, 1)
|
|
btn_prev.bind(on_press=lambda *_: render(prev_month))
|
|
btn_next.bind(on_press=lambda *_: render(next_month))
|
|
|
|
popup.content = root
|
|
render(month)
|
|
popup.open()
|
|
|
|
def _parse_date_filter(self):
|
|
text = (self.date_filter.text or "").strip()
|
|
if not text:
|
|
return None
|
|
key = self._normalize_date(text)
|
|
try:
|
|
return datetime.strptime(key, "%Y-%m-%d").date()
|
|
except Exception:
|
|
return None
|
|
|
|
def _set_calendar_date(self, popup, value: date):
|
|
self.date_filter.text = value.strftime("%d.%m.%Y")
|
|
popup.dismiss()
|
|
|
|
def _clear_calendar_date(self, popup):
|
|
self.date_filter.text = ""
|
|
popup.dismiss()
|
|
|
|
def _render_list(self):
|
|
self.list_grid.clear_widgets()
|
|
for row in self.filtered:
|
|
btn = Button(
|
|
text=self._row_text(row),
|
|
size_hint_y=None,
|
|
height=dp(54),
|
|
halign="left",
|
|
valign="middle",
|
|
)
|
|
btn.text_size = (dp(1100), None)
|
|
if self.selected_summary and getattr(row, "ucislo", "") == getattr(self.selected_summary, "ucislo", ""):
|
|
btn.background_color = (0.20, 0.55, 0.75, 1)
|
|
else:
|
|
status = str(getattr(row, "status_text", "") or "").upper()
|
|
if status == "STORNO":
|
|
btn.background_color = (0.52, 0.18, 0.18, 1)
|
|
elif status == "STORNOVANY":
|
|
btn.background_color = (0.36, 0.20, 0.20, 1)
|
|
elif status in ("CIAST. STORNO", "VYSTORNOVANY"):
|
|
btn.background_color = (0.55, 0.42, 0.18, 1)
|
|
btn.bind(on_press=lambda _btn, item=row: self._select_receipt(item))
|
|
self.list_grid.add_widget(btn)
|
|
|
|
def _row_text(self, row) -> str:
|
|
return (
|
|
f"{getattr(row, 'status_text', '') or 'UCET'} "
|
|
f"# {getattr(row, 'ucislo', '')} "
|
|
f"{getattr(row, 'closed_at', '')} "
|
|
f"stol {getattr(row, 'stul', '') or '-'} "
|
|
f"{getattr(row, 'payments_text', '') or '-'} "
|
|
f"{self._money(getattr(row, 'total_base_currency', 0))} "
|
|
f"{getattr(row, 'autor', '') or '-'}"
|
|
)
|
|
|
|
def _money(self, value) -> str:
|
|
return f"{float(value or 0):.2f} {self.controller._currency()}"
|
|
|
|
def _select_receipt(self, row):
|
|
self.selected_summary = row
|
|
self.selected_quantities = {}
|
|
try:
|
|
self.selected_ucet = self.controller.load_closed_ucet_detail(row.ucislo)
|
|
except Exception as exc:
|
|
Logger.exception("ClosedReceipts: detail failed")
|
|
self.controller._popup_info("Ucty", f"Ucet {row.ucislo} sa nepodarilo nacitat:\n{exc}")
|
|
self.selected_ucet = None
|
|
self._render_list()
|
|
self._render_detail()
|
|
|
|
def _render_detail(self):
|
|
self.detail_grid.clear_widgets()
|
|
ucet = self.selected_ucet
|
|
if not ucet:
|
|
self.detail_title.text = "Vyber ucet"
|
|
self.detail_subtitle.text = ""
|
|
self._refresh_actions()
|
|
return
|
|
self.detail_title.text = f"Ucet {ucet.ucislo or ''}"
|
|
self.detail_subtitle.text = f"Stol {ucet.stul or '-'} | {ucet.closed_at or ''} | {self._money(ucet.total_czk())}"
|
|
for pol in getattr(ucet, "poloz", []) or []:
|
|
self.detail_grid.add_widget(self._item_row(pol))
|
|
self._refresh_actions()
|
|
|
|
def _item_row(self, pol):
|
|
line_id = self._line_id(pol)
|
|
available = self._storno_available_units(pol)
|
|
selected = self.selected_quantities.get(line_id, 0.0)
|
|
partial = self._is_partially_storned(pol)
|
|
row = BoxLayout(
|
|
orientation="horizontal",
|
|
spacing=dp(4),
|
|
size_hint_y=None,
|
|
height=dp(78),
|
|
)
|
|
bg = (0.30, 0.30, 0.30, 1)
|
|
if available <= 0:
|
|
bg = (0.35, 0.35, 0.35, 1)
|
|
elif partial:
|
|
bg = (0.55, 0.42, 0.18, 1)
|
|
elif selected:
|
|
bg = (0.20, 0.55, 0.75, 1)
|
|
qty_btn = Button(
|
|
text=self._left_qty_text(pol),
|
|
markup=True,
|
|
size_hint=(None, None),
|
|
width=dp(74),
|
|
height=dp(78),
|
|
background_color=bg,
|
|
background_normal="",
|
|
background_down="",
|
|
halign="center",
|
|
valign="top",
|
|
disabled=available <= 0,
|
|
)
|
|
qty_btn.bind(on_press=lambda *_: self._select_quantity_with_numberpad(pol))
|
|
qty_btn.bind(size=lambda inst, *_: setattr(inst, "text_size", inst.size))
|
|
name_btn = Button(
|
|
text=self._item_text(pol),
|
|
markup=True,
|
|
size_hint=(1, None),
|
|
height=dp(78),
|
|
background_color=bg,
|
|
background_normal="",
|
|
background_down="",
|
|
halign="left",
|
|
valign="top",
|
|
disabled=available <= 0,
|
|
)
|
|
name_btn.bind(on_press=lambda *_: self._toggle_all_units(pol))
|
|
name_btn.bind(size=lambda inst, *_: setattr(inst, "text_size", (inst.width - dp(8), None)))
|
|
row.add_widget(qty_btn)
|
|
row.add_widget(name_btn)
|
|
return row
|
|
|
|
def _item_text(self, pol) -> str:
|
|
units = abs(float(getattr(pol, "pocet", 0) or 0))
|
|
qty = units / max(int(getattr(pol, "delitel", 1) or 1), 1)
|
|
total = qty * float(getattr(pol, "cena", 0) or 0)
|
|
available = self._storno_available_units(pol)
|
|
return (
|
|
f"{getattr(pol, 'nazev', '')}\n"
|
|
f"Pocet: {qty:g} Stornovat: {self._qty_text(available, pol)} "
|
|
f"{float(getattr(pol, 'cena', 0) or 0):.2f} = {total:.2f}"
|
|
)
|
|
|
|
def _line_id(self, pol) -> str:
|
|
return str(getattr(pol, "line_id", "") or "")
|
|
|
|
def _storno_available_units(self, pol) -> float:
|
|
units = abs(float(getattr(pol, "pocet", 0) or 0))
|
|
raw = getattr(pol, "kstornu", None)
|
|
if raw is None:
|
|
return units
|
|
try:
|
|
return max(min(float(raw or 0), units), 0.0)
|
|
except Exception:
|
|
return units
|
|
|
|
def _qty_text(self, units, pol) -> str:
|
|
units = float(units or 0)
|
|
den = max(int(getattr(pol, "delitel", 1) or 1), 1)
|
|
if den != 1:
|
|
num = int(units) if float(units).is_integer() else units
|
|
return f"{num:g}/{den}"
|
|
return f"{int(units) if float(units).is_integer() else units:g}"
|
|
|
|
def _left_qty_text(self, pol) -> str:
|
|
line_id = self._line_id(pol)
|
|
available = self._storno_available_units(pol)
|
|
selected = self.selected_quantities.get(line_id, 0.0)
|
|
available_txt = self._qty_text(available, pol)
|
|
if selected and selected < available:
|
|
return f"{self._qty_text(selected, pol)}\n/{available_txt}"
|
|
return available_txt
|
|
|
|
def _select_quantity_with_numberpad(self, pol):
|
|
available = self._storno_available_units(pol)
|
|
if available <= 0:
|
|
return
|
|
current = self.selected_quantities.get(self._line_id(pol), 0.0)
|
|
initial = str(int(current or available))
|
|
|
|
def accept(value: str):
|
|
try:
|
|
qty = int(str(value or "0").strip())
|
|
except Exception:
|
|
return
|
|
self._set_selected_units(pol, min(max(qty, 0), available))
|
|
|
|
NumberPad(
|
|
mode="number",
|
|
allow_fraction=False,
|
|
show_dot=False,
|
|
decimal_places=0,
|
|
max_len=4,
|
|
initial_value=initial,
|
|
on_accept=accept,
|
|
).open()
|
|
|
|
def _toggle_all_units(self, pol):
|
|
line_id = self._line_id(pol)
|
|
available = self._storno_available_units(pol)
|
|
if not line_id or available <= 0:
|
|
return
|
|
selected = self.selected_quantities.get(line_id, 0.0)
|
|
if selected >= available:
|
|
self._set_selected_units(pol, 0)
|
|
else:
|
|
self._set_selected_units(pol, available)
|
|
|
|
def _is_partially_storned(self, pol) -> bool:
|
|
raw = getattr(pol, "kstornu", None)
|
|
if raw is None:
|
|
return False
|
|
units = abs(float(getattr(pol, "pocet", 0) or 0))
|
|
try:
|
|
available = float(raw or 0)
|
|
except Exception:
|
|
return False
|
|
return 0 <= available < units
|
|
|
|
def _change_selected_units(self, pol, delta: int):
|
|
line_id = self._line_id(pol)
|
|
if not line_id:
|
|
return
|
|
available = self._storno_available_units(pol)
|
|
current = self.selected_quantities.get(line_id, 0.0)
|
|
self._set_selected_units(pol, current + float(delta))
|
|
|
|
def _set_selected_units(self, pol, value):
|
|
line_id = self._line_id(pol)
|
|
if not line_id:
|
|
return
|
|
available = self._storno_available_units(pol)
|
|
value = max(min(float(value or 0), available), 0.0)
|
|
if value <= 0:
|
|
self.selected_quantities.pop(line_id, None)
|
|
else:
|
|
self.selected_quantities[line_id] = value
|
|
Clock.schedule_once(lambda *_: self._render_detail(), 0)
|
|
|
|
def _select_all_items(self):
|
|
ucet = self.selected_ucet
|
|
if not ucet:
|
|
return
|
|
all_quantities = {
|
|
self._line_id(pol): self._storno_available_units(pol)
|
|
for pol in (getattr(ucet, "poloz", []) or [])
|
|
if self._line_id(pol) and self._storno_available_units(pol) > 0
|
|
}
|
|
if self.selected_quantities == all_quantities:
|
|
self.selected_quantities = {}
|
|
else:
|
|
self.selected_quantities = all_quantities
|
|
self._render_detail()
|
|
|
|
def _refresh_actions(self):
|
|
has_ucet = bool(self.selected_ucet)
|
|
has_selected = bool(self.selected_quantities)
|
|
is_storno = bool(getattr(self.selected_ucet, "is_storno", None)) if self.selected_ucet else False
|
|
is_limit = bool(getattr(self.selected_ucet, "limit_id", None)) if self.selected_ucet else False
|
|
has_storno_available = self._has_storno_available()
|
|
self.btn_storno.text = "Storno vybraneho" if has_selected else "Stornovat ucet"
|
|
for btn in (self.btn_storno, self.btn_return_table, self.btn_copy, self.btn_change_pay, self.btn_tip, self.btn_select_all):
|
|
btn.disabled = not has_ucet
|
|
if has_ucet and is_storno:
|
|
self.btn_storno.disabled = True
|
|
self.btn_return_table.disabled = True
|
|
self.btn_change_pay.disabled = True
|
|
self.btn_tip.disabled = True
|
|
if has_ucet and is_limit:
|
|
self.btn_return_table.disabled = True
|
|
self.btn_select_all.disabled = True
|
|
if has_selected:
|
|
self.btn_storno.disabled = True
|
|
self.btn_storno.text = "Stornovat ucet"
|
|
if has_ucet and not has_storno_available:
|
|
self.btn_storno.disabled = True
|
|
self.btn_select_all.disabled = True
|
|
|
|
def _has_storno_available(self) -> bool:
|
|
ucet = self.selected_ucet
|
|
if not ucet:
|
|
return False
|
|
return any(
|
|
self._storno_available_units(pol) > 0
|
|
for pol in (getattr(ucet, "poloz", []) or [])
|
|
)
|
|
|
|
def _print_copy(self):
|
|
if self.selected_ucet:
|
|
self.controller.closed_receipt_print_copy(self.selected_ucet)
|
|
|
|
def _storno(self):
|
|
if self.selected_ucet:
|
|
if getattr(self.selected_ucet, "limit_id", None):
|
|
self.selected_quantities = {}
|
|
self.controller.closed_receipt_storno_full(self.selected_ucet)
|
|
return
|
|
if self.selected_quantities:
|
|
self.controller.closed_receipt_storno_items(self.selected_ucet, self.selected_quantities)
|
|
self.selected_quantities = {}
|
|
self.refresh()
|
|
else:
|
|
self.controller.closed_receipt_storno_full(self.selected_ucet)
|
|
|
|
def _return_to_table(self):
|
|
if self.selected_ucet:
|
|
self.controller.closed_receipt_storno_return_to_table(self.selected_ucet)
|
|
|
|
def _change_payment(self):
|
|
if self.selected_ucet:
|
|
self.controller.closed_receipt_change_payment(self.selected_ucet)
|
|
|
|
def _edit_tip(self):
|
|
if self.selected_ucet:
|
|
self.controller.closed_receipt_edit_tip(self.selected_ucet)
|