Compare commits

1 Commits

Author SHA1 Message Date
milan.barlok 6d91e83e8c Stav 23.06.2026 2026-06-23 15:20:56 +02:00
5667 changed files with 1145858 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (pokladna)" project-jdk-type="Python SDK" />
<component name="PyPackaging">
<option name="earlyReleasesAsUpgrades" value="true" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pokladna.iml" filepath="$PROJECT_DIR$/.idea/pokladna.iml" />
</modules>
</component>
</project>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.10 (pokladna)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+28
View File
@@ -0,0 +1,28 @@
import tst_data
import api_call
from pydantic import SecretStr
cenik=tst_data.create_tst_cenik("01")
for p in cenik.cenpol:
for pos in p.pos_pc:
print(p.d_name, pos.page, pos.line, pos.col)
def get_ctx():
ctx = api_call.ApiContext(
user="Alto",
base_url="http://127.0.0.1:8000",
refresh_url="http://127.0.0.1:8000",
client_id="99",
id_kas="01",
username="Kobrle",
password=SecretStr("heslo"),
)
return ctx
ctx = get_ctx()
api_call.login_API(ctx)
setup = api_call.load_setup_API(ctx)
api_call.debugrepl_cenik_API(ctx)
+34
View File
@@ -0,0 +1,34 @@
#replace ceniku na jeden request
import tst_data
import api_call
from pydantic import SecretStr
# ---------- vytvoření testovacího ceníku ----------
cenik = tst_data.create_tst_cenik("01")
for p in cenik.cenpol:
for pos in p.pos_pc:
print(p.d_name, pos.page, pos.line, pos.col)
# ---------- API context ----------
def get_ctx():
ctx = api_call.ApiContext(
user="Alto",
base_url="http://127.0.0.1:8000",
refresh_url="http://127.0.0.1:8000",
client_id="99",
id_kas="01",
username="Kobrle",
password=SecretStr("heslo"),
)
return ctx
ctx = get_ctx()
# ---------- login ----------
api_call.login_API(ctx)
# ---------- načtení setup ----------
setup = api_call.load_setup_API(ctx)
# ---------- uložení CELÉHO ceníku ----------
api_call.debugrepl2_cenik_API(
ctx,
cenik.cenpol # ← list[CenPolCreate]
)
+74
View File
@@ -0,0 +1,74 @@
#testovaci program pro nahrani mapy stolu
# replace mapa_stolu na jeden request
import api_call
from pydantic import SecretStr
import data
# ---------- vytvoření testovací mapy ----------
def create_tst_mapa():
return data.MapaStolu(
pokladny=["01", "07"],
rooms=[
data.Room(
room_name="Hlavni",
stoly=[
data.Table(id="1", name="Stůl 1", pos_x=50, pos_y=50, width=100, height=100, radius=0),
data.Table(id="2", name="Stůl 2", pos_x=200, pos_y=50, width=100, height=100, radius=1),
data.Table(id="1", name="VIP", pos_x=150, pos_y=500, width=100, height=100, radius=1.0), # kulatý
data.Table(id="2", name="Bar", pos_x=380, pos_y=420, width=100, height=100, radius=0.0), # čtverec
data.Table(id="3", name="Rodina", pos_x= 20, pos_y=280, width=180, height=100, radius=0.2),# obdélník
data.Table(id="R4", name="Personal_dopo", pos_x=900, pos_y=520, width=120, height=120, radius=0.4),
],
),
data.Room(
room_name="Salonek",
stoly=[
data.Table(id="10", name="S1", pos_x=50, pos_y=50, width=80, height=80, radius=0),
data.Table(id="R4", name="Personal_dopo", pos_x=900, pos_y=520, width=120, height=120, radius=0.4),
data.Table(id="12", name="Malý", pos_x=700, pos_y=220, width=90, height=90, radius=0.0),
],
),
],
)
# ---------- API context ----------
def get_ctx():
ctx = api_call.ApiContext(
user="Alto",
base_url="http://127.0.0.1:8000",
refresh_url="http://127.0.0.1:8000",
client_id="99",
id_kas="01",
username="Kobrle",
password=SecretStr("heslo"),
)
return ctx
ctx = get_ctx()
# ---------- login ----------
api_call.login_API(ctx)
# ---------- vytvoření mapy ----------
mapa = create_tst_mapa()
print("\n--- TEST MAPA (CREATE) ---")
for room in mapa.rooms:
print("Room:", room.room_name)
for t in room.stoly:
print(" ", t.id, t.name, t.pos_x, t.pos_y)
# ---------- uložení mapy ----------
api_call.save_mapa_stolu_API(ctx, mapa)
print("\nMapa uložena\n")
# ---------- načtení mapy ----------
mapa_loaded = api_call.load_mapa_stolu_API(ctx)
api_call.logout_API(ctx)
print("\n--- TEST MAPA (LOADED) ---")
for room in mapa_loaded.rooms:
print("Room:", room.room_name)
for t in room.stoly:
print(" ", t.id, t.name, t.pos_x, t.pos_y)
+71
View File
@@ -0,0 +1,71 @@
# testovací program pro reset users
import api_call
from pydantic import SecretStr
import data
# ---------- vytvoření test users ----------
def create_tst_users():
return [
data.UserIn(
name="admin",
heslo="1234",
permits=[
"SPLIT",
"PLATBA",
"PL_HOTOVE",
"CLOSE",
"STORNO_PL",
"PL_SELECT",
],
),
data.UserIn(
name="obsluha",
heslo="1111",
permits=[
"PLATBA",
"PL_HOTOVE",
],
),
data.UserIn(
name="Petr",
heslo="123",
permits=[
"PL_HOTOVE",
"SPLIT",
"PLATBA",
"CLOSE",
"STORNO_PL",
"PL_SELECT",
],
),
]
# ---------- API context ----------
def get_ctx():
ctx = api_call.ApiContext(
user="Alto",
base_url="http://127.0.0.1:8000",
refresh_url="http://127.0.0.1:8000",
client_id="99",
id_kas="01",
username="Kobrle",
password=SecretStr("heslo"),
)
return ctx
ctx = get_ctx()
# ---------- login ----------
api_call.login_API(ctx)
# ---------- vytvoření users ----------
users = create_tst_users()
print("\n--- TEST USERS (CREATE) ---")
for u in users:
print("User:", u.name, u.permits)
# ---------- reset users ----------
api_call.reset_users_API(ctx, users)
print("\nUsers resetnuty\n")
# ---------- (volitelně) načtení ----------
users_loaded = api_call.load_users_API(ctx)
print("\n--- TEST USERS (LOADED) ---")
for u in users_loaded:
print("User:", u.name, u.permits)
# ---------- logout ----------
api_call.logout_API(ctx)
+655
View File
@@ -0,0 +1,655 @@
{% if printer %}
{{ printer.reset }}
{% if hlavicka.uz_cislo is defined %}
{{ hlavicka.titulka }} {{ hlavicka.uz_cislo }}
{% else %}
{{ hlavicka.titulka }} {{ hlavicka.c_uzaverka }}
{% endif %}
{{ hlavicka.uzaverka }}
{% endif %}
{% if hlavicka.id_zkratka is defined %}
{{ hlavicka.id_zkratka}}
{% endif %}
{% if hlavicka.h1 is defined %}
{{ hlavicka.h1}}
{% endif %}
{% if hlavicka.h2 is defined %}
{{ hlavicka.h2}}
{% endif %}
{% if hlavicka.h3 is defined %}
{{ hlavicka.h3}}
{% endif %}
{% if hlavicka.h4 is defined %}
{{ hlavicka.h4}}
{% endif %}
{% if hlavicka.h5 is defined %}
{{ hlavicka.h5}}
{% endif %}
{% if hlavicka.h6 is defined %}
{{ hlavicka.h6}}
{% endif %}
{% if hlavicka.h7 is defined %}
{{ hlavicka.h7}}
{% endif %}
{% if hlavicka.h8 is defined %}
{{ hlavicka.h8}}
{% endif %}
Od: {{ hlavicka.od }}
Do: {{ hlavicka.do }}
{% for section in sekcie %}
{{ "\n" }}{{ sekcie[section]['meno'] }}
{% for i in range(1, 40) %}={% endfor %}{{ '\r' }}
{% if sekcie[section]['meno'] == "Zoznam uzávierok" %}
{{ "\n" }}Kasa Uzávierka
{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['id_zkratka']|truncate(22, True, '') }} {{ sekcie[section]['data'][data]['uzaverka']}}
{% endfor %}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po cenových hladinách" %}
{% set prachy, prachy_puv = [0.0], [0.0] %}
{{ "\n" }} Tržba Po zľave
{% for data in sekcie[section]['data'] %}
Cenova hladina {{ sekcie[section]['data'][data]['cen_hlad'] }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy_puv'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy_puv']) }}{% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if prachy_puv.append(prachy_puv.pop() + sekcie[section]['data'][data]['prachy_puv']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(prachy_puv[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy_puv[0]) }}{% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po druhoch platby" %}
{% set cena_pl, cena_pl_puv, tip = [0.0], [0.0], [0.0] %}
{{ "\n" }} Tržba Po zľave TIP
{% for data in sekcie[section]['data'] %}
{% set row = sekcie[section]['data'][data] %}
{% set pred = row.get('cena_pl_puv', row['cena_pl']) %}
{% set popis = row['druh_pl'] if row['popis'] is none else row['popis'] %}
{{ popis|truncate(12, True, '') }} {{ '%8.2f'|format(pred) }}{{ '%8.2f'|format(row['cena_pl']) }}{{ '%8.2f'|format(row['tip']) }}
{% if cena_pl_puv.append(cena_pl_puv.pop() + pred) %}{% endif %}
{% if cena_pl.append(cena_pl.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% if tip.append(tip.pop() + sekcie[section]['data'][data]['tip']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {{ '%8.2f'|format(cena_pl_puv[0]) }}{{ '%8.2f'|format(cena_pl[0]) }}{{ '%8.2f'|format(tip[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po menách" %}
{% set cena_pl = [0.0] %}
{{ "\n" }}Mena Tržba v EUR Tržba{{ "\n" }}{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['mena']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['cena_pl'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['cena_pl']) }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['cena_mena'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['cena_mena']) }}
{% if cena_pl.append(cena_pl.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}
{{ "\n" }}C E L K O M {% for i in range(1, 11-('%0.2f'|format(cena_pl[0])|length)) %} {% endfor %}{{ '%0.2f'|format(cena_pl[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby za fiškálne platby" %}
{% set cena_pl, cena_pl_puv, tip = [0.0], [0.0], [0.0] %}
{{ "\n" }} Tržba Po zľave TIP
{% for data in sekcie[section]['data'] %}
{% set row = sekcie[section]['data'][data] %}
{% set pred = row.get('cena_pl_puv', row['cena_pl']) %}
{{ row['popis']|truncate(12, True, '') }} {{ '%8.2f'|format(pred) }}{{ '%8.2f'|format(row['cena_pl']) }}{{ '%8.2f'|format(row['tip']) }}
{% if cena_pl_puv.append(cena_pl_puv.pop() + pred) %}{% endif %}
{% if cena_pl.append(cena_pl.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% if tip.append(tip.pop() + sekcie[section]['data'][data]['tip']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {{ '%8.2f'|format(cena_pl_puv[0]) }}{{ '%8.2f'|format(cena_pl[0]) }}{{ '%8.2f'|format(tip[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Úhrady pohľadávok" %}
{% set cena_pl = [0.0] %}
{{ "\n" }} Tržba
{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['druh_pl'] }} {{ sekcie[section]['data'][data]['username']|truncate(15, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['cena_pl'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['cena_pl']) }}
{% if cena_pl.append(cena_pl.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(cena_pl[0])|length)) %} {% endfor %}{{ '%0.2f'|format(cena_pl[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po manageroch" %}
{% set prachy, prachy_puv = [0.0], [0.0] %}
{{ "\n" }} Tržba Po zľave
{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['id_zkratka']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy_puv'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy_puv']) }}{% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if prachy_puv.append(prachy_puv.pop() + sekcie[section]['data'][data]['prachy_puv']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(prachy_puv[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy_puv[0]) }}{% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po manageroch a daniach" %}
{% set prachy = [0.0] %}
{{ "\n" }} Základ Daň Celkom
{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['id_zkratka']|truncate(24, True, '') }} {{ '%0.0f'|format(sekcie[section]['data'][data]['dan_sazba']) }}%
{% if sekcie[section]['data'][data]['dan'] is defined %}
{% for i in range(1, 15-('%0.2f'|format(sekcie[section]['data'][data]['zaklad'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['zaklad']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['dan'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['dan']) }}{% for i in range(1, 15-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% else %}
{% for i in range(1, 15-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% endif %}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Na odovzdanie" %}
{% set suma, celkom, memodovzdat, memdruh_pl, memtxt, mempocet = [0.0], [0.0], ['?'],['?'],['?'],[0] %}
{{ "\n" }}
{% for data in sekcie[section]['data'] %}
{% if memodovzdat[0]=='?' or memodovzdat[0]==sekcie[section]['data'][data]['odovzdat'] %}
{% if memdruh_pl[0]=='?' or memdruh_pl[0]|trim()==sekcie[section]['data'][data]['druh_pl']|trim() %}
{% if sekcie[section]['data'][data]['operacia'] != 3 %}
{% if suma.append(suma.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% if celkom.append(celkom.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 0 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Tržba {{ sekcie[section]['data'][data]['j0']|truncate(21, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Tržba {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(18, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 1 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Úhrada {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Úhrada {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 2 %}
{% if sekcie[section]['data'][data]['operacia'] == 0 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Vklady {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Vklady {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 1 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Výbery {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Výbery {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 2 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Zo včera+ {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Zo včera+ {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(14, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 3 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Uzávierka {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Uzávierka {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(14, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 4 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Prenos- {{ sekcie[section]['data'][data]['j0']|truncate(19, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Prenos- {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(16, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% endif %}
{% if memdruh_pl|length > 0 %}{{ memdruh_pl.remove(memdruh_pl[0]) or ""}}{% endif %}
{% if memdruh_pl.append(sekcie[section]['data'][data]['druh_pl']) %}{% endif %}
{% if memtxt|length > 0 %}{{ memtxt.remove(memtxt[0]) or ""}}{% endif %}
{% if memtxt.append(sekcie[section]['data'][data]['j0']) %}{% endif %}
{% if memodovzdat|length > 0 %}{{ memodovzdat.remove(memodovzdat[0]) or "" }}{% endif %}
{% if memodovzdat.append(sekcie[section]['data'][data]['odovzdat']) %}{% endif %}
{% else %}
{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
Odovzdať {{ memodovzdat[0]|truncate(19, True, '') }} {% for i in range(1, 11-('%0.2f'|format(suma[0])|length)) %} {% endfor %}{{ '%0.2f'|format(suma[0]) }}
{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
{% if mempocet.append(mempocet.pop()+1) %}{% endif %}
{% if suma|length > 0 %}{{ suma.remove(suma[0]) or ""}}{% endif %}
{% if suma.append(0.0) %}{% endif %}
{% if sekcie[section]['data'][data]['operacia'] != 3 %}
{% if suma.append(suma.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% if celkom.append(celkom.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 0 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Tržba {{ sekcie[section]['data'][data]['j0']|truncate(21, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Tržba {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(18, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 1 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Úhrada {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Úhrada {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 2 %}
{% if sekcie[section]['data'][data]['operacia'] == 0 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Vklady {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Vklady {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 1 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Výbery {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Výbery {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 2 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Zo včera+ {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Zo včera+ {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(14, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 3 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Uzávierka {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Uzávierka {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(14, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 4 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Prenos- {{ sekcie[section]['data'][data]['j0']|truncate(19, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Prenos- {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(16, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% endif %}
{% if memdruh_pl|length > 0 %}{{ memdruh_pl.remove(memdruh_pl[0]) or ""}}{% endif %}
{% if memdruh_pl.append(sekcie[section]['data'][data]['druh_pl']) %}{% endif %}
{% if memtxt|length > 0 %}{{ memtxt.remove(memtxt[0]) or ""}}{% endif %}
{% if memtxt.append(sekcie[section]['data'][data]['j0']) %}{% endif %}
{% if memodovzdat|length > 0 %}{{ memodovzdat.remove(memodovzdat[0]) or "" }}{% endif %}
{% if memodovzdat.append(sekcie[section]['data'][data]['odovzdat']) %}{% endif %}
{% endif %}
{% else %}
{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
Odovzdať {{ memodovzdat[0]|truncate(19, True, '') }} {% for i in range(1, 11-('%0.2f'|format(suma[0])|length)) %} {% endfor %}{{ '%0.2f'|format(suma[0]) }}
{% if mempocet.append(mempocet.pop()+1) %}{% endif %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
{% if mempocet[0] > 1 %}
ODOVZDAŤ {{ memodovzdat[0] }} {% for i in range(1, 11-('%0.2f'|format(celkom[0])|length)) %} {% endfor %}{{ '%0.2f'|format(celkom[0]) }}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
{% endif %}
{% if mempocet|length > 0 %}{{ mempocet.remove(mempocet[0]) or ""}}{% endif %}
{% if mempocet.append(0) %}{% endif %}
{% if suma|length > 0 %}{{ suma.remove(suma[0]) or ""}}{% endif %}
{% if suma.append(0.0) %}{% endif %}
{% if celkom|length > 0 %}{{ celkom.remove(celkom[0]) or ""}}{% endif %}
{% if celkom.append(0.0) %}{% endif %}
{% if sekcie[section]['data'][data]['operacia'] < 3 %}
{% if suma.append(suma.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% if celkom.append(celkom.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 0 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Tržba {{ sekcie[section]['data'][data]['j0']|truncate(21, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Tržba {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(18, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 1 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Úhrada {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Úhrada {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['typ'] == 2 %}
{% if sekcie[section]['data'][data]['operacia'] == 0 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Vklady {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Vklady {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 1 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Výbery {{ sekcie[section]['data'][data]['j0']|truncate(20, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Výbery {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 2 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Prevod+{{ sekcie[section]['data'][data]['j0']|truncate(19, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Prevod+{{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(16, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 3 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Uzávierka {{ sekcie[section]['data'][data]['j0']|truncate(17, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Uzávierka {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(14, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% if sekcie[section]['data'][data]['operacia'] == 4 %}
{% if sekcie[section]['data'][data]['prn_no'] is none %}
Prevod- {{ sekcie[section]['data'][data]['j0']|truncate(19, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% else %}
Prevod- {{ sekcie[section]['data'][data]['prn_no']|truncate(2, True, '') }} {{ sekcie[section]['data'][data]['j0']|truncate(16, True, '') }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% endif %}
{% endif %}
{% endif %}
{% if memdruh_pl|length > 0 %}{{ memdruh_pl.remove(memdruh_pl[0]) or ""}}{% endif %}
{% if memdruh_pl.append(sekcie[section]['data'][data]['druh_pl']) %}{% endif %}
{% if memtxt|length > 0 %}{{ memtxt.remove(memtxt[0]) or ""}}{% endif %}
{% if memtxt.append(sekcie[section]['data'][data]['j0']) %}{% endif %}
{% if memodovzdat|length > 0 %}{{ memodovzdat.remove(memodovzdat[0]) or "" }}{% endif %}
{% if memodovzdat.append(sekcie[section]['data'][data]['odovzdat']) %}{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
Odovzdať {{ memodovzdat[0]|truncate(19, True, '') }} {% for i in range(1, 11-('%0.2f'|format(suma[0])|length)) %} {% endfor %}{{ '%0.2f'|format(suma[0]) }}
{% if mempocet.append(mempocet.pop()+1) %}{% endif %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
{% if mempocet[0] > 1 %}
ODOVZDAŤ {{ memodovzdat[0] }} {% for i in range(1, 11-('%0.2f'|format(celkom[0])|length)) %} {% endfor %}{{ '%0.2f'|format(celkom[0]) }}
{% endif %}
{% if mempocet|length > 0 %}{{ mempocet.remove(mempocet[0]) or ""}}{% endif %}
{% if mempocet.append(0) %}{% endif %}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po DPH - fiškálne platby" %}
{% set prachy, dan, zaklad, round50 = [0.0], [0.0], [0.0], [0.0] %}
{{ "\n" }} Tržba Daň Základ
{% for data in sekcie[section]['data'] %}
{% if sekcie[section]['data'][data]['round50'] is defined and sekcie[section]['data'][data]['round50'] != None %}
{% if round50.append(round50.pop() + sekcie[section]['data'][data]['round50']) %}{% endif %}
{% else %}
{% if sekcie[section]['data'][data]['dan'] is defined %}
DPH {{ sekcie[section]['data'][data]['dan_sazba'] }}% {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['dan'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['dan']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['zaklad'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['zaklad']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if dan.append(dan.pop() + sekcie[section]['data'][data]['dan']) %}{% endif %}
{% if zaklad.append(zaklad.pop() + sekcie[section]['data'][data]['zaklad']) %}{% endif %}
{% else %}
{% if sekcie[section]['data'][data]['dan_sazba'] is defined %}
DPH {{ sekcie[section]['data'][data]['dan_sazba'] }}% {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}{% for i in range(1, 10-('%0.2f'|format(dan[0])|length)) %} {% endfor %}{{ '%0.2f'|format(dan[0]) }}{% for i in range(1, 10-('%0.2f'|format(zaklad[0])|length)) %} {% endfor %}{{ '%0.2f'|format(zaklad[0]) }}
{% if round50[0]!=0 %}
ZAOKRÚHLENIE {% for i in range(1, 10-('%0.2f'|format(round50[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]) }}
Po ZAOKR. {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]+prachy[0]) }}
{% endif %}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po DPH" %}
{% set prachy, dan, zaklad, round50 = [0.0], [0.0], [0.0], [0.0] %}
{{ "\n" }} Tržba Daň Základ
{% for data in sekcie[section]['data'] %}
{% if sekcie[section]['data'][data]['round50'] is defined and sekcie[section]['data'][data]['round50'] != None %}
{% if round50.append(round50.pop() + sekcie[section]['data'][data]['round50']) %}{% endif %}
{% else %}
{% if sekcie[section]['data'][data]['dan'] is defined %}
DPH {% for i in range(1, 5-('%0.1f'|format(sekcie[section]['data'][data]['dan_sazba'])|length)) %} {% endfor %}{{ '%0.1f'|format(sekcie[section]['data'][data]['dan_sazba']) }}% {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['dan'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['dan']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['zaklad'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['zaklad']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if dan.append(dan.pop() + sekcie[section]['data'][data]['dan']) %}{% endif %}
{% if zaklad.append(zaklad.pop() + sekcie[section]['data'][data]['zaklad']) %}{% endif %}
{% else %}
{% if sekcie[section]['data'][data]['dan_sazba'] is defined %}
DPH {% for i in range(1, 5-('%0.1f'|format(sekcie[section]['data'][data]['dan_sazba'])|length)) %} {% endfor %}{{ '%0.1f'|format(sekcie[section]['data'][data]['dan_sazba']) }}% {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}{% for i in range(1, 10-('%0.2f'|format(dan[0])|length)) %} {% endfor %}{{ '%0.2f'|format(dan[0]) }}{% for i in range(1, 10-('%0.2f'|format(zaklad[0])|length)) %} {% endfor %}{{ '%0.2f'|format(zaklad[0]) }}
{% if round50[0]!=0 %}
ZAOKRÚHLENIE {% for i in range(1, 10-('%0.2f'|format(round50[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]) }}
Po ZAOKR. {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]+prachy[0]) }}
{% endif %}
{% endif %}
{% if sekcie[section]['meno'] == "Žurnál storien" %}
{% set prachy = [0.0] %}
{% set prachy, celkom, memmeno, memoperacia, oldmeno = [0.0], [0.0],['?'],['?'],['?'] %}
{{ "\n" }}
Účet Platba Suma Pôv.účet
{% for data in sekcie[section]['data'] %}
{% if oldmeno|length > 0 %}{{ oldmeno.remove(oldmeno[0]) or ""}}{% endif %}
{% if sekcie[section]['data'][data]['username'] is none %}
{% if oldmeno.append(sekcie[section]['data'][data]['autor']) %}{% endif %}
{% else %}
{% if oldmeno.append(sekcie[section]['data'][data]['username']) %}{% endif %}
{% endif %}
{% if ((memmeno[0]=='?' or memmeno[0]==oldmeno[0]) and (memoperacia[0]=='?' or memoperacia[0]==sekcie[section]['data'][data]['text_sleva']|truncate(1,True,'') )) %}
{{'%5i'|format(sekcie[section]['data'][data]['ucet'])}} {% if sekcie[section]['data'][data]['j0'] is none %}{{sekcie[section]['data'][data]['druh_pl']|truncate(18, True, '')}}{% else %}{{sekcie[section]['data'][data]['j0']|truncate(18, True, '')}}{% endif %} {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['cena_pl'])|length)) %} {% endfor %}{{'%0.2f'|format(sekcie[section]['data'][data]['cena_pl']) }} {{sekcie[section]['data'][data]['text_sleva']| replace("@","")| replace("^","") }}
{% if celkom.append(celkom.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% if memmeno|length > 0 %}{{ memmeno.remove(memmeno[0]) or ""}}{% endif %}
{% if memmeno.append(oldmeno[0]) %}{% endif %}
{% if memoperacia|length > 0 %}{{ memoperacia.remove(memoperacia[0]) or "" }}{% endif %}
{% if memoperacia.append(sekcie[section]['data'][data]['text_sleva']|truncate(1,True,'')) %}{% endif %}
{% else %}
{% if memoperacia|length > 0 %}
{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
{% if memoperacia[0]=='@' %}Storno {% else %}Zmena platby {% endif %}{{memmeno[0]|trim()}}{% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
{% endif %}
{% if prachy|length > 0 %}{{ prachy.remove(prachy[0]) or ""}}{% endif %}
{% if prachy.append(0.0) %}{% endif %}
{{'%5i'|format(sekcie[section]['data'][data]['ucet'])}} {% if sekcie[section]['data'][data]['j0'] is none %}{{sekcie[section]['data'][data]['druh_pl']|truncate(18, True, '')}}{% else %}{{sekcie[section]['data'][data]['j0']|truncate(18, True, '')}}{% endif %} {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['cena_pl'])|length)) %} {% endfor %}{{'%0.2f'|format(sekcie[section]['data'][data]['cena_pl']) }} {{sekcie[section]['data'][data]['text_sleva']| replace("@","")| replace("^","") }}
{% if memmeno|length > 0 %}{{ memmeno.remove(memmeno[0]) or ""}}{% endif %}
{% if memmeno.append(oldmeno[0]) %}{% endif %}
{% if memoperacia|length > 0 %}{{ memoperacia.remove(memoperacia[0]) or "" }}{% endif %}
{% if memoperacia.append(sekcie[section]['data'][data]['text_sleva']|truncate(1,True,'')) %}{% endif %}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% if celkom.append(celkom.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% endif %}
{% endfor %}
{% if memoperacia|length > 0 %}
{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
{% if memoperacia[0]=='@' %}Storno {% else %}Zmena platby {% endif %}{{memmeno[0]|trim()}}{% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 10-('%0.2f'|format(celkom[0])|length)) %} {% endfor %}{{ '%0.2f'|format(celkom[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Počet účtov čašníka" %}
{{ "\n" }}Meno Kto Poč.Druh pl. Čiastka
{% set prachy = [0.0] %}
{% for data in sekcie[section]['data'] %}
{{ (sekcie[section]['data'][data]['username']|truncate(13, True, '')) .ljust(13) }} {{ sekcie[section]['data'][data]['autor'] .ljust(3) }} {{ ('%0.0f'|format(sekcie[section]['data'][data]['pocet'])) .rjust(3) }} {{ (sekcie[section]['data'][data]['druh_pl']|truncate(10, True, '')) .ljust(10) }} {{ ('%0.2f'|format(sekcie[section]['data'][data]['suma'])) .rjust(6) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 21-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Platby terminálom" %}
{{ "\n" }}Terminál Suma
{% set prachy = [0.0] %}
{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['prn_name'] }} {% for i in range(1, 18-('%0.2f'|format(sekcie[section]['data'][data]['suma'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['suma']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['suma']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 21-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po platbách a daniach" %}
{% set prachy, dan, zaklad, round50 = [0.0], [0.0], [0.0], [0.0] %}
{{ "\n" }}Sadzba Tržba Daň Základ
{% for data in sekcie[section]['data'] %}
{% if data != None %}
{% if sekcie[section]['data'][data]['round50'] is defined and sekcie[section]['data'][data]['round50'] != None %}
{% if round50.append(round50.pop() + sekcie[section]['data'][data]['round50']) %}{% endif %}
{% else %}
{% if sekcie[section]['data'][data]['j0'] != None and sekcie[section]['data'][data]['dan_sazba'] is defined and sekcie[section]['data'][data]['dan_sazba'] != None %}
{{ sekcie[section]['data'][data]['j0']|truncate(40, True, '') }}
{{ sekcie[section]['data'][data]['dan_sazba'] }}% {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['dan'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['dan']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['zaklad'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['zaklad']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if dan.append(dan.pop() + sekcie[section]['data'][data]['dan']) %}{% endif %}
{% if zaklad.append(zaklad.pop() + sekcie[section]['data'][data]['zaklad']) %}{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}{% for i in range(1, 10-('%0.2f'|format(dan[0])|length)) %} {% endfor %}{{ '%0.2f'|format(dan[0]) }}{% for i in range(1, 10-('%0.2f'|format(zaklad[0])|length)) %} {% endfor %}{{ '%0.2f'|format(zaklad[0]) }}
{% if round50[0]!=0 %}
ZAOKRÚHLENIE {% for i in range(1, 10-('%0.2f'|format(round50[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]) }}
Po ZAOKR. {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]+prachy[0]) }}
{% endif %}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po fiškálnych platbách a daniach" %}
{% set prachy, dan, zaklad, round50 = [0.0], [0.0], [0.0], [0.0] %}
{{ "\n" }}Sadzba Tržba Daň Základ
{% for data in sekcie[section]['data'] %}
{% if data != None %}
{% if sekcie[section]['data'][data]['round50'] is defined and sekcie[section]['data'][data]['round50'] != None %}
{% if round50.append(round50.pop() + sekcie[section]['data'][data]['round50']) %}{% endif %}
{% else %}
{% if sekcie[section]['data'][data]['j0'] != None and sekcie[section]['data'][data]['dan_sazba'] is defined and sekcie[section]['data'][data]['dan_sazba'] != None %}
{{ sekcie[section]['data'][data]['j0']|truncate(40, True, '') }}
{{ sekcie[section]['data'][data]['dan_sazba'] }}% {% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['dan'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['dan']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['zaklad'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['zaklad']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if dan.append(dan.pop() + sekcie[section]['data'][data]['dan']) %}{% endif %}
{% if zaklad.append(zaklad.pop() + sekcie[section]['data'][data]['zaklad']) %}{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}{% for i in range(1, 10-('%0.2f'|format(dan[0])|length)) %} {% endfor %}{{ '%0.2f'|format(dan[0]) }}{% for i in range(1, 10-('%0.2f'|format(zaklad[0])|length)) %} {% endfor %}{{ '%0.2f'|format(zaklad[0]) }}
{% if round50[0]!=0 %}
ZAOKRÚHLENIE {% for i in range(1, 10-('%0.2f'|format(round50[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]) }}
Po ZAOKR. {% for i in range(1, 10-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(round50[0]+prachy[0]) }}
{% endif %}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po druhoch" %}
{% set prachy, prachy_puv, mnozstvi = [0.0], [0.0], [0.0] %}
{{ "\n" }} Tržba Po zľave Počet
{% for data in sekcie[section]['data'] %}{% if sekcie[section]['data'][data]['mnozstvi'] is defined %}
{{ sekcie[section]['data'][data]['druh']|truncate(10, True, '') }}{% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy_puv'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy_puv']) }}{% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['mnozstvi'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['mnozstvi']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if prachy_puv.append(prachy_puv.pop() + sekcie[section]['data'][data]['prachy_puv']) %}{% endif %}
{% if mnozstvi.append(mnozstvi.pop() + sekcie[section]['data'][data]['mnozstvi']) %}{% endif %}
{% else %}
{{ sekcie[section]['data'][data]['druh'] }}{% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy_puv'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy_puv']) }}{% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if prachy_puv.append(prachy_puv.pop() + sekcie[section]['data'][data]['prachy_puv']) %}{% endif %}
{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
CELKOM {% for i in range(1, 11-('%0.2f'|format(prachy_puv[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy_puv[0]) }}{% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}{% for i in range(1, 10-('%0.2f'|format(mnozstvi[0])|length)) %} {% endfor %}{{ '%0.2f'|format(mnozstvi[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Spotreba" %}
{% set ciastka = [0.0] %}
{% set last_category, last_section = ["s"], ["s"] %}
Počet Cena Hladina Čiastka DPH
{% for data in sekcie[section]['data'] %}
{% if last_section[0] != sekcie[section]['data'][data]['id_zkratka'] %}
{% if last_section.pop() %}{% endif %}
{{ sekcie[section]['data'][data]['id_zkratka'] }}
{% if last_section.append(sekcie[section]['data'][data]['id_zkratka']) %}{% endif %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
{% endif %}
{% if last_category[0] != sekcie[section]['data'][data]['druh'] %}
{{ sekcie[section]['data'][data]['druh'] }}
{% if last_category.pop() %}{% endif %}
{% if last_category.append(sekcie[section]['data'][data]['druh']) %}{% endif %}{% for i in range(1, 40) %}-{% endfor %}{{ "\n" }}
{% endif %}
{% if sekcie[section]['data'][data]['jc'] is defined %}
{{ sekcie[section]['data'][data]['nazev'] }}
{% for i in range(1, 4-('%d'|format(sekcie[section]['data'][data]['mnozstvi']))|length) %} {% endfor %}{{ sekcie[section]['data'][data]['mnozstvi'] }}x {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['jc'])|length)) %} {% endfor %} {{ '%0.2f'|format(sekcie[section]['data'][data]['jc']) }} {{ sekcie[section]['data'][data]['cen_hlad'] }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['ciastka'])|length)) %} {% endfor %} {{ '%0.2f'|format(sekcie[section]['data'][data]['ciastka']) }} {{ sekcie[section]['data'][data]['dph'] }}%
{% else %}
{{ sekcie[section]['data'][data]['nazev'] }}
{% for i in range(1, 4-('%d'|format(sekcie[section]['data'][data]['mnozstvi']))|length) %} {% endfor %}{{ sekcie[section]['data'][data]['mnozstvi'] }}x {{ sekcie[section]['data'][data]['cen_hlad'] }} {{ sekcie[section]['data'][data]['spart'] }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['ciastka'])|length)) %} {% endfor %} {{ '%0.2f'|format(sekcie[section]['data'][data]['ciastka']) }} {{ sekcie[section]['data'][data]['dph'] }}%
{% endif %}
{% if ciastka.append(ciastka.pop() + sekcie[section]['data'][data]['ciastka']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(ciastka[0])|length)) %} {% endfor %}{{ '%0.2f'|format(ciastka[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po čašníkoch" %}
{% set prachy, prachy_puv = [0.0], [0.0] %}
{{ "\n" }} Tržba Po zľave
{% for data in sekcie[section]['data'] %}
{% if sekcie[section]['data'][data]['username'] != None %}{{ (sekcie[section]['data'][data]['username']+" ")|truncate(17, True, '') }}{% else %} {% endif %} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy_puv'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy_puv']) }}{% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% if prachy_puv.append(prachy_puv.pop() + sekcie[section]['data'][data]['prachy_puv']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(prachy_puv[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy_puv[0]) }}{% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% if prachy_puv[0] != prachy[0] %}
Z Ľ A V A {% for i in range(1, 11-('%0.2f'|format(prachy_puv[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy_puv[0]-prachy[0]) }}
{% endif %}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po čašníkoch a druhoch platieb" %}
{% set cena_pl = [0.0] %}
{{ "\n" }}Meno Autor Čiastka Druh
{% for data in sekcie[section]['data'] %}
{% if sekcie[section]['data'][data]['username'] != None %}{{ (sekcie[section]['data'][data]['username']+" ")|truncate(17, True, '') }}{% else %} {% endif %}{{ sekcie[section]['data'][data]['autor'] }} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['cena_pl'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['cena_pl']) }} {{ sekcie[section]['data'][data]['druh_pl']|truncate(6, True, '', 0) }}
{% if cena_pl.append(cena_pl.pop() + sekcie[section]['data'][data]['cena_pl']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(cena_pl[0])|length)) %} {% endfor %}{{ '%0.2f'|format(cena_pl[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po čašníkoch v hotovosti" %}
{% set prachy = [0.0] %}
{{ "\n" }} Po zľave
{% for data in sekcie[section]['data'] %}
{% if sekcie[section]['data'][data]['username'] != None %}{{ (sekcie[section]['data'][data]['username']+" ")|truncate(17, True, '') }}{% else %} {% endif %} {% for i in range(1, 11-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Tržby po platbách, manageroch a daniach" %}
{% set prachy = [0.0] %}
{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['id_zkratka']|truncate(24, True, '') }} {{ sekcie[section]['data'][data]['druh_pl'] }} {{ '%0.0f'|format(sekcie[section]['data'][data]['dan_sazba']) }}%
{% if sekcie[section]['data'][data]['dan'] is defined %}
{% for i in range(1, 15-('%0.2f'|format(sekcie[section]['data'][data]['zaklad'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['zaklad']) }}{% for i in range(1, 10-('%0.2f'|format(sekcie[section]['data'][data]['dan'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['dan']) }}{% for i in range(1, 15-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% else %}
{% for i in range(1, 15-('%0.2f'|format(sekcie[section]['data'][data]['prachy'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['prachy']) }}
{% endif %}
{% if prachy.append(prachy.pop() + sekcie[section]['data'][data]['prachy']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(prachy[0])|length)) %} {% endfor %}{{ '%0.2f'|format(prachy[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Sumár vkladov a výberov" %}
{% set ciastka = [0.0] %}
{{ "\n" }}
{% for data in sekcie[section]['data'] %}
{% if sekcie[section]['data'][data]['username'] != None %}{{ (sekcie[section]['data'][data]['username']+" ")|truncate(20, True, '') }}{% else %} {% endif %}{{ sekcie[section]['data'][data]['datum'] }}
{{ (sekcie[section]['data'][data]['popis'])|truncate(27, True, '') }} {% for i in range(1, 12-('%0.2f'|format(sekcie[section]['data'][data]['ciastka'])|length)) %} {% endfor %}{{ '%0.2f'|format(sekcie[section]['data'][data]['ciastka']) }}
{% if ciastka.append(ciastka.pop() + sekcie[section]['data'][data]['ciastka']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.2f'|format(ciastka[0])|length)) %} {% endfor %}{{ '%0.2f'|format(ciastka[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Predaj alkoholu" %}
{% set pocet = [0.0] %}
{{ "\n" }}Ean Názov Počet
{% for data in sekcie[section]['data'] %}
{{ sekcie[section]['data'][data]['ean']|truncate(13, True, '') }} {% if sekcie[section]['data'][data]['nazev'] %}{{ (sekcie[section]['data'][data]['nazev']+" ")|truncate(19, True, '') }}{% else %} {% endif %}{% for i in range(1, 7-('%0.0f'|format(sekcie[section]['data'][data]['pocet'])|length)) %} {% endfor %}{{ sekcie[section]['data'][data]['pocet'] }}
{% if pocet.append(pocet.pop() + sekcie[section]['data'][data]['pocet']) %}{% endif %}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
C E L K O M {% for i in range(1, 11-('%0.0f'|format(pocet[0])|length)) %} {% endfor %}{{ '%0.0f'|format(pocet[0]) }}
{% endif %}
{% if sekcie[section]['meno'] == "Otvorené stoly" %}
{{ "\n" }}Stôl Suma
{% for data in sekcie[section]['data'] %}
{{ "%-27s%12s" % ( ((sekcie[section]['data'][data]['stol']|trim) ~ '/' ~ (sekcie[section]['data'][data]['miestnost']|trim))[:27], "%0.2f" % sekcie[section]['data'][data]['suma']) }}
{% endfor %}
{% for i in range(1, 40) %}={% endfor %}{{ "\n" }}
{% endif %}
{% endfor %}
{% if printer %}
{{ printer.crlf }}
{{ printer.crlf }}
{{ printer.crlf }}
{{ printer.fullcut }}
{% endif %}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1037
View File
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
import math
from kivy.uix.screenmanager import Screen
from kivy.clock import Clock
from kivy.metrics import dp
COLS = 7
BTN_W = dp(150)
BTN_H = dp(120)
SP = dp(10)
PAD = dp(10)
class BaseAccountSelectScreen(Screen):
def _create_ucet_button(self, u):
raise NotImplementedError(
"_create_ucet_button must be implemented in subclass"
)
def _load_ucty(self, ucty):
self.grid.clear_widgets()
for u in ucty:
if not u.stul:
continue
btn = self._create_ucet_button(u)
self.grid.add_widget(btn)
Clock.schedule_once(self._update_scroll, 0)
def _update_scroll(self, *_):
if not self.scroll or not self.grid:
return
count = len(self.grid.children)
if count == 0:
self.scroll.do_scroll_x = False
self.scroll.do_scroll_y = False
return
rows = math.ceil(count / COLS)
content_h = rows * BTN_H + (rows - 1) * SP + 2 * PAD
self.scroll.do_scroll_y = content_h > self.scroll.height
+9
View File
@@ -0,0 +1,9 @@
from datetime import datetime
def admheslo():
today = datetime.today()
day = today.day
month = today.month
day_of_month = (today.weekday()+2)%8
if day_of_month == 0:
day_of_month = 1
return (f"{day*month}.{day*day_of_month}{day_of_month*month}")
+2035
View File
File diff suppressed because it is too large Load Diff
+209
View File
@@ -0,0 +1,209 @@
from __future__ import annotations
import json
import uuid
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any
import requests
from requests.auth import HTTPBasicAuth
@dataclass
class BankTerminalConfig:
terminal_type: str
url: str = ""
terminal_id: str = ""
sale_id: str = "Alto/foodw32"
user: str = ""
password: str = ""
currency: str = "CZK"
timeout: int = 60
log_path: str = "terminal_log.jsonl"
extra: dict[str, Any] = field(default_factory=dict)
@dataclass
class BankTerminalResult:
success: bool
raw: dict[str, Any] | None = None
error: str = ""
service_id: str = ""
def legacy_dict(self) -> dict[str, Any]:
result: dict[str, Any] = {
"success": self.success,
"service_id": self.service_id,
}
if self.raw is not None:
result["raw"] = self.raw
if self.error:
result["error"] = self.error
return result
class BankTerminalClient:
def __init__(self, config: BankTerminalConfig):
self.config = config
def payment(self, amount: float) -> BankTerminalResult:
raise NotImplementedError
def abort(self, service_id: str) -> BankTerminalResult:
return BankTerminalResult(success=False, error="Abort nie je podporovany.", service_id=service_id)
def refund(self, original_service_id: str, amount: float) -> BankTerminalResult:
return BankTerminalResult(success=False, error="Refund nie je podporovany.", service_id=original_service_id)
def extract_receipt_text(self, response: dict[str, Any]) -> str:
return ""
def _log_transaction(self, data: dict[str, Any]) -> None:
log_path = Path(self.config.log_path or "terminal_log.jsonl")
with log_path.open("a", encoding="utf-8") as f:
f.write(json.dumps({
"time": datetime.utcnow().isoformat(),
"terminal_type": self.config.terminal_type,
"data": data,
}, ensure_ascii=False) + "\n")
class BesteronTerminalClient(BankTerminalClient):
def _message_header(self, category: str, service_id: str) -> dict[str, str]:
return {
"ProtocolVersion": "3.0",
"MessageClass": "Service",
"MessageCategory": category,
"MessageType": "Request",
"ServiceID": service_id,
"SaleID": self.config.sale_id,
"POIID": self.config.terminal_id,
}
def payment(self, amount: float) -> BankTerminalResult:
service_id = str(uuid.uuid4())[:10]
payload = {
"SaleToPOIRequest": {
"MessageHeader": self._message_header("Payment", service_id),
"PaymentRequest": {
"SaleData": {
"SaleTransactionID": {
"TransactionID": service_id,
"TimeStamp": datetime.utcnow().isoformat(),
}
},
"PaymentTransaction": {
"AmountsReq": {
"Currency": self.config.currency,
"RequestedAmount": float(amount),
}
},
},
}
}
try:
response = requests.post(
self.config.url,
json=payload,
auth=HTTPBasicAuth(self.config.user, self.config.password),
headers={"Content-Type": "application/json"},
timeout=self.config.timeout,
)
response.raise_for_status()
data = response.json()
self._log_transaction(data)
payment_response = data.get("SaleToPOIResponse", {}).get("PaymentResponse", {})
result = payment_response.get("Response", {}).get("Result")
return BankTerminalResult(success=result == "Success", raw=data, service_id=service_id)
except Exception as exc:
return BankTerminalResult(success=False, error=str(exc), service_id=service_id)
def abort(self, service_id: str) -> BankTerminalResult:
if not service_id:
return BankTerminalResult(success=False, error="Chyba service_id pre abort.")
payload = {
"SaleToPOIRequest": {
"MessageHeader": self._message_header("Abort", service_id),
"AbortRequest": {
"AbortReason": "MerchantAbort",
},
}
}
try:
response = requests.post(
self.config.url,
json=payload,
auth=HTTPBasicAuth(self.config.user, self.config.password),
headers={"Content-Type": "application/json"},
timeout=min(self.config.timeout, 10),
)
response.raise_for_status()
data = response.json() if response.text else {}
if data:
self._log_transaction(data)
return BankTerminalResult(success=True, raw=data, service_id=service_id)
except Exception as exc:
return BankTerminalResult(success=False, error=str(exc), service_id=service_id)
def refund(self, original_service_id: str, amount: float) -> BankTerminalResult:
service_id = str(uuid.uuid4())[:10]
payload = {
"SaleToPOIRequest": {
"MessageHeader": self._message_header("Reversal", service_id),
"ReversalRequest": {
"OriginalPOITransaction": {
"POITransactionID": {
"TransactionID": original_service_id,
}
},
"ReversedAmount": float(amount),
},
}
}
try:
response = requests.post(
self.config.url,
json=payload,
auth=HTTPBasicAuth(self.config.user, self.config.password),
headers={"Content-Type": "application/json"},
timeout=min(self.config.timeout, 30),
)
response.raise_for_status()
data = response.json()
self._log_transaction(data)
reversal_response = data.get("SaleToPOIResponse", {}).get("ReversalResponse", {})
result = reversal_response.get("Response", {}).get("Result")
return BankTerminalResult(success=result in ("Success", None), raw=data, service_id=service_id)
except Exception as exc:
return BankTerminalResult(success=False, error=str(exc), service_id=service_id)
def extract_receipt_text(self, response: dict[str, Any]) -> str:
receipts = response.get("PaymentReceipt")
if receipts is None:
receipts = (
response.get("SaleToPOIResponse", {})
.get("PaymentResponse", {})
.get("PaymentReceipt", [])
)
lines: list[str] = []
for receipt in receipts:
content = receipt.get("OutputContent", {}).get("OutputText", [])
lines.extend(str(line.get("Text", "")) for line in content if line.get("Text"))
return "\n".join(lines)
class UnsupportedBankTerminalClient(BankTerminalClient):
def payment(self, amount: float) -> BankTerminalResult:
return BankTerminalResult(
success=False,
error=f"Terminal {self.config.terminal_type or '-'} zatial nie je implementovany.",
)
def create_bank_terminal_client(config: BankTerminalConfig) -> BankTerminalClient:
terminal_type = (config.terminal_type or "").strip().upper()
if terminal_type == "BESTERON":
return BesteronTerminalClient(config)
return UnsupportedBankTerminalClient(config)
+926
View File
@@ -0,0 +1,926 @@
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.core.window import Window
from kivy.effects.scroll import ScrollEffect
from kivy.uix.modalview import ModalView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.clock import Clock
# IMPORT TVÝCH DAT
from data import Cenik, CenPol, CenPolCreate, Cena, Position
# =========================================================
# DUMMY CENÍK
# =========================================================
def create_dummy_cenik():
return Cenik(cenpol=[
CenPol(
id=1,
id_card=100,
d_name="Pivo",
ch_name="Pivo",
pokl="A",
sklad="A",
ceny=[Cena(cena=50, mena="CZK", dan="21", name="standard",
cena2=25, cena3=17, cena4=12)],
pos_pc=[Position(page=1, line=0, col=0)],
),
CenPol(
id=2,
id_card=101,
d_name="Káva",
ch_name="Káva",
pokl="A",
sklad="A",
ceny=[Cena(cena=60, mena="CZK", dan="21", name="standard",
cena2=30, cena3=20, cena4=15)],
pos_pc=[Position(page=1, line=1, col=0)],
),
])
# =========================================================
# EDITOR SCREEN
# =========================================================
class WidthDialog(ModalView):
def __init__(self, on_ok, default=3, **kwargs):
super().__init__(**kwargs)
self.on_ok = on_ok
self.size_hint = (None, None)
self.size = (dp(300), dp(200))
self.auto_dismiss = False
root = BoxLayout(orientation="vertical", spacing=dp(10), padding=dp(10))
self.input = TextInput(
text=str(default),
multiline=False,
input_filter="int"
)
btn_ok = Button(text="OK")
btn_cancel = Button(text="Zrušit")
btn_ok.bind(on_press=self._ok)
btn_cancel.bind(on_press=lambda *_: self.dismiss())
root.add_widget(self.input)
root.add_widget(btn_ok)
root.add_widget(btn_cancel)
self.add_widget(root)
def _ok(self, *_):
try:
val = int(self.input.text)
if val < 1:
val = 1
except:
val = 3
if self.on_ok:
self.on_ok(val)
self.dismiss()
class CenikItemEditor(ModalView):
def __init__(self, pol, on_save, **kwargs):
super().__init__(**kwargs)
self.pol = pol
self.on_save_cb = on_save
self.size_hint = (None, None)
self.size = (dp(500), dp(600))
self.auto_dismiss = False
root = GridLayout(
cols=2,
spacing=dp(6),
padding=dp(10),
size_hint=(1, 1)
)
# ---------------- NÁZEV ----------------
root.add_widget(Label(text="Display name"))
self.d_name = TextInput(text=pol.d_name or "")
root.add_widget(self.d_name)
root.add_widget(Label(text="Účet name"))
self.ch_name = TextInput(text=pol.ch_name or "")
root.add_widget(self.ch_name)
# ---------------- CENA ----------------
cena = pol.ceny[0] if pol.ceny else None
root.add_widget(Label(text="Cena"))
self.cena = TextInput(text=str(cena.cena if cena else "0"))
root.add_widget(self.cena)
root.add_widget(Label(text="Měna"))
self.mena = TextInput(text=cena.mena if cena else "CZK")
root.add_widget(self.mena)
root.add_widget(Label(text="DPH"))
self.dan = TextInput(text=cena.dan if cena else "21")
root.add_widget(self.dan)
root.add_widget(Label(text="Cenová hladina"))
self.price_name = TextInput(text=cena.name if cena else "standard")
root.add_widget(self.price_name)
# ---------------- FRAKCE ----------------
root.add_widget(Label(text="1/2"))
self.cena2 = TextInput(text=str(cena.cena2 if cena else "0"))
root.add_widget(self.cena2)
root.add_widget(Label(text="1/3"))
self.cena3 = TextInput(text=str(cena.cena3 if cena else "0"))
root.add_widget(self.cena3)
root.add_widget(Label(text="1/4"))
self.cena4 = TextInput(text=str(cena.cena4 if cena else "0"))
root.add_widget(self.cena4)
# ---------------- BUTTONY ----------------
btn_save = Button(text="Uložit")
btn_cancel = Button(text="Zrušit")
btn_save.bind(on_press=self._save)
btn_cancel.bind(on_press=lambda *_: self.dismiss())
root.add_widget(btn_save)
root.add_widget(btn_cancel)
self.add_widget(root)
def _save(self, *_):
try:
cena = Cena(
cena=float(self.cena.text),
mena=self.mena.text,
dan=self.dan.text,
name=self.price_name.text,
cena2=float(self.cena2.text),
cena3=float(self.cena3.text),
cena4=float(self.cena4.text),
)
self.pol.d_name = self.d_name.text
self.pol.ch_name = self.ch_name.text
self.pol.ceny = [cena]
if self.on_save_cb:
self.on_save_cb(self.pol)
self.dismiss()
except Exception as e:
print("chyba při ukládání:", e)
class CenikButton(Button):
def __init__(self, pol=None, on_select=None, on_long_press=None, **kwargs):
price_updater = kwargs.pop("price_updater", None)
super().__init__(**kwargs)
self.pol = pol
self.on_select_cb = on_select
self.on_long_press_cb = on_long_press
self.price_updater = price_updater
self._lp_event = None
self._long_pressed = False
Clock.schedule_once(lambda *_: self.refresh(), 0)
def refresh(self):
if self.pol and self.price_updater:
self.price_updater(self)
# 🔥 KLÍČOVÁ OPRAVA
def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
return False
self._long_pressed = False
self._lp_event = Clock.schedule_once(self._trigger_long_press, 0.5)
return True # ❗ NEVOLAT super()
def on_touch_up(self, touch):
if not self.collide_point(*touch.pos):
return False
if self._lp_event:
self._lp_event.cancel()
self._lp_event = None
if not self._long_pressed:
if self.on_select_cb and self.pol:
self.on_select_cb(self.pol)
return True # ❗ NEVOLAT super()
def _trigger_long_press(self, *_):
self._long_pressed = True
if self.on_long_press_cb and self.pol:
self.on_long_press_cb(self.pol)
class CenikEditor(Screen):
def __init__(self, cenik=None, menu_cols=10, menu_rows=8, **kwargs):
cenik = kwargs.pop("cenik", cenik)
super().__init__(**kwargs)
# ================= DATA =================
self.cenik = cenik
self.cols = menu_cols
self.rows = menu_rows
# ================= STAV =================
self.current_page = 1
levels = self.cenik.used_price_levels() if self.cenik else []
self.price_level = levels[0] if levels else "standard"
self._selected_pol = None
self._move_mode = False
self.cell_w = dp(80)
self.cell_h = dp(80)
# ================= ROOT =================
root = BoxLayout(
orientation="vertical",
spacing=dp(6),
padding=dp(6),
size_hint=(1, 1),
)
# ================= SCROLL + GRID =================
self.scroll = ScrollView(
do_scroll_x=True,
do_scroll_y=True,
size_hint=(1, 1),
bar_width=dp(6),
effect_cls=ScrollEffect,
)
self.grid = FloatLayout(size_hint=(None, None))
self.grid.size = (
self.cols * self.cell_w,
self.rows * self.cell_h
)
self.scroll.add_widget(self.grid)
root.add_widget(self.scroll)
# ================= SPODNÍ PANEL =================
bottom = BoxLayout(
size_hint=(1, None),
height=dp(90),
spacing=dp(6),
padding=dp(6)
)
# --- tlačítka ---
self.btn_new = Button(text="Nový\nbutton")
self.btn_page = Button(text="Nová\nstránka")
self.btn_switch = Button(text=f"Stránka\n{self.current_page}")
self.btn_save = Button(text="Uložit")
self.btn_delete = Button(text="Smazat")
self.btn_move = Button(text="Pozice")
btn_levels = Button(text="Cenové\nhladiny")
btn_price = Button(text=f"Cena\n{self.price_level}")
# --- bindy ---
self.btn_new.bind(on_press=self._new_button)
self.btn_page.bind(on_press=self._new_page)
self.btn_switch.bind(on_press=self._switch_page)
self.btn_save.bind(on_press=self._save)
self.btn_delete.bind(on_press=self._delete_item)
self.btn_move.bind(on_press=self._start_move)
btn_levels.bind(on_press=self._edit_price_levels)
btn_price.bind(on_press=self.on_select_price_level)
# --- ulož reference ---
self.btn_price = btn_price
self._btn_move_color = self.btn_move.background_color
# --- add ---
for w in [
self.btn_new,
self.btn_page,
self.btn_switch,
self.btn_save,
self.btn_delete,
self.btn_move,
btn_levels,
btn_price,
]:
bottom.add_widget(w)
root.add_widget(bottom)
# ================= ADD ROOT =================
self.add_widget(root)
# ================= INIT BUILD =================
Clock.schedule_once(lambda *_: self._build(), 0)
# scroll nahoru (jako POS)
Clock.schedule_once(lambda *_: setattr(self.scroll, "scroll_y", 1), 0)
def _open_item_editor(self, pol):
modal = ModalView(size_hint=(0.6, 0.9))
root = BoxLayout(
orientation="vertical",
spacing=dp(6),
padding=dp(10),
)
# ================= NÁZEV =================
ti_name = TextInput(
text=pol.d_name,
multiline=False,
size_hint=(1, None),
height=dp(50)
)
root.add_widget(ti_name)
# ================= BARVA =================
btn_color = Button(
text=f"Barva: {pol.color}",
size_hint=(1, None),
height=dp(50)
)
def change_color(*_):
pol.color = (pol.color + 1) % 6
btn_color.text = f"Barva: {pol.color}"
btn_color.bind(on_press=change_color)
root.add_widget(btn_color)
# ================= ŠÍŘKA =================
pos = next((p for p in pol.pos_pc if p.page == self.current_page), None)
ti_width = TextInput(
text=str(pos.sirka if pos else 1),
multiline=False,
size_hint=(1, None),
height=dp(50)
)
root.add_widget(ti_width)
# ================= CENY =================
levels = self.cenik.used_price_levels()
ceny_map = {c.name: c for c in pol.ceny}
grid = GridLayout(
cols=2,
size_hint=(1, None),
spacing=dp(4)
)
grid.bind(minimum_height=grid.setter("height"))
inputs = {}
for lvl in levels:
grid.add_widget(Button(text=lvl, size_hint=(1, None), height=dp(40)))
cena = ceny_map.get(lvl)
ti = TextInput(
text=str(cena.cena if cena else ""),
multiline=False,
size_hint=(1, None),
height=dp(40),
background_color=(0.2, 0.7, 0.2, 1) if cena else (0.5, 0.5, 0.5, 1)
)
inputs[lvl] = ti
grid.add_widget(ti)
root.add_widget(grid)
# ================= ULOŽIT =================
def save(*_):
# název
pol.d_name = ti_name.text
# šířka
try:
w = max(1, int(ti_width.text))
except:
w = 1
for p in pol.pos_pc:
if p.page == self.current_page:
p.sirka = w
# ceny
new_ceny = []
for lvl, ti in inputs.items():
txt = ti.text.strip()
if not txt:
continue
try:
val = float(txt)
except:
val = 0
new_ceny.append(Cena(
name=lvl,
cena=val,
mena="Kc",
dan="21",
cena2=0,
cena3=0,
cena4=0
))
pol.ceny = new_ceny
modal.dismiss()
self._build()
btn_save = Button(text="Uložit", size_hint=(1, None), height=dp(60))
btn_save.bind(on_press=save)
root.add_widget(btn_save)
modal.add_widget(root)
modal.open()
def _edit_price(self, pol, level):
root = BoxLayout(orientation="vertical", spacing=dp(6), padding=dp(10))
# najdi cenu
cena = next((c for c in pol.ceny if c.name == level), None)
ti = TextInput(
text=str(cena.cena if cena else 0),
multiline=False
)
root.add_widget(ti)
def save(*_):
try:
val = float(ti.text)
except:
val = 0
if cena:
cena.cena = val
else:
pol.ceny.append(Cena(
name=level,
cena=val,
mena="Kc",
dan="21",
cena2=0,
cena3=0,
cena4=0
))
modal.dismiss()
self._build()
btn_save = Button(text="Uložit")
btn_save.bind(on_press=save)
root.add_widget(btn_save)
modal = ModalView(size_hint=(0.4, 0.3))
modal.add_widget(root)
modal.open()
"""
def _open_price_menu(self, pol):
levels = self.cenik.used_price_levels()
root = BoxLayout(
orientation="vertical",
spacing=dp(6),
padding=dp(10),
)
# ===== HLAVIČKA =====
root.add_widget(Button(
text=pol.d_name,
size_hint=(1, None),
height=dp(50),
disabled=True
))
# ===== MAPA EXISTUJÍCÍCH CEN =====
existing = {c.name: c for c in pol.ceny}
# ===== TLAČÍTKA HLADIN =====
for lvl in levels:
cena = existing.get(lvl)
has_price = cena and cena.cena > 0
btn = Button(
text=lvl,
size_hint=(1, None),
height=dp(50),
background_color=(0.2, 0.7, 0.2, 1) if has_price else (0.5, 0.5, 0.5, 1)
)
btn.bind(on_press=lambda b, lvl=lvl: self._edit_price(pol, lvl))
root.add_widget(btn)
modal = ModalView(size_hint=(0.5, 0.7))
modal.add_widget(root)
modal.open()
"""
def on_select_price_level(self, *_):
levels = self.cenik.used_price_levels()
if not levels:
return
try:
idx = levels.index(self.price_level)
except ValueError:
idx = 0
self.price_level = levels[(idx + 1) % len(levels)]
print("price_level:", self.price_level)
# update tlačítka
if hasattr(self, "btn_price"):
self.btn_price.text = f"Cena\n{self.price_level}"
self._build()
def _select_price_level(self, *_):
levels = self.cenik.used_price_levels()
if not levels:
return
# jednoduchý cyklus
idx = levels.index(self.price_level)
self.price_level = levels[(idx + 1) % len(levels)]
print("price_level:", self.price_level)
self._build()
def _edit_price_levels(self, *_):
print("OPEN LEVEL EDITOR") # 👈 debug
levels = list(self.cenik.used_price_levels())
root = BoxLayout(
orientation="vertical",
spacing=dp(6),
padding=dp(10),
)
inputs = []
for lvl in levels:
ti = TextInput(text=lvl, multiline=False)
inputs.append(ti)
root.add_widget(ti)
def add_level(*_):
ti = TextInput(text="", multiline=False)
inputs.append(ti)
root.add_widget(ti)
btn_add = Button(text="Přidat")
btn_add.bind(on_press=add_level)
root.add_widget(btn_add)
def save(*_):
new_levels = [ti.text.strip() for ti in inputs if ti.text.strip()]
if not new_levels:
print("prázdné hladiny ignoruji")
return
self._apply_price_levels(new_levels)
modal.dismiss()
btn_save = Button(text="Uložit")
btn_save.bind(on_press=save)
root.add_widget(btn_save)
modal = ModalView(size_hint=(0.6, 0.8))
modal.add_widget(root)
modal.open()
def _apply_price_levels(self, new_levels):
for pol in self.cenik.cenpol:
existing = {c.name: c for c in pol.ceny}
new_ceny = []
for lvl in new_levels:
if lvl in existing:
new_ceny.append(existing[lvl])
else:
# nová hladina → default cena
new_ceny.append(
Cena(
name=lvl,
cena=0.0,
mena="Kc",
dan="21",
cena2=0.0,
cena3=0.0,
cena4=0.0,
)
)
pol.ceny = new_ceny
# reset aktuální hladiny pokud zmizela
if self.price_level not in new_levels:
self.price_level = new_levels[0]
print("nové hladiny:", new_levels)
self._build()
# -----------------------------------------------------
def _move_selected_to(self, col, row):
if not self._selected_pol:
return
old_pos = None
for p in self._selected_pol.pos_pc:
if p.page == self.current_page:
old_pos = p
break
if old_pos is None:
sirka = 3
color = 0
else:
sirka = max(1, getattr(old_pos, "sirka", 1))
color = getattr(old_pos, "color", 0)
# ochrana proti přetečení doprava
if col + sirka > self.cols:
col = max(0, self.cols - sirka)
new_pos = Position(
page=self.current_page,
col=col,
line=row,
sirka=sirka,
color=color,
)
replaced = False
new_list = []
for p in self._selected_pol.pos_pc:
if p.page == self.current_page and not replaced:
new_list.append(new_pos)
replaced = True
else:
new_list.append(p)
if not replaced:
new_list.append(new_pos)
self._selected_pol.pos_pc = new_list
self._build()
def _select_item(self, pol):
self._selected_pol = pol
self._build()
def _find_free(self, sirka):
occupied = set()
# obsazené buňky (rozšířené o šířku)
for pol in self.cenik.cenpol:
for pos in pol.positions:
if pos.page != self.current_page:
continue
w = max(1, getattr(pos, "sirka", 1))
for dx in range(w):
occupied.add((pos.col + dx, pos.line))
# hledání místa
for row in range(self.rows):
for col in range(self.cols):
# nevejde se do gridu?
if col + sirka > self.cols:
continue
# kontrola kolize
collision = False
for dx in range(sirka):
if (col + dx, row) in occupied:
collision = True
break
if not collision:
return col, row
return None
def _new_button(self, *_):
dlg = WidthDialog(
on_ok=self._create_button_with_width,
default=3
)
dlg.open()
def _create_button_with_width(self, sirka):
free = self._find_free(sirka)
if not free:
print("Není místo pro button této šířky")
return
col, row = free
self._create_item(col, row, sirka)
def _new_page(self, *_):
max_page = 1
for pol in self.cenik.cenpol:
for pos in pol.positions:
max_page = max(max_page, pos.page)
self.current_page = max_page + 1
print("nová stránka:", self.current_page)
self._build()
def _switch_page(self, *_):
max_page = 1
for pol in self.cenik.cenpol:
for pos in pol.positions:
max_page = max(max_page, pos.page)
self.current_page += 1
if self.current_page > max_page:
self.current_page = 1
print("stránka:", self.current_page)
self._build()
def _save(self, *_):
print("UKLÁDÁM")
self.cenik.prn()
def _pos_to_xy(self, col, row):
x = col * self.cell_w
y = self.grid.height - (row + 1) * self.cell_h
return x, y
# -----------------------------------------------------
def _build(self):
self.grid.clear_widgets()
for pol in self.cenik.cenpol:
for pos in pol.positions:
if pos.page != self.current_page:
continue
x, y = self._pos_to_xy(pos.col, pos.line)
width_cells = max(1, getattr(pos, "sirka", 1))
btn = CenikButton(
pol=pol,
on_select=self._select_item,
on_long_press=self._open_item_editor,
price_updater=self._update_button_price,
text=pol.d_name,
size=(self.cell_w * width_cells, self.cell_h),
size_hint=(None, None),
pos=(x, y),
)
# zvýraznění výběru
if pol == self._selected_pol:
btn.background_color = (1, 0.5, 0.3, 1)
else:
btn.background_color = (1, 1, 1, 1)
self.grid.add_widget(btn)
def _update_button_price(self, btn):
pol = getattr(btn, "pol", None)
if not pol:
btn.text = ""
return
# najdi cenu podle hladiny
cena = None
for c in pol.ceny:
if c.name == self.price_level:
cena = c
break
if not cena and pol.ceny:
cena = pol.ceny[0]
if not cena:
btn.text = pol.d_name
return
# formát jako v POS
btn.text = f"{pol.d_name}\n{cena.cena:.2f} {cena.mena}"
# -----------------------------------------------------
def _find_item(self, col, row):
for pol in self.cenik.cenpol:
for pos in pol.positions:
if (
pos.page == self.current_page and
pos.col == col and
pos.line == row
):
return pol
return None
# -----------------------------------------------------
def _create_item(self, col, row, sirka=3):
import time
# ořez na grid
if col + sirka > self.cols:
sirka = self.cols - col
# fallback kdyby něco selhalo
if sirka < 1:
sirka = 1
new_id = int(time.time() * 1000)
pol = CenPolCreate(
id_card=new_id,
kod=new_id,
d_name=f"Item {len(self.cenik.cenpol) + 1}",
ch_name=f"Item {len(self.cenik.cenpol) + 1}",
pokl="A",
sklad="A",
ceny=[
Cena(
cena=100,
mena="CZK",
dan="21",
name="standard",
cena2=50,
cena3=33,
cena4=25,
)
],
pos_pc=[
Position(
page=self.current_page,
col=col,
line=row,
sirka=sirka,
)
],
)
# Převedení na CenPol (DB model)
self.cenik.cenpol.append(CenPol(**pol.model_dump()))
print(f"Přidán button: {pol.d_name} @ ({col},{row}) w={sirka}")
self._build()
# -----------------------------------------------------
"""
def _edit_item(self, pol):
self._selected_pol = pol
dlg = CenikItemEditor(
pol=pol,
on_save=self._on_item_saved
)
dlg.open()
self._build()
"""
def _on_item_saved(self, pol):
print("Uložen:", pol.d_name)
self._build()
def _delete_item(self, *_):
if not self._selected_pol:
print("Není vybraná položka")
return
print("Mažu:", self._selected_pol.d_name)
self.cenik.cenpol = [
p for p in self.cenik.cenpol
if p != self._selected_pol
]
self._selected_pol = None
self._build()
def _delete_item(self, *_):
if not self._selected_pol:
print("není vybraná položka")
return
print("Mažu:", self._selected_pol.d_name)
self.cenik.cenpol = [
p for p in self.cenik.cenpol
if p != self._selected_pol
]
self._selected_pol = None
self._build()
def _start_move(self, *_):
if not self._selected_pol:
print("Není vybraná položka")
return
print("Klikni do gridu pro novou pozici")
self._move_mode = True
# zvýraznění tlačítka
self.btn_move.background_color = (1, 0.4, 0.2, 1)
self.btn_move.text = "Klikni\ndo gridu"
def _can_place(self, col, row, sirka):
# mimo grid
if col < 0 or row < 0 or col + sirka > self.cols or row >= self.rows:
return False
occupied = set()
for pol in self.cenik.cenpol:
if pol == self._selected_pol:
continue # ignorujeme přesouvaný button
for pos in pol.positions:
if pos.page != self.current_page:
continue
w = max(1, getattr(pos, "sirka", 1))
for dx in range(w):
occupied.add((pos.col + dx, pos.line))
# kontrola kolize
for dx in range(sirka):
if (col + dx, row) in occupied:
return False
return True
def set_cenik(self, cenik):
self.cenik = cenik
levels = self.cenik.used_price_levels()
self.price_level = levels[0] if levels else "standard"
from kivy.clock import Clock
Clock.schedule_once(lambda *_: self._build(), 0)
def on_touch_down(self, touch):
# ================= MOVE REŽIM =================
if self._move_mode:
# není vybraná položka
if not self._selected_pol:
print("žádná vybraná položka")
self._move_mode = False
return super().on_touch_down(touch)
# klik mimo grid = zrušit move
if not self.grid.collide_point(*touch.pos):
self._move_mode = False
self.btn_move.background_color = self._btn_move_color
self.btn_move.text = "Pozice"
return super().on_touch_down(touch)
# ================= ZACHOVÁNÍ ŠÍŘKY =================
old_pos = None
for p in self._selected_pol.pos_pc:
if p.page == self.current_page:
old_pos = p
break
sirka = max(1, getattr(old_pos, "sirka", 3)) if old_pos else 3
color = getattr(old_pos, "color", 0) if old_pos else 0
# ================= SOUŘADNICE =================
lx = touch.x - self.grid.x
ly = touch.y - self.grid.y
col = int(lx // self.cell_w)
row = int((self.grid.height - ly) // self.cell_h)
print(f"move na: {col},{row} (w={sirka})")
# ================= OCHRANA GRID =================
if col + sirka > self.cols:
col = max(0, self.cols - sirka)
if col < 0 or row < 0 or row >= self.rows:
print("mimo grid")
return True
# ================= KOLIZE =================
if not self._can_place(col, row, sirka):
print("kolize, přesun zrušen")
return True
# ================= ULOŽENÍ =================
replaced = False
new_list = []
for p in self._selected_pol.pos_pc:
if p.page == self.current_page and not replaced:
new_list.append(Position(
page=self.current_page,
col=col,
line=row,
sirka=sirka,
color=color,
))
replaced = True
else:
new_list.append(p)
if not replaced:
new_list.append(Position(
page=self.current_page,
col=col,
line=row,
sirka=sirka,
color=color,
))
self._selected_pol.pos_pc = new_list
# ================= RESET =================
self._move_mode = False
self.btn_move.background_color = self._btn_move_color
self.btn_move.text = "Pozice"
self._build()
return True
# ================= DEFAULT =================
return super().on_touch_down(touch)
# =========================================================
# APP
# =========================================================
class CenikEditorApp(App):
def build(self):
cenik = create_dummy_cenik()
return CenikEditor(cenik=cenik)
# =========================================================
if __name__ == "__main__":
Window.size = (1000, 700)
Window.minimum_width = 800
Window.minimum_height = 600
CenikEditorApp().run()
+254
View File
@@ -0,0 +1,254 @@
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.metrics import dp
from kivy.clock import Clock
from kivy.effects.scroll import ScrollEffect
from kivy.logger import Logger
from ui_utils import ucet_ui_type, UCET_COLORS
import math
import data
import numberpad
from accountselect_base import BaseAccountSelectScreen
from ui_utils import _popup_info
COLS = 7
BTN_W = dp(150)
BTN_H = dp(120)
SP = dp(10)
PAD = dp(10)
class ClosedAccountSelectScreen(BaseAccountSelectScreen):
def __init__(
self,
*,
controller,
operation: str,
get_ucty,
on_done,
on_cancel,
allow_manual: bool = True,
**kwargs,):
super().__init__(**kwargs)
self.controller = controller
self.operation = operation
self.get_ucty = get_ucty
self.on_done = on_done
self.on_cancel = on_cancel
self.allow_manual = allow_manual
self._all_ucty: list[data.UcetSelect] = []
root = BoxLayout(orientation="vertical", spacing=dp(10), padding=dp(10))
self.search_input = TextInput(
hint_text="HLEDAT",
multiline=False,
size_hint_y=None,
height=dp(52),
font_size=dp(20),
padding=(dp(12), dp(12), dp(12), dp(8)),
)
self.search_input.bind(text=self._on_search_text)
root.add_widget(self.search_input)
self.scroll = ScrollView(
size_hint=(1, 1),
do_scroll_x=True,
do_scroll_y=True,
bar_width=dp(14),
scroll_type=["bars", "content"],
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)
root.add_widget(self.scroll)
bottom = BoxLayout(size_hint_y=None, height=dp(70), spacing=dp(10))
if self.allow_manual:
bottom.add_widget(Button(
text="ZADAT ÚČET",
on_press=self._manual, # <- musí existovat
))
bottom.add_widget(Button(
text="ZPĚT",
on_press=lambda *_: self.on_cancel(),
))
root.add_widget(bottom)
self.add_widget(root)
def _manual(self, *_):
import numberpad
from kivy.uix.popup import Popup
pad = numberpad.NumberPad(
mode="number",
max_len=10,
allow_fraction=False,
on_accept=self._manual_accept,
on_cancel=self._manual_cancel,
)
self._manual_popup = Popup(
title="Zadat číslo účtu",
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
value = (value or "").strip()
if not value:
return
self._select(value)
def _manual_cancel(self):
if getattr(self, "_manual_popup", None):
self._manual_popup.dismiss()
self._manual_popup = None
def on_enter(self):
self.refresh()
def _select(self, ucislo: int):
Logger.info(f"ClosedSelect: picked ucislo={ucislo} op={self.operation}")
self.on_done(self.operation, ucislo)
def _update_scroll(self, *_):
if not self.scroll or not self.grid:
return
count = len(self.grid.children)
if count == 0:
self.scroll.do_scroll_x = False
self.scroll.do_scroll_y = False
return
rows = math.ceil(count / COLS)
content_h = rows * BTN_H + (rows - 1) * SP + 2 * PAD
self.scroll.do_scroll_y = content_h > self.scroll.height
def _create_ucet_button(self, u: data.UcetSelect):
ui_type = ucet_ui_type(u)
color = UCET_COLORS.get(ui_type, (0.3, 0.3, 0.3, 1))
txt = self._format_closed_ucet(u) or ""
if not txt.strip():
txt = f"ID {getattr(u, 'id', '')}"
btn = Button(
text=txt,
size_hint=(None, None),
background_color=color,
width=BTN_W,
height=dp(130), # lehce vyšší pro 45 řádků
font_size=dp(12), # trochu menší
halign="center",
valign="top", )
btn.bind(
size=lambda b, *_: setattr(
b, "text_size", (b.width - dp(10), None)
))
ucislo = getattr(u, "ucislo", None)
if ucislo is None:
btn.disabled = True
btn.opacity = 0.4
return btn
btn.ucislo = ucislo
btn.bind(on_press=lambda b: self._select(b.ucislo))
return btn
def _format_closed_ucet(self, u):
lines = []
# --- měna (fallback) potreba najit alesponmenu s s kurzem 1!!!
unit = (
getattr(u, "unit", None)
or getattr(u, "mena", None)
or getattr(u, "currency", None)
or ""
)
# 1) origin + stůl
txt = ""
if getattr(u, "origin", None):
txt += f"{u.origin}"
if getattr(u, "stul", None):
if txt:
txt += " "
txt += f"stůl {u.stul}"
if txt:
lines.append(txt)
# 2) číslo účtu + čas uzavření
txt = ""
if getattr(u, "ucislo", None):
txt += f"{u.ucislo}"
if getattr(u, "autor", None):
if txt:
txt += f" | by {u.autor}"
if txt:
lines.append(txt)
txt = ""
if getattr(u, "closed_at", None): #zatim nech
if txt:
txt += " | "
txt += f"{u.closed_at}"
if txt:
lines.append(txt)
# 3) suma
if getattr(u,"total_base_currency", None):
lines.append(f"{u.total_base_currency:,.2f} {unit}")
if getattr(u, "is_storno", None):
lines.append(f"Puvodni ucet {u.is_storno}")
return "\n".join(lines)
def _matches_filter(self, u: data.UcetSelect, query: str) -> bool:
if not query:
return True
fields = [
getattr(u, "ucislo", ""),
getattr(u, "stul", ""),
getattr(u, "closed_at", ""),
getattr(u, "autor", ""),
getattr(u, "origin", ""),
f"{getattr(u, 'total_base_currency', 0) or 0:.2f}",
]
text = " ".join(str(v or "") for v in fields).lower()
return query.lower() in text
def _on_search_text(self, *_):
self._render_ucty()
def _render_ucty(self):
if not getattr(self, "grid", None):
return
self.grid.clear_widgets()
query = (self.search_input.text or "").strip()
ucty = [u for u in self._all_ucty if self._matches_filter(u, query)]
# seřadit podle čísla účtu vzestupně
ucty_sorted = sorted(
ucty,
key=lambda u: u.ucislo or "", reverse=True )
for u in ucty_sorted:
btn = self._create_ucet_button(u)
if btn:
self.grid.add_widget(btn)
Clock.schedule_once(self._update_scroll, 0)
def _load_ucty(self, ucty: list[data.UcetSelect]):
self._all_ucty = list(ucty or [])
self._render_ucty()
def refresh(self):
ucty = self.get_ucty() or []
self._load_ucty(ucty)
def load_closed_ucty(self):
return [u for u in self.load_stoly() if u.closed]
+647
View File
@@ -0,0 +1,647 @@
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)
+232
View File
@@ -0,0 +1,232 @@
import json
from collections import defaultdict
import sqlite3
import json
from collections import defaultdict
from data import (
ClosureInterval,
ClosureSummary,
ClosureUser,
ClosureVAT,
ClosureReportOut,
)
def compute_closure_report(conn,
table_ucty: str,
table_clsrep: str | None=None,
ucislo_st: str | None=None,
ucislo_end: str | None=None,
id_kas: str="") -> ClosureReportOut | None:
if not table_clsrep:
table_clsrep = table_ucty.replace("_ucty", "_clsrep")
cur = conn.cursor()
# =====================================================
# 1️⃣ URČENÍ STARTU
# =====================================================
if not ucislo_st:
# zjisti poslední uzávěrku
cur.execute(f"""
SELECT ucislo_end
FROM "{table_clsrep}"
WHERE id_kas=?
ORDER BY clsrep_id DESC
LIMIT 1
""", (id_kas,))
row = cur.fetchone()
if row and row[0]:
# další účet po poslední uzávěrce
ucislo_st = row[0]
else:
# žádná uzávěrka → vezmi první existující účet
cur.execute(f"""
SELECT MIN(ucislo)
FROM "{table_ucty}"
WHERE id_kas=?
AND closed_at IS NOT NULL
AND TRIM(ucislo) != ''
""", (id_kas,))
row = cur.fetchone()
ucislo_st = row[0] if row else None
# =====================================================
# 2️⃣ URČENÍ KONCE
# =====================================================
if not ucislo_end:
cur.execute(f"""
SELECT MAX(ucislo)
FROM "{table_ucty}"
WHERE id_kas=?
AND closed_at IS NOT NULL
AND TRIM(ucislo) != ''
""", (id_kas,))
row = cur.fetchone()
ucislo_end = row[0] if row else None
if not ucislo_st or not ucislo_end:
return None
# =====================================================
# 3️⃣ NAČTENÍ ÚČTŮ
# =====================================================
cur.execute(f"""
SELECT ucislo, closed_at, data
FROM "{table_ucty}"
WHERE id_kas=?
AND closed_at IS NOT NULL
AND TRIM(ucislo) != ''
AND ucislo>=?
AND ucislo<=?
ORDER BY ucislo
""", (id_kas, ucislo_st, ucislo_end))
rows = cur.fetchall()
if not rows:
return None
# --- interval ---
ucislo_first, closed_first, _ = rows[0]
ucislo_last, closed_last, _ = rows[-1]
# =====================================================
# 4️⃣ AGREGACE (stejná jako předtím)
# =====================================================
by_payment = defaultdict(float)
by_user_total = defaultdict(float)
by_user_cash = defaultdict(float)
by_vat = defaultdict(lambda: {"zaklad": 0.0, "dan": 0.0})
total_base = 0.0
total_payments = 0.0
count = 0
for ucislo, closed_at, data_json in rows:
if not data_json:
continue
try:
u = json.loads(data_json)
except Exception:
continue
if not u.get("ucislo"):
continue
count += 1
autor = u.get("autor") or "UNKNOWN"
base_val = u.get("total_base_currency") or 0.0
total_base += base_val
by_user_total[autor] += base_val
for p in u.get("platby", []) or []:
code = p.get("code") or "UNKNOWN"
suma = p.get("suma_czk") or 0.0
by_payment[code] += suma
total_payments += suma
if code == "CASH":
by_user_cash[autor] += suma
for d in u.get("dane", []) or []:
rate = str(d.get("rate"))
zaklad = d.get("zaklad") or 0.0
try:
r = float(rate)
dan = zaklad * (r - 1)
except Exception:
dan = 0.0
by_vat[rate]["zaklad"] += zaklad
by_vat[rate]["dan"] += dan
# =====================================================
# 5️⃣ SLOŽENÍ MODELU
# =====================================================
interval = ClosureInterval(
ucislo_od=ucislo_first,
ucislo_do=ucislo_last,
closed_at_od=closed_first,
closed_at_do=closed_last,
)
summary = ClosureSummary(
pocet_uctu=count,
total_base_currency=round(total_base, 2),
total_payments=round(total_payments, 2),
difference=round(total_base - total_payments, 2),
)
users = {
user: ClosureUser(
total_base_currency=round(by_user_total[user], 2),
hotovost=round(by_user_cash[user], 2),
)
for user in by_user_total
}
vat = {
rate: ClosureVAT(
zaklad=round(v["zaklad"], 2),
dan=round(v["dan"], 2),
celkem=round(v["zaklad"] + v["dan"], 2),
)
for rate, v in by_vat.items()
}
return ClosureReportOut(
blocked_by="",
clsrep_no=None,
created_at=None,
interval=interval,
summary=summary,
platby={k: round(v, 2) for k, v in by_payment.items()},
uzivatele=users,
dph=vat,
)
import sqlite3
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
import kivy_printer
class TestApp(App):
def build(self):
with sqlite3.connect("testVIII.db") as conn:
rep = compute_closure_report(
conn,
table_ucty="00001_ucty",
#table_clsrep="00001_clsrep",
#ucislo_st="01000435",
#ucislo_end="01000500",
id_kas="01"
)
if rep:
print(rep.model_dump_json(indent=4, ensure_ascii=False))
kivy_printer.show_clsrep_preview(rep.model_dump())
else:
print("Uzávěrka je prázdná.")
return BoxLayout() # dummy root
if __name__ == "__main__":
TestApp().run()
+78
View File
@@ -0,0 +1,78 @@
from kivy.metrics import dp
from kivy.uix.modalview import ModalView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class ClosureSelectDialog(ModalView):
def __init__(self, closures, on_select, title="Vyber uzávěrku", **kwargs):
super().__init__(**kwargs)
self.size_hint = (None, None)
self.size = (dp(700), dp(700))
self.auto_dismiss = True
self.closures = closures or []
self.on_select = on_select
scroll = ScrollView(size_hint=(1, 1))
content = GridLayout(
cols=1,
spacing=dp(10),
padding=dp(12),
size_hint_y=None,
)
content.bind(minimum_height=content.setter("height"))
lbl_title = Label(
text=title,
size_hint_y=None,
height=dp(40),
)
content.add_widget(lbl_title)
for c in self.closures:
txt = self._closure_text(c)
btn = Button(
text=txt,
markup=True,
size_hint_y=None,
height=dp(90),
halign="left",
valign="middle",
text_size=(dp(650), None),
)
btn.bind(
size=lambda inst, val: setattr(
inst, "text_size", (inst.width - dp(20), None)
)
)
btn.bind(
on_release=lambda inst, clsrep_no=c.clsrep_no: self._select(clsrep_no)
)
content.add_widget(btn)
btn_close = Button(
text="Zavřít",
size_hint_y=None,
height=dp(56),
)
btn_close.bind(on_release=self.dismiss)
content.add_widget(btn_close)
scroll.add_widget(content)
self.add_widget(scroll)
def _select(self, clsrep_no):
self.dismiss()
if self.on_select:
self.on_select(clsrep_no)
def _closure_text(self, c):
clsrep_no = getattr(c, "clsrep_no", "") or ""
ucislo_od = getattr(c, "ucislo_od", "") or ""
ucislo_do = getattr(c, "ucislo_do", "") or ""
closed_at_od = getattr(c, "closed_at_od", "") or ""
closed_at_do = getattr(c, "closed_at_do", "") or ""
return (
f"[b]Uzávěrka {clsrep_no}[/b]\n"
f"Účty: {ucislo_od} - {ucislo_do}\n"
f"Od: {closed_at_od}\n"
f"Do: {closed_at_do}"
)
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://127.0.0.1:8000",
"refresh_url": "http://127.0.0.1:8000/refresh/",
"client_id": "01",
"id_kas": "07",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://127.0.0.1:8000",
"refresh_url": "http://127.0.0.1:8000/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://192.168.0.145:8000",
"refresh_url": "http://192.168.0.145:8000/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://192.168.180.133:8000",
"refresh_url": "http://192.168.180.133:8000/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://192.168.180.133:8081",
"refresh_url": "http://192.168.180.133:8081/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+1686
View File
File diff suppressed because it is too large Load Diff
+1686
View File
File diff suppressed because it is too large Load Diff
+402
View File
@@ -0,0 +1,402 @@
from __future__ import annotations
import re
import time
import logging
from datetime import datetime
from decimal import Decimal, ROUND_HALF_UP
import data
import postgres_service
class FidelioDbError(Exception):
pass
logger = logging.getLogger(__name__)
def _s(value) -> str:
return "" if value is None else str(value).strip()
def _i(value, default: int = 0) -> int:
try:
return int(float(str(value).strip().strip("\"'")))
except Exception:
return default
def _money(value) -> Decimal:
try:
return Decimal(str(value).replace(",", ".")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
except Exception:
return Decimal("0.00")
def _quote_ident(name: str) -> str:
name = _s(name)
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name):
raise FidelioDbError(f"Neplatny PostgreSQL identifikator: {name}")
return f'"{name}"'
def _table(conn: data.PostgresConnection, table_name: str) -> str:
schema = _s(getattr(conn, "schema_", "")) or "food600"
return f"{_quote_ident(schema)}.{_quote_ident(table_name)}"
def _now_parts():
now = datetime.now()
return now, now.strftime("%y%m%d"), now.strftime("%H%M%S")
def _db_out_defaults() -> dict:
now, dat, cas = _now_parts()
payload = {
"typ": "",
"stav": "I",
"cis_kasy": "",
"outlet": "",
"porad_tiz": "",
"pokoj": "",
"datum": now.date(),
"sekund": Decimal("0.000"),
"dat": dat,
"cas": cas,
"ucet_kasa": "",
"serv_per": "",
"folio": "",
"gn": "",
"p_hostu": "",
"total": Decimal("0.00"),
"dysko": Decimal("0.00"),
"pm": "",
"ckar": "",
"track2": "",
"retrans": Decimal("0"),
}
for idx in range(1, 10):
payload[f"s{idx}"] = Decimal("0.00")
payload[f"d{idx}"] = Decimal("0.00")
payload[f"t{idx}"] = Decimal("0.00")
return payload
def _insert_db_out(pg, conn: data.PostgresConnection, payload: dict) -> int:
table = _table(conn, "db_out")
columns = [
"typ", "stav", "cis_kasy", "outlet", "porad_tiz", "pokoj",
"datum", "sekund", "dat", "cas", "ucet_kasa", "serv_per",
"folio", "gn", "p_hostu", "total",
"s1", "d1", "t1", "s2", "d2", "t2", "s3", "d3", "t3",
"s4", "d4", "t4", "s5", "d5", "t5", "s6", "d6", "t6",
"s7", "d7", "t7", "s8", "d8", "t8", "s9", "d9", "t9",
"dysko", "pm", "ckar", "track2", "retrans",
]
placeholders = ", ".join(["%s"] * len(columns))
column_sql = ", ".join(_quote_ident(col) for col in columns)
values = [payload.get(col) for col in columns]
cur = pg.cursor()
try:
cur.execute(
f"INSERT INTO {table} ({column_sql}) VALUES ({placeholders}) RETURNING postseqnr",
values,
)
row = cur.fetchone()
pg.commit()
finally:
cur.close()
return int(row[0])
def _mark_request_done(pg, conn: data.PostgresConnection, postseqnr: int, db_in_id: int | None = None) -> None:
cur = pg.cursor()
try:
cur.execute(
f"UPDATE {_table(conn, 'db_out')} SET stav=%s WHERE postseqnr=%s",
("O", postseqnr),
)
if db_in_id is not None:
cur.execute(
f"UPDATE {_table(conn, 'db_in')} SET stav=%s WHERE idriadok=%s",
("O", db_in_id),
)
pg.commit()
finally:
cur.close()
def _wait_for_answer(pg, conn: data.PostgresConnection, postseqnr: int, timeout_seconds: int = 30) -> dict | None:
table = _table(conn, "db_in")
columns = [
"idriadok", "odpoved", "kecy", "pokoj1",
"folio1", "gn1", "pokoj2", "folio2", "gn2",
"pokoj3", "folio3", "gn3", "pokoj4", "folio4", "gn4",
]
column_sql = ", ".join(_quote_ident(col) for col in columns)
deadline = time.time() + max(1, int(timeout_seconds or 30))
while time.time() < deadline:
cur = pg.cursor()
try:
cur.execute(
f"SELECT {column_sql} FROM {table} WHERE postseqnr=%s ORDER BY idriadok LIMIT 1",
(postseqnr,),
)
row = cur.fetchone()
finally:
cur.close()
if row:
return dict(zip(columns, row))
time.sleep(1)
return None
def _is_ok_answer(answer: dict) -> bool:
value = _s(answer.get("odpoved")).upper()
return value in {"", "OK", "UR"}
def _guest_from_answer(answer: dict, idx: int) -> data.HotelGuest | None:
folio = _s(answer.get(f"folio{idx}"))
if not folio:
return None
room_code = _s(answer.get(f"pokoj{idx}")) or _s(answer.get("pokoj1"))
return data.HotelGuest(
id=folio,
account_id=folio,
guest_name=_s(answer.get(f"gn{idx}")),
room_id=room_code,
room_code=room_code,
result=0,
)
def load_guests(
conn: data.PostgresConnection,
id_kas: str,
params: dict,
room_code: str = "",
track2: str = "",
timeout_seconds: int = 30,
) -> list[data.HotelGuest]:
width = _i((params or {}).get("fidelio_izba_znakov"), 3)
room_code = _s(room_code)
track2 = _s(track2)
if len(room_code) >= width:
width = len(room_code)
track2 = ""
elif not room_code:
width = 0
payload = _db_out_defaults()
payload.update({
"typ": "DOTAZ",
"cis_kasy": _s(id_kas),
"outlet": _s(id_kas),
"pokoj": room_code.rjust(width, "0"),
"track2": track2,
})
with postgres_service.connect(conn) as pg:
postseqnr = _insert_db_out(pg, conn, payload)
answer = _wait_for_answer(pg, conn, postseqnr, timeout_seconds=timeout_seconds)
if not answer:
_mark_request_done(pg, conn, postseqnr)
raise FidelioDbError("Opera neodpoveda.")
_mark_request_done(pg, conn, postseqnr, _i(answer.get("idriadok")))
if not _is_ok_answer(answer):
raise FidelioDbError(_s(answer.get("kecy")) or "Opera vratila chybu.")
guests = []
for idx in range(1, 5):
guest = _guest_from_answer(answer, idx)
if guest:
guests.append(guest)
return guests
def _normalize_mag_card(card_code: str) -> tuple[str, str]:
code = _s(card_code)
if code.startswith("%") and "?" in code:
code = code[1:code.find("?")]
elif code.startswith("5") and "<" in code:
code = code[1:code.find("<")]
elif code.startswith("_") and "<" in code:
code = code[1:code.find("<")]
else:
raise FidelioDbError("Neznamy format hotelovej karty.")
replacements = {
"+": "1",
"\u013e": "2",
"\u0161": "3",
"\u010d": "4",
"\u0165": "5",
"\u017e": "6",
"\u00fd": "7",
"\u00e1": "8",
"\u00ed": "9",
"\u00e9": "0",
"!": "1",
"@": "2",
"#": "3",
"$": "4",
"%": "5",
"^": "6",
"&": "7",
"*": "8",
"(": "9",
")": "0",
}
for src, dst in replacements.items():
code = code.replace(src, dst)
try:
room_code = str(int(code[30:34]))
except Exception as e:
raise FidelioDbError("Neznamy format hotelovej karty.") from e
try:
guest_id = str(int(code[10:20]))
except Exception:
guest_id = ""
try:
checkout = datetime.strptime(code[20:30], "%y%m%d%H%M")
except Exception as e:
raise FidelioDbError("Neznamy format hotelovej karty.") from e
if checkout < datetime.now():
raise FidelioDbError("Nacitana hotelova karta je neplatna.")
return room_code, guest_id
def _normalize_salto_card(card_code: str) -> str:
code = _s(card_code)
normalized = ""
while code:
if len(code) > 1:
normalized += code[-2:]
code = code[:-2]
else:
normalized += code
code = ""
return normalized.ljust(14, "0")
def check_card(
conn: data.PostgresConnection,
id_kas: str,
params: dict,
card_code: str,
timeout_seconds: int = 30,
) -> data.HotelCardResult:
params = params or {}
card_type = _s(params.get("hotel_karta_typ") or "SALTO").strip("\"'").upper()
length = _i(params.get("hotel_karta_length"), 14)
guest_id = ""
if card_type == "SALTO":
room_code = ""
track2 = _normalize_salto_card(card_code)
elif card_type == "MAG":
room_code, guest_id = _normalize_mag_card(card_code)
track2 = room_code
else:
room_code = ""
track2 = _s(card_code).ljust(length, "0") if length else _s(card_code)
guests = load_guests(
conn,
id_kas=id_kas,
params=params,
room_code=room_code,
track2=track2,
timeout_seconds=timeout_seconds,
)
if guest_id:
guests = [guest for guest in guests if _s(guest.id) == guest_id] or guests
if not guests:
raise FidelioDbError("Ku karte sa nenasiel ziadny host.")
guest = guests[0]
return data.HotelCardResult(
room_id=guest.room_id,
room_code=guest.room_code,
account_id=guest.account_id or guest.id,
guest_id=guest.id,
guest_name=guest.guest_name,
)
def _raster_index(value) -> int:
idx = _i(value, 1)
if idx < 1 or idx > 9:
return 1
return idx
def _charge_amounts(preparation: data.HotelChargePreparation) -> list[Decimal]:
amounts = [Decimal("0.00") for _ in range(9)]
for line in preparation.lines or []:
idx = _raster_index(line.raster_id)
amounts[idx - 1] += _money(line.amount)
return [amount.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) for amount in amounts]
def charge_account(
conn: data.PostgresConnection,
id_kas: str,
preparation: data.HotelChargePreparation,
timeout_seconds: int = 30,
) -> dict:
if not preparation or not preparation.ready:
raise FidelioDbError("Hotelovy ucet nie je pripraveny na odoslanie.")
target = preparation.target
if not target:
raise FidelioDbError("Hotelovy ucet nema ciel.")
amounts = _charge_amounts(preparation)
total = sum(amounts, Decimal("0.00")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
if not total:
raise FidelioDbError("Hotelovy ucet nema ziadnu sumu na odoslanie.")
payload = _db_out_defaults()
payload.update({
"typ": "ZATIZ",
"cis_kasy": _s(id_kas),
"outlet": _s(id_kas),
"pokoj": _s(target.room_code),
"serv_per": _s(target.time_attribute)[:1],
"gn": _s(target.guest_name),
"folio": _s(target.guest_id or target.account_id),
"ucet_kasa": _s(preparation.receipt_number)[-7:],
"total": total,
})
for idx, amount in enumerate(amounts, start=1):
payload[f"s{idx}"] = amount
with postgres_service.connect(conn) as pg:
postseqnr = _insert_db_out(pg, conn, payload)
logger.info(
"Fidelio ZATIZ inserted: postseqnr=%s id_kas=%s pokoj=%s folio=%s "
"ucet_kasa=%s total=%s amounts=%s",
postseqnr,
_s(id_kas),
payload["pokoj"],
payload["folio"],
payload["ucet_kasa"],
total,
[str(x) for x in amounts],
)
answer = _wait_for_answer(pg, conn, postseqnr, timeout_seconds=timeout_seconds)
if not answer:
_mark_request_done(pg, conn, postseqnr)
raise FidelioDbError("Opera neodpoveda.")
_mark_request_done(pg, conn, postseqnr, _i(answer.get("idriadok")))
if not _is_ok_answer(answer):
raise FidelioDbError(_s(answer.get("kecy")) or "Opera vratila chybu.")
return {
"ok": True,
"request_number": postseqnr,
"message": "OK",
}
+1120
View File
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
import json
from pathlib import Path
DEFAULT_LANG = "sk"
SUPPORTED_LANGS = {"sk", "cs", "en", "de", "hu", "pl", "it"}
def normalize_lang(lang: str | None) -> str:
value = str(lang or DEFAULT_LANG).strip().lower()
if value == "cz":
value = "cs"
return value if value in SUPPORTED_LANGS else DEFAULT_LANG
class Translator:
def __init__(self, lang: str = DEFAULT_LANG, locale_dir: str | Path | None = None):
self.locale_dir = Path(locale_dir) if locale_dir else Path(__file__).with_name("locales")
self.lang = DEFAULT_LANG
self.messages: dict[str, str] = {}
self.set_lang(lang)
def set_lang(self, lang: str | None):
self.lang = normalize_lang(lang)
fallback = self._load(DEFAULT_LANG)
current = self._load(self.lang) if self.lang != DEFAULT_LANG else {}
self.messages = {**fallback, **current}
def _load(self, lang: str) -> dict[str, str]:
path = self.locale_dir / f"{normalize_lang(lang)}.json"
if not path.exists():
return {}
try:
with path.open("r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"Invalid locale JSON : {e}")
return {}
return {str(k): str(v) for k, v in data.items()}
def tr(self, key: str, default: str | None = None, **kwargs) -> str:
text = self.messages.get(key, default if default is not None else key)
if kwargs:
try:
return text.format(**kwargs)
except Exception:
return text
return text
def available_locales(locale_dir: str | Path | None = None) -> list[str]:
base = Path(locale_dir) if locale_dir else Path(__file__).with_name("locales")
if not base.exists():
return []
return sorted(path.stem for path in base.glob("*.json"))
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
client
kivy grafika
pydantic kontrola typu (pro komunikaci server x client)
requests klientska kommunikace se serverem
qrcode generovani qr kodu pro placeni
pillow import a zobraceni obrazku (pro qr platbu)
server
uvicorn http server
fastapi --"--
+12
View File
@@ -0,0 +1,12 @@
client
kivy grafika
pydantic kontrola typu (pro komunikaci server x client)
requests klientska kommunikace se serverem
qrcode generovani qr kodu pro placeni
pillow import a zobraceni obrazku (pro qr platbu)
server
uvicorn http server
fastapi --"--
+35
View File
@@ -0,0 +1,35 @@
client
Python 3.11 nebo 3.12
po instalaci (napr. do c:\pokladna) v adresari pokladna vytvorit virtualni prostredi
pro w11 prikaz "py -m venv k3.12" nebo "python venv k3.12" (pred tim overit, pousti-li se opravdu python 3.11 resp.3.12)
potom z adresare pokladna "k3.12/script/activate" a prompt je pak
(k3.12) c:\pokladna
pak upgrade pip
pip install --upgrade pip setuptools wheel
a instalace kivy
pip install kivy[full]
a instalace zbytku
pip install pydantic, requests, qrcode, pillow
v config.json je nutne spravne vyplnit adresu a port serveru
a spusteni frontendu
python kivy_app.py
kivy grafika
pydantic kontrola typu (pro komunikaci server x client)
requests klientska kommunikace se serverem
qrcode generovani qr kodu pro placeni
pillow import a zobraceni obrazku (pro qr platbu)
server
uvicorn http server
fastapi --"--
psycopg
jinja2
+965
View File
@@ -0,0 +1,965 @@
2026-06-10 14:58:59,847 [INFO] kivy: Inicializace ApiController
2026-06-10 14:58:59,883 [INFO] kivy: APP on_start → startuji backend init
2026-06-10 14:58:59,884 [INFO] kivy: THREAD: start_app běží
2026-06-10 14:58:59,884 [INFO] kivy: Base: Start application main loop
2026-06-10 14:58:59,888 [INFO] kivy: GL: NPOT texture support is available
2026-06-10 14:58:59,930 [INFO] api_call:
Přihlášení úspěšné
token=6XDkFs4VibkMml9Z2f3tLfcg13X1tgQLVOuSTfg8icY
refresh_token=66Bmgd6dvixmm9259ePb251m4Tgsg1g448KLFkgVI5T2aAckXG4pUVY3TfWgoNJE
2026-06-10 14:59:00,038 [INFO] api_call: GET load_kasutxt_api: resp={'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 14:59:00,073 [INFO] kivy: CTRL: POS static maps built items=95, codes=96, search=64
2026-06-10 14:59:00,080 [INFO] api_call: Heartbeat thread started
2026-06-10 14:59:00,080 [INFO] kivy: Verze API 069_9_Kivy.API.hotel-target, verze frontend 069_8_Kivy.MB.hotel-restore
2026-06-10 14:59:00,081 [INFO] kivy: Aplikace připravena
2026-06-10 14:59:00,082 [INFO] kivy: THREAD RESULT = True
2026-06-10 14:59:00,089 [INFO] kivy: UI refresh po startu
2026-06-10 14:59:00,089 [INFO] kivy: UI ready → čekám na login
2026-06-10 14:59:00,101 [INFO] kivy: APP: show login user
2026-06-10 15:01:34,671 [INFO] kivy: NumberPad CANCEL
2026-06-10 15:01:34,672 [INFO] kivy: LOGIN CANCEL
2026-06-10 15:01:34,672 [INFO] kivy: Kivy on_stop called
2026-06-10 15:01:34,673 [INFO] kivy: Application cleanup started
2026-06-10 15:01:34,673 [INFO] api_call: Stopping heartbeat
2026-06-10 15:01:36,716 [INFO] kivy: Base: Leaving application in progress...
2026-06-10 15:01:36,717 [INFO] kivy: Kivy on_stop called
2026-06-10 15:01:39,706 [INFO] kivy: Inicializace ApiController
2026-06-10 15:01:39,736 [INFO] kivy: APP on_start → startuji backend init
2026-06-10 15:01:39,737 [INFO] kivy: THREAD: start_app běží
2026-06-10 15:01:39,737 [INFO] kivy: Base: Start application main loop
2026-06-10 15:01:39,740 [INFO] kivy: GL: NPOT texture support is available
2026-06-10 15:01:39,791 [INFO] api_call:
Přihlášení úspěšné
token=gJTKANAzXxJHMlutmEQLCxzNtnAD7dgi8FO_svgSg04
refresh_token=lk5sFhC9poeHPKKlowtp2Kmo5bX5oA9C9sN0nh9acDPUBy2jF_oJm_lORbVKe5j5
2026-06-10 15:01:39,890 [INFO] api_call: GET load_kasutxt_api: resp={'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 15:01:39,890 [WARNING] kivy: Hlavicky uctov sa podařilo načíst: userhead1=None userhead2=None userhead3=None userhead4=None userhead5=None userhead6=None userhead7=None userhead8=None userhead9=None usertail1=None usertail2=None usertail3=None usertail4=None usertail5=None usertail6=None
2026-06-10 15:01:39,937 [INFO] kivy: CTRL: POS static maps built items=95, codes=96, search=64
2026-06-10 15:01:39,959 [INFO] api_call: Heartbeat thread started
2026-06-10 15:01:39,959 [INFO] kivy: Verze API 069_9_Kivy.API.hotel-target, verze frontend 069_8_Kivy.MB.hotel-restore
2026-06-10 15:01:39,959 [INFO] kivy: Aplikace připravena
2026-06-10 15:01:39,959 [INFO] kivy: THREAD RESULT = True
2026-06-10 15:01:39,964 [INFO] kivy: UI refresh po startu
2026-06-10 15:01:39,964 [INFO] kivy: UI ready → čekám na login
2026-06-10 15:01:39,976 [INFO] kivy: APP: show login user
2026-06-10 15:03:00,081 [INFO] kivy: NumberPad CANCEL
2026-06-10 15:03:00,081 [INFO] kivy: LOGIN CANCEL
2026-06-10 15:03:00,081 [INFO] kivy: Kivy on_stop called
2026-06-10 15:03:00,081 [INFO] kivy: Application cleanup started
2026-06-10 15:03:00,082 [INFO] api_call: Stopping heartbeat
2026-06-10 15:03:00,189 [INFO] api_call: Heartbeat thread stopped
2026-06-10 15:03:00,233 [INFO] kivy: Base: Leaving application in progress...
2026-06-10 15:03:00,235 [INFO] kivy: Kivy on_stop called
2026-06-10 15:03:02,982 [INFO] kivy: Inicializace ApiController
2026-06-10 15:03:03,021 [INFO] kivy: APP on_start → startuji backend init
2026-06-10 15:03:03,022 [INFO] kivy: THREAD: start_app běží
2026-06-10 15:03:03,023 [INFO] kivy: Base: Start application main loop
2026-06-10 15:03:03,026 [INFO] kivy: GL: NPOT texture support is available
2026-06-10 15:03:03,069 [INFO] api_call:
Přihlášení úspěšné
token=Z_lM7fKhPyI84evXF3tBRWTja5ObDdluOqYXxshsSlU
refresh_token=6dkzH8dWE9I-o8YNLph_sFT_7I4lmlUeFsrSskVKEWdT8nuGq-xc-g3xEX9sR3lq
2026-06-10 15:03:03,170 [INFO] api_call: GET load_kasutxt_api: resp={'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 15:03:03,171 [WARNING] kivy: Hlavicky uctov se nepodařilo načíst: unhashable type: 'dict'
2026-06-10 15:03:03,223 [INFO] kivy: CTRL: POS static maps built items=95, codes=96, search=64
2026-06-10 15:03:03,242 [INFO] api_call: Heartbeat thread started
2026-06-10 15:03:03,242 [INFO] kivy: Verze API 069_9_Kivy.API.hotel-target, verze frontend 069_8_Kivy.MB.hotel-restore
2026-06-10 15:03:03,243 [INFO] kivy: Aplikace připravena
2026-06-10 15:03:03,243 [INFO] kivy: THREAD RESULT = True
2026-06-10 15:03:03,250 [INFO] kivy: UI refresh po startu
2026-06-10 15:03:03,250 [INFO] kivy: UI ready → čekám na login
2026-06-10 15:03:03,257 [INFO] kivy: APP: show login user
2026-06-10 15:10:03,994 [INFO] kivy: NumberPad CANCEL
2026-06-10 15:10:03,995 [INFO] kivy: LOGIN CANCEL
2026-06-10 15:10:03,995 [INFO] kivy: Kivy on_stop called
2026-06-10 15:10:03,996 [INFO] kivy: Application cleanup started
2026-06-10 15:10:03,997 [INFO] api_call: Stopping heartbeat
2026-06-10 15:10:04,449 [INFO] api_call: Heartbeat thread stopped
2026-06-10 15:10:04,509 [INFO] kivy: Base: Leaving application in progress...
2026-06-10 15:10:04,513 [INFO] kivy: Kivy on_stop called
2026-06-10 15:10:11,192 [INFO] kivy: Inicializace ApiController
2026-06-10 15:10:11,250 [INFO] kivy: APP on_start → startuji backend init
2026-06-10 15:10:11,254 [INFO] kivy: Base: Start application main loop
2026-06-10 15:10:11,256 [INFO] kivy: THREAD: start_app běží
2026-06-10 15:10:11,261 [INFO] kivy: GL: NPOT texture support is available
2026-06-10 15:10:11,345 [INFO] api_call:
Přihlášení úspěšné
token=ZW0xvC_haO6kU64DE3YynWiouL0bEYaiNouvy12vlWE
refresh_token=C_EQMl7dHIHRQYEdlRlPkXn9IGnX6hSuWZH9p61z72n42fxMODBy8fDBMNyV-7UW
2026-06-10 15:10:11,464 [INFO] api_call: GET load_kasutxt_api: resp={'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 15:10:11,466 [WARNING] kivy: Hlavicky uctov se nepodařilo načíst: 1 validation error for KasUtxtRiadky
JSON input should be string, bytes or bytearray [type=json_type, input_value={'userhead1': 'Norbert Fr...5': '', 'usertail6': ''}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/json_type
2026-06-10 15:10:11,565 [INFO] kivy: CTRL: POS static maps built items=95, codes=96, search=64
2026-06-10 15:10:11,580 [INFO] api_call: Heartbeat thread started
2026-06-10 15:10:11,581 [INFO] kivy: Verze API 069_9_Kivy.API.hotel-target, verze frontend 069_8_Kivy.MB.hotel-restore
2026-06-10 15:10:11,582 [INFO] kivy: Aplikace připravena
2026-06-10 15:10:11,585 [INFO] kivy: THREAD RESULT = True
2026-06-10 15:10:11,591 [INFO] kivy: UI refresh po startu
2026-06-10 15:10:11,591 [INFO] kivy: UI ready → čekám na login
2026-06-10 15:10:11,603 [INFO] kivy: APP: show login user
2026-06-10 15:12:01,117 [INFO] kivy: NumberPad CANCEL
2026-06-10 15:12:01,118 [INFO] kivy: LOGIN CANCEL
2026-06-10 15:12:01,118 [INFO] kivy: Kivy on_stop called
2026-06-10 15:12:01,119 [INFO] kivy: Application cleanup started
2026-06-10 15:12:01,119 [INFO] api_call: Stopping heartbeat
2026-06-10 15:12:01,949 [INFO] api_call: Heartbeat thread stopped
2026-06-10 15:12:01,992 [INFO] kivy: Base: Leaving application in progress...
2026-06-10 15:12:01,994 [INFO] kivy: Kivy on_stop called
2026-06-10 15:12:05,447 [INFO] kivy: Inicializace ApiController
2026-06-10 15:12:05,482 [INFO] kivy: APP on_start → startuji backend init
2026-06-10 15:12:05,483 [INFO] kivy: THREAD: start_app běží
2026-06-10 15:12:05,483 [INFO] kivy: Base: Start application main loop
2026-06-10 15:12:05,487 [INFO] kivy: GL: NPOT texture support is available
2026-06-10 15:12:05,530 [INFO] api_call:
Přihlášení úspěšné
token=xkcpl6bMh8VJshIHNlvRp6d8iaGq2nZL0s-UV-cxLE4
refresh_token=AigsSsH6N5Z0VQ-kvbvB5fNCFtYhoL5Mg4Lc_Klw3m3tS8njqBlqG0xk8xXi03oM
2026-06-10 15:12:05,613 [INFO] api_call: GET load_kasutxt_api: resp={'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 15:12:05,614 [WARNING] kivy: Hlavicky uctov sa podařilo načíst: {'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 15:12:05,649 [INFO] kivy: CTRL: POS static maps built items=95, codes=96, search=64
2026-06-10 15:12:05,657 [INFO] api_call: Heartbeat thread started
2026-06-10 15:12:05,657 [INFO] kivy: Verze API 069_9_Kivy.API.hotel-target, verze frontend 069_8_Kivy.MB.hotel-restore
2026-06-10 15:12:05,658 [INFO] kivy: Aplikace připravena
2026-06-10 15:12:05,658 [INFO] kivy: THREAD RESULT = True
2026-06-10 15:12:05,664 [INFO] kivy: UI refresh po startu
2026-06-10 15:12:05,664 [INFO] kivy: UI ready → čekám na login
2026-06-10 15:12:05,677 [INFO] kivy: APP: show login user
2026-06-10 15:12:55,666 [INFO] kivy: NumberPad CANCEL
2026-06-10 15:12:55,666 [INFO] kivy: LOGIN CANCEL
2026-06-10 15:12:55,666 [INFO] kivy: Kivy on_stop called
2026-06-10 15:12:55,666 [INFO] kivy: Application cleanup started
2026-06-10 15:12:55,667 [INFO] api_call: Stopping heartbeat
2026-06-10 15:12:55,789 [INFO] api_call: Heartbeat thread stopped
2026-06-10 15:12:55,826 [INFO] kivy: Base: Leaving application in progress...
2026-06-10 15:12:55,827 [INFO] kivy: Kivy on_stop called
2026-06-10 15:13:01,179 [INFO] kivy: Inicializace ApiController
2026-06-10 15:13:01,224 [INFO] kivy: APP on_start → startuji backend init
2026-06-10 15:13:01,228 [INFO] kivy: Base: Start application main loop
2026-06-10 15:13:01,232 [INFO] kivy: GL: NPOT texture support is available
2026-06-10 15:13:01,237 [INFO] kivy: THREAD: start_app běží
2026-06-10 15:13:01,307 [INFO] api_call:
Přihlášení úspěšné
token=0tg_3Y64J-vHBSOYRR5YnMGjbuXwHmkoWtmdb5dxqEo
refresh_token=fw1R31O9TvxTNguBXb2uIMWC07IgarKGVknowhNdn5O485s4AMz02VRRitlEktgP
2026-06-10 15:13:01,416 [INFO] api_call: GET load_kasutxt_api: resp={'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 15:13:01,417 [WARNING] kivy: Hlavicky uctov sa podařilo načíst: {'userhead1': 'Norbert Frank - EUROINF TATRY', 'userhead2': 'Popradská 34', 'userhead3': '064 01 Stará Ľubovňa', 'userhead4': 'Salaš u Franka', 'userhead5': 'Popradská 34', 'userhead6': '064 01 Stará Ľubovňa', 'userhead7': '', 'userhead8': '', 'userhead9': '', 'usertail1': '', 'usertail2': '', 'usertail3': '', 'usertail4': '', 'usertail5': '', 'usertail6': ''}
2026-06-10 15:13:01,528 [INFO] kivy: CTRL: POS static maps built items=95, codes=96, search=64
2026-06-10 15:13:01,550 [INFO] api_call: Heartbeat thread started
2026-06-10 15:13:01,551 [INFO] kivy: Verze API 069_9_Kivy.API.hotel-target, verze frontend 069_8_Kivy.MB.hotel-restore
2026-06-10 15:13:01,552 [INFO] kivy: Aplikace připravena
2026-06-10 15:13:01,553 [INFO] kivy: THREAD RESULT = True
2026-06-10 15:13:01,564 [INFO] kivy: UI refresh po startu
2026-06-10 15:13:01,564 [INFO] kivy: UI ready → čekám na login
2026-06-10 15:13:01,576 [INFO] kivy: APP: show login user
2026-06-10 15:13:04,753 [INFO] kivy: NumberPad ACCEPT: 613190
2026-06-10 15:13:04,754 [INFO] kivy: CTRL: login_user
2026-06-10 15:13:04,788 [INFO] kivy: CTRL: user logged in: SYSTEM, permits=['PLATBA', 'PL_HOTOVE', 'SPLIT', 'PL_SELECT', 'DISCOUNT', 'STORNO_UCT', 'PL_CHG', 'CENHLAD', 'STORNO_PL', 'CLOSE']
2026-06-10 15:13:04,788 [INFO] kivy: LOGIN USER OK
2026-06-10 15:13:04,789 [INFO] kivy: APP: show_account_select
2026-06-10 15:13:05,195 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:13:05,196 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:13:05,223 [INFO] kivy: AccountSelect: start auto refresh
2026-06-10 15:13:06,225 [INFO] kivy: ACCOUNT SELECT → stul=6 mode=normal view=open_table
2026-06-10 15:13:06,226 [INFO] kivy: Opening table 6
2026-06-10 15:13:06,227 [INFO] kivy: CTRL: load_ucet stul=6
2026-06-10 15:13:06,260 [INFO] kivy: CTRL: open_posdialog
2026-06-10 15:13:06,342 [INFO] kivy: POS: set_ucet stul=6
2026-06-10 15:13:40,713 [INFO] kivy: CTRL: _on_pos_finish operation=edit_only
2026-06-10 15:13:40,715 [INFO] kivy: CTRL: POS finalize operation=edit_only
2026-06-10 15:13:40,715 [INFO] kivy: CTRL: edit_only
2026-06-10 15:13:41,259 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:13:41,260 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:13:41,292 [INFO] kivy: AccountSelect: start auto refresh
2026-06-10 15:13:46,258 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:13:46,259 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:13:46,259 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:13:51,263 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:13:51,263 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:13:51,265 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:13:56,260 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:13:56,261 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:13:56,261 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:01,256 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:01,256 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:01,257 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:06,258 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:06,258 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:06,259 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:11,260 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:11,262 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:11,264 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:16,264 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:16,264 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:16,265 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:21,268 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:21,270 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:21,271 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:26,271 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:26,271 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:26,272 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:31,272 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:31,272 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:31,273 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:36,273 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:36,275 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:36,277 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:41,268 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:41,268 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:41,269 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:46,265 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:46,265 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:46,266 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:51,261 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:51,261 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:51,262 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:14:56,266 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:14:56,267 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:14:56,267 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:01,264 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:01,265 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:01,265 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:06,263 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:06,264 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:06,264 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:11,266 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:11,266 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:11,267 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:16,265 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:16,266 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:16,267 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:21,260 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:21,260 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:21,261 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:26,267 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:26,267 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:26,268 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:31,263 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:31,263 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:31,264 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:36,267 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:36,268 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:36,268 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:41,263 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:41,265 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:41,266 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:46,267 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:46,268 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:46,268 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:51,268 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:51,269 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:51,269 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:15:56,268 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:15:56,269 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:15:56,270 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:01,264 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:01,265 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:01,267 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:06,264 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:06,266 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:06,267 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:11,270 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:11,270 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:11,271 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:16,275 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:16,276 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:16,276 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:21,270 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:21,274 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:21,276 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:26,292 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:26,293 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:26,294 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:31,287 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:31,288 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:31,288 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:36,292 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:36,294 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:36,295 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:41,287 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:41,287 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:41,288 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:46,288 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:46,289 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:46,289 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:51,283 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:51,285 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:51,287 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:16:56,286 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:16:56,287 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:16:56,288 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:01,292 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:01,294 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:01,295 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:06,292 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:06,293 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:06,294 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:11,291 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:11,293 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:11,294 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:16,288 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:16,289 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:16,289 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:21,291 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:21,292 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:21,294 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:26,286 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:26,287 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:26,288 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:31,280 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:31,281 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:31,282 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:36,283 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:36,286 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:36,288 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:41,280 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:41,280 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:41,281 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:46,274 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:46,276 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:46,277 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:51,271 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:51,273 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:51,275 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:17:56,269 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:17:56,269 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:17:56,270 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:01,263 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:01,264 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:01,265 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:06,264 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:06,266 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:06,268 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:11,264 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:11,264 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:11,265 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:16,267 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:16,268 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:16,270 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:21,274 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:21,274 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:21,275 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:26,273 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:26,273 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:26,274 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:31,277 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:31,278 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:31,279 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:36,282 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:36,284 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:36,286 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:41,283 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:41,285 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:41,286 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:46,283 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:46,284 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:46,286 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:51,281 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:51,283 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:51,284 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:18:56,282 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:18:56,283 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:18:56,283 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:01,283 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:01,284 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:01,285 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:06,279 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:06,280 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:06,280 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:11,283 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:11,284 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:11,286 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:16,282 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:16,283 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:16,283 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:21,288 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:21,289 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:21,291 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:26,283 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:26,283 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:26,284 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:31,280 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:31,280 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:31,281 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:36,281 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:36,282 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:36,283 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:41,276 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:41,277 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:41,278 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:46,274 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:46,276 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:46,277 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:51,274 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:51,274 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:51,275 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:19:56,275 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:19:56,276 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:19:56,278 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:01,273 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:01,274 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:01,274 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:06,277 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:06,277 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:06,278 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:11,278 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:11,278 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:11,279 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:16,282 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:16,284 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:16,285 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:21,279 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:21,279 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:21,280 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:26,275 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:26,276 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:26,276 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:31,281 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:31,281 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:31,282 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:36,281 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:36,281 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:36,282 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:41,287 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:41,288 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:41,289 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:46,291 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:46,293 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:46,294 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:51,295 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:51,296 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:51,297 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:20:56,301 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:20:56,301 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:20:56,302 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:01,306 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:01,306 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:01,307 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:06,321 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:06,322 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:06,323 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:11,321 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:11,321 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:11,322 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:16,318 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:16,318 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:16,319 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:21,321 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:21,322 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:21,323 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:26,326 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:26,326 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:26,327 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:31,324 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:31,324 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:31,325 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:36,326 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:36,328 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:36,329 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:41,328 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:41,330 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:41,331 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:46,331 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:46,331 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:46,332 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:51,327 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:51,330 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:51,331 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:21:56,324 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:21:56,324 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:21:56,325 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:01,320 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:01,320 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:01,321 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:06,326 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:06,326 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:06,327 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:11,325 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:11,325 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:11,326 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:16,327 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:16,327 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:16,328 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:21,331 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:21,332 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:21,332 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:26,335 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:26,337 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:26,338 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:31,333 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:31,333 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:31,334 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:36,339 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:36,339 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:36,339 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:41,338 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:41,338 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:41,339 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:46,342 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:46,342 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:46,343 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:51,346 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:51,346 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:51,347 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:22:56,346 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:22:56,346 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:22:56,347 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:01,353 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:01,357 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:01,358 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:06,358 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:06,360 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:06,361 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:11,361 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:11,364 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:11,365 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:16,359 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:16,360 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:16,361 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:21,366 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:21,367 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:21,367 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:26,370 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:26,371 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:26,373 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:31,371 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:31,392 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:31,392 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:36,373 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:36,374 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:36,374 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:41,369 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:41,369 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:41,370 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:46,374 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:46,376 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:46,377 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:51,374 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:51,375 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:51,375 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:23:56,372 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:23:56,373 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:23:56,374 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:01,376 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:01,378 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:01,379 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:06,381 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:06,382 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:06,382 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:11,379 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:11,381 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:11,382 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:16,383 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:16,383 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:16,384 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:21,386 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:21,386 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:21,387 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:26,386 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:26,386 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:26,387 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:31,391 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:31,391 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:31,392 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:36,389 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:36,389 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:36,390 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:41,392 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:41,394 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:41,395 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:46,390 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:46,391 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:46,392 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:51,402 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:51,402 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:51,403 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:24:56,404 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:24:56,404 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:24:56,405 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:01,410 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:01,412 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:01,414 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:06,414 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:06,417 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:06,418 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:11,418 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:11,419 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:11,419 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:16,416 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:16,417 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:16,417 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:21,422 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:21,422 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:21,423 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:26,426 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:26,427 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:26,428 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:31,422 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:31,423 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:31,423 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:36,427 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:36,428 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:36,430 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:41,439 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:41,440 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:41,441 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:46,438 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:46,438 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:46,439 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:51,442 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:51,443 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:51,445 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:25:56,441 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:25:56,442 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:25:56,444 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:01,445 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:01,447 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:01,449 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:06,447 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:06,448 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:06,448 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:11,442 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:11,445 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:11,447 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:16,469 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:16,470 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:16,471 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:21,474 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:21,476 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:21,478 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:26,471 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:26,474 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:26,476 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:31,474 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:31,474 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:31,475 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:36,479 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:36,480 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:36,481 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:41,482 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:41,483 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:41,483 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:46,483 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:46,485 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:46,486 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:51,477 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:51,477 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:51,478 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:26:56,479 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:26:56,481 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:26:56,483 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:01,476 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:01,476 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:01,477 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:06,482 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:06,482 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:06,483 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:11,479 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:11,479 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:11,480 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:16,474 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:16,474 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:16,475 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:21,474 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:21,475 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:21,476 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:26,477 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:26,477 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:26,477 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:31,472 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:31,473 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:31,474 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:36,475 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:36,476 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:36,476 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:41,472 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:41,472 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:41,473 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:46,468 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:46,468 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:46,469 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:51,471 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:51,471 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:51,472 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:27:56,477 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:27:56,478 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:27:56,478 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:01,475 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:01,477 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:01,478 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:04,498 [INFO] api_call: 401 → refreshing access token
2026-06-10 15:28:06,475 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:06,476 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:06,477 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:11,473 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:11,473 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:11,474 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:16,474 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:16,477 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:16,480 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:21,470 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:21,470 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:21,471 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:26,465 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:26,465 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:26,466 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:31,472 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:31,472 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:31,473 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:36,477 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:36,480 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:36,482 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:41,476 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:41,477 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:41,477 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:46,471 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:46,471 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:46,472 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:51,469 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:51,469 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:51,470 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:28:56,467 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:28:56,468 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:28:56,468 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:01,462 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:01,463 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:01,463 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:06,457 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:06,458 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:06,459 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:11,456 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:11,459 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:11,461 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:16,452 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:16,452 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:16,453 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:21,453 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:21,453 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:21,454 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:26,470 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:26,471 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:26,472 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:31,476 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:31,476 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:31,477 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:36,480 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:36,480 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:36,481 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:41,495 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:41,496 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:41,498 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:46,492 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:46,494 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:46,495 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:51,487 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:51,489 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:51,490 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:29:56,482 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:29:56,483 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:29:56,484 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:01,480 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:01,480 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:01,481 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:06,682 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:06,683 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:06,683 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:11,679 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:11,679 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:11,680 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:16,674 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:16,675 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:16,675 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:21,676 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:21,676 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:21,677 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:26,673 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:26,675 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:26,676 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:31,676 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:31,678 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:31,678 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:36,682 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:36,684 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:36,685 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:41,679 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:41,680 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:41,681 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:46,679 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:46,680 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:46,680 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:51,680 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:51,681 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:51,682 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:30:56,679 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:30:56,682 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:30:56,684 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:01,681 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:01,682 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:01,682 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:06,682 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:06,683 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:06,684 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:11,688 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:11,688 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:11,689 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:16,693 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:16,694 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:16,694 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:21,689 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:21,690 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:21,691 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:26,688 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:26,689 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:26,691 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:31,684 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:31,684 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:31,685 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:36,687 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:36,688 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:36,689 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:41,683 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:41,684 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:41,685 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:46,688 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:46,688 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:46,689 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:51,693 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:51,693 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:51,694 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:31:56,696 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:31:56,696 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:31:56,697 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:01,699 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:01,700 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:01,700 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:06,732 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:06,733 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:06,734 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:11,728 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:11,729 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:11,730 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:16,723 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:16,724 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:16,724 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:21,722 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:21,723 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:21,724 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:26,725 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:26,726 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:26,727 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:31,729 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:31,729 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:31,730 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:36,729 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:36,729 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:36,729 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:41,724 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:41,726 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:41,727 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:46,721 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:46,721 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:46,722 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:51,720 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:51,720 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:51,721 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:32:56,724 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:32:56,724 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:32:56,725 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:01,730 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:01,731 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:01,731 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:06,726 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:06,727 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:06,728 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:11,723 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:11,724 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:11,725 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:16,726 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:16,726 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:16,727 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:21,724 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:21,724 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:21,725 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:26,727 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:26,728 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:26,729 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:31,722 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:31,722 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:31,723 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:36,718 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:36,718 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:36,719 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:41,723 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:41,723 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:41,724 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:46,727 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:46,727 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:46,728 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:51,721 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:51,721 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:51,722 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:33:56,717 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:33:56,717 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:33:56,718 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:01,721 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:01,721 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:01,722 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:06,717 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:06,718 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:06,718 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:11,721 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:11,723 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:11,725 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:16,725 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:16,725 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:16,726 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:21,720 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:21,720 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:21,721 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:26,719 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:26,720 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:26,721 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:31,724 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:31,726 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:31,727 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:36,724 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:36,724 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:36,725 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:41,729 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:41,730 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:41,730 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:46,733 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:46,734 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:46,736 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:51,736 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:51,737 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:51,738 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:34:56,737 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:34:56,737 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:34:56,738 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:35:01,744 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:35:01,744 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:35:01,745 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:35:06,750 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:35:06,750 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:35:06,751 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:35:11,754 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:35:11,754 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:35:11,754 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:35:16,758 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:35:16,758 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:35:16,759 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:35:21,760 [WARNING] kivy: AccountSelect: TIMER TICK
2026-06-10 15:35:21,760 [WARNING] kivy: AccountSelect: REFRESH CALLED mode=open_table
2026-06-10 15:35:21,760 [INFO] kivy: CTRL: load_stoly
2026-06-10 15:35:21,885 [INFO] kivy: Kivy on_stop called
2026-06-10 15:35:21,886 [INFO] kivy: Application cleanup started
2026-06-10 15:35:21,887 [INFO] api_call: Stopping heartbeat
2026-06-10 15:35:23,926 [INFO] kivy: Base: Leaving application in progress...
2026-06-10 15:35:23,928 [INFO] kivy: Kivy on_stop called
+5629
View File
File diff suppressed because it is too large Load Diff
+1994
View File
File diff suppressed because it is too large Load Diff
+96
View File
@@ -0,0 +1,96 @@
Verze 1. Beta
spousteni primo pod pythonem
venv/Scripts/python.exe
ToDo:
-
-do blokovani na serveru dopln test na expiraci blocku
-blokovane stoly s jinou barvou a on_time aktivovat stav
-všude používat popup z kivu_app, je ještě posdialogu
-v souboru zkouska mas funkcni import configu, zarid to misto cteni parametru
-informacni blok (cislo pokladny, cisnik, defaultni hladina...)
-do ceniku stranky
-uzivatele
-opravneni, server je hotovy heslo 123 je zatím user AltoAdmin
-podivej se na tisky.py (je tam bordel)
-tisk bonu
pozor, při stornu odeslanych polozek mas v u_sec odeslane i ještě neodeslane
-vyzkousej TCP tisk
-vyhazej ze serveru endpointy pro users, které nejsou potřeba
-rozdelit server na endpointy a DB operace
-implementuj pojmenovane stul
-automaticky test
-vice pokladen, vice terminalu
-blokace, release
-relogin, refresh token
-start s novou databazi
-zkus jinou databazi (postgres nebo DuckDB)
-strukturovane setupy server i client, zvaz config.fp nacteni rovnou do objektu
Chyby:
Hotovo:
kivy_app, dodělat heartbeats
při stronu uctu jsou ve vyberu mezery, jako by tam byly ty odfiltrovane ucty
po celkove platbe pak nefunguje prohlizeni uctu
pokud stornujes ucet vznikly jako změna druhu platby, vznikne storno storna kladne
prohlizeni/kopie zaplaceneho uctu
pridana sleva
opraven vypocet DPH
storna odeslanych polozek jsou ted jen v otevrenem stole
zrusena storna odeslanych polozek z otevrenych uctu z accountselectdialog
změna druhu platby
storno uctu
do server endpointu volaného z load_stoly_API podle ucis pridan limit kolik posledních zaznamu (defaultne 50uctu, vraci close_at)
Když v zadani hesla das zrusit, pokladna se ma ukoncit
Server tvori databazi users a vlozi Alto admin
kdyz pri castecne platbe pouzijes Eura, zmizi puvodni ucet a nic se nezaplati
QR platba, IBAN a poznámku pro platce (pos_name) bere ze Setupu
setup uz nedela upgrade DB, moc to nefungovalo (zmeny nutno implementovat
jak do init_setup_tab tak do data)
logger pro clienta (misto printu if debug:)
heart beet
-automaticke odhlaseni mrtvol
-release bloku mrtvych klientu
-spousti se az po login
tisk uctu
Storno polozek z odeslanych uctu
Markovani kodem
Storno odeslanych polozek
Vytvorit spravne novou databazi s nulovym cislem uctu
Pouziva tokeny k autentifikaci, tokeny expiruji (kratky a dlouhy, oba ziskas pri prihlaseni)
callApi ma parametricky retry pri vypadku serveru
multi verze pro vic provozu a spolecnosti
do tokenu je pripojen na zacatek prefix na tabulky
existuje databaze spolecnosti s uzivateli, hesly a prefixy a tokeny
logovani, bud info nebo do souboru
heslo v databazi zakazek ulozeno kodovane
vic spolecnosti, tabulky zaklada automaticky
get_nucis fungovaje tak, ze když nenajde zadne číslo uctu, da xx000001,
jinak další v poradi xx00000, kde xx je cislo pokladny
cenik vytvoren, triden pro pokladny procedury save a load i delete je hotovy
login a autentifikace ted umi vice zakazniku a vice terminalu u jednoho zakaznika
id client je v hlavicce rq, je na nej vydan jeho token
tokeny a klienti v pohode, vse funguje i pro ruzne instance (terminaly)
polozky odeslany do kuchyne maji svuj atribut sent, maji jinou barvu a moznost omezit
akce na tlacitkach polozky uctu v ceniku zatim
pridany barvy butonu matice
hotovy setup, pro cislo pokladny (login, setup -> prace s POSDialog)
menu cenove hladiny vzdy nastaveny na aktualni, co se stane kdyz je Null
v ceniku i uctech je id_card, vybere se prvni hladina polozky ceniku
defaultni cenova hladina v setupu, lze menit v POSDialogu
hotovy dialog platby
mas block ucet a unblock ucet
get ucet defaultne ucet blokuje, uprest odblokuje
ucet ma metodu upsert (update/insert), cislo uctu prideli server pri platbe
na zaklade vyplneneho closed_at
get metoda ma param blokuj, upsert def odblokovava, ale ma moznost i ponechat zablokovany (pro pripad dlouho otevreneho uctu on timer update)
okamzita platba pri zalozeni uctu stolu
unblock uctu po ukonceni PosDialogu bez akce OK
osetri ukladani uctu bez polozek pri splitu
kdyz prevadis na ucet, kde uz neco je -> zavedena novy endpoint merge_ucet
prevody to co vyberes zustava, to co ma zustat se prevadi??? vyreseno, ale chce to trochu ucesat
pridan do vsech operaci s ucty filtr na id_kas
+81
View File
@@ -0,0 +1,81 @@
############################################################
# INSTALACE POKLADNY NA UBUNTU (PYTHON 3.12)
############################################################
sudo apt update
sudo apt upgrade -y
# -- INSTALACE PYTHON 3.12 A VENV
sudo apt install -y \
python3 \
python3-pip \
python3-venv \
python3-dev
# -- KONTROLA VERZE PYTHONU
python3 --version
# -- ZAKLADNI SYSTEMOVE BALICKY (obvykle není potřeba)
sudo apt install -y \
build-essential \
pkg-config \
git \
curl \
wget \
zip \
unzip
# -- KNIHOVNY POTREBNE PRO KIVY
sudo apt install -y \
libgl1-mesa-dev \
libgles2-mesa-dev \
mesa-utils \
libglib2.0-0 \
libmtdev1 \
xclip \
xsel \
libjpeg-dev \
libpng-dev \
libfreetype6-dev \
zlib1g-dev \
libffi-dev \
libssl-dev \
libsqlite3-dev
# -- VYTVORENI SLOZKY PRO POKLADNU, aktivace venv
mkdir -p ~/pokladna
cd ~/pokladna
python3 -m venv k312
source k312/bin/activate
# -- UPGRADE PIP, dulezite!!!
python -m pip install --upgrade pip setuptools wheel
# -- INSTALACE KIVY
pip install kivy
# -- BALICKY PRO POKLADNU
pip install \
pydantic \
fastapi \
uvicorn \
requests \
pillow \
qrcode \
# -- VYTVORENI STARTOVACIHO SKRIPTU
cat > start_pokladna.sh <<'BASH'
#!/usr/bin/env bash
cd ~/pokladna
source venv/bin/activate
python kivy_app.py
BASH
chmod +x start_pokladna.sh
# -- SPUSTENI POKLADNY
./start_pokladna.sh
deactivate
+284
View File
@@ -0,0 +1,284 @@
Verze s Kivy (dole popis principu)
v posdialogu ostava dorobit:
- doplnit do action_panela zlavu na polozku a zmenu spravy
Uzavierky
Jazyky
Prehlad spotreby
Zlavy na polozky
Vklady, vybery
Uhrada pohladavky
Nastavenie dostupnosti kariet
Okno na nastavenie dat v config.json - aby sa dala vybrat kasa, s ktorou chceme pracovat
Aktualizacia cennik bez restartu server a appky
Cook
Bazen
LoyalMan
Rajonovy rezim a uzavierky casnikov
Prava casnikov
pod s buttonem pod zpet, nevim co s tim
- pokud nechces uzaverku blokovat spustenym terminálem, oprav to zde
server_sqlite.py
@app.post("/closure/save/",response_model=server_clsrep.ClosureReportOut)
def save_closure_report(
- odstran chybu co popsal Milan v mailu (pro zuseni uzaverky kdyz vyberes stejny ucet k uzaverce to spadne)
- sleva na položku (cena, cena puv)
(do ceniku pridat slovnik atributu, atribut a hodnota, zde max sleva napr. 0)
- dopln na stale podrzeni butonu pro markovani do menu sleva na polozku
- dodělat bar rezim s automatickym odhlasenim po nastavene dobe
- otestuj spec. touche při markovani a editaci uctu
- v posdialog se pouziva _popup_info z controlleru a jsou tam ještě definovany nejaky spec. sjednot to
- menu uzaverky (uzaverka s doplnenim id_ucty), meziuzaverka, tisk kopie uzaverky, mazani poslední uzaverky, posun uzaverky?
- do posdialog a numpad udelej cteni RS232 a klavesnice pro vstup externich ctecek
- otestuj prihlaseni stejne id_kas se stejnym client_id
- zjistit při pokusu zablokovat ucet ke stornu když probiha jeho storno na jinem terminalu
- do config.json pridat parametry pro velikost accountselect a posdialog (zejména počet buttonu v matici)
- upravit format uctu a bonu na terminalu
- zobrazeni konfiguracnich parametru při neuspesnem startu
Hotovo:
===== 072_8_Kivy ====================================================================================================
Bar
Limity
- doplnena tabulka fooddat
Akceptujem parametre z def.suboru:
IS_LIMSPRA - ak je nastaveny connect na postgresql v centralnom setupe
===== 071_8_Kivy ====================================================================================================
- prvy nastrel tlace. Kasa uklada tlacove joby do fronty v tabulke print_jobs. Zapisuje tam vsetky joby, ktore nemaju tlacit na afs.
- k tlaciarni je mozne vo foode nadefinovat sablonu, cez ktoru sa maju tlacit bony aj ucty. su to podobne jinja2 subory ako pouzival foodie. kasa potom hlada bud nastavenu sablonu od tlaciarne, alebo default sablonu, alebo ked nic nenajde vytlaci jednoduchy bon bez formatovania. Rovnako sa postupuje pri tlaci uctov.
- komunikacia s bank terminalmi je podmienena nastavenim priznaku v druhu platby a v tlaciarni, na ktoru sa ide tlacit. potom podla toho aky terminal je k tlaciarni priradeny, vola sa obsluha terminalu. zatial som oskusal slovenske AFS a pripraveny je Besternom, tak, ako ho napisal Pepino. Ten som ale nevzskusal, lebo ho tu nemam
- pribudol local_print_agent ako samostatny script, ktory sa moze spustit na windowse/linuxe v lokalnej sieti, alebo moze byt urobeny ako samostatna androidova appka. ten bude zo servera vytahovat pripravene joby a tlacit ich. skusal som to u nas na raw printeroch. pocita aj s cups tlaciarnami
- tlace dotiahnute aj v okne na kopie a storna uctov
- storno uctu s platbou kartou je riadene parametrom terminal_storno rovnako ako bolo vo foodie
- pre nefiskalne ucty je nastavitelne v def.subore, ci sa ucet moze aj nevytlacit
- menu v accountselet (storno s vraceni na ucet, storno polozek z uctu)
- rozmysli tiskarny bonu (muzou byt v setupu sklad -> bon printer)
- zaokrouhlovani uctu, vracet sumu zaokrouhleni
- tisky bonu do kuchyne (musíš pridat tiskárnu do ceniku, vazbu sklad printer do setupu)
Akceptujem parametre z def.suboru:
DEF_CENHLA
OBJEDNAVKA_TLACITKO1
OBJEDNAVKA_TLACITKO2
OBJEDNAVKA_TLACITKO3
IS_CHOD
IS_HOST
IS_TRETINY
IS_STVRTINY
KC_SPART
MENU_SPART
CARD_PAY_NO_TERM
BAR_PAY_IN_CASH
ZKR_MENA
TERMINAL_STORNO
NKOP_EDIT
REPRINT_CURRENT_CLOSURE_BILL
REPRINT_PAST_CLOSURE_BILL
RASTR_HOT
IS_HORSKUP
POSTGRES_ENABLED
===== 070_8_Kivy ====================================================================================================
- v platobnom okne doplnena moznost vybrat viacero zliav. nasledne sa vola recalculate, ako metoda, ktora cely ucet prepocita. ide sa v poradi - zmena cenovej hladiny, absolutne zlavy, percentualne zlavy, a potom jednotlive platby
- dodělej logiku ke slevam (absolutni, procentuelni, na položku a atributy nodiscountv ceniku)
- do uctu/UcPol dodělat cena_puv, vyndat mena (markuje se jen zakladni mene). Nebo prepocet dle kurzu? Zatim to tam nechat.
- presun messages ze setupu do ceniku
- doplnena tabulka bankterm
- v tabulke prndef doplneny stlpec id_term
- do parametrov doplnene is_chod a is_host. Da sa nimi vypnut praca s chodmi a hostami)
- doplnena tabulka clients - applikacia si tu uklada cislo posledne vybranej tlaciarne a miestnosti pre kazdy terminal, aby to pri dalsom spusteni vedela pouzit
===== 069_8_Kivy ====================================================================================================
- doplnit predvolene sposoby platby nie iba hotovost
- Platba vybranych poloziek a Platba vsetkeho podla toho ci su naklikane nejake polozky, alebo nie
- niekde zobrazit predvolenu cenovu hladinu
- doplnit volbu tlaciarne, kam sa budu tlacit ucty
- upraveny platobny formular - najprv sa zadaju zlavy, potom sa vyberaju platby
- vyriesene dotaz_re, dotaz_st, dotaz_ho / oskusana platba na Hores, Fidelio, Previo. Pripravene aj Mews a Protel
v data:
doplnene tabulky na nastavenie recepcie a rastrov pre recepciu
v cenniku doplneny c_druh, aby som veel posielat na recepciu
v uctoch doplnene data uveru a hoteloveho uctu, doplneny stlpec c_uzaverka
===== 068_8_Kivy ====================================================================================================
- v PosDialogu je zjednotene otvaranie modalnych okien do jedneho modalmanagera, ktory spravuje aj vstupy z klavesnice
- prihlasenie klavesnicou, ked nemam numpad robi problem, lebo shift sa berie ako samostatna klavesa
- v hladani doplnit aj hladanie podla kodov a eanov
===== 067_8_Kivy ====================================================================================================
- v objednavke je mozne z klavesnice zadat kod, ci ean a ked sa najde, nablokuje sa. funguje aj 2050*10.
- tlacitko vyber vsetko nahradaene tlacitkom Hladaj. Ukaze sa klavesnica na zadanie retazca. Podla zadaneho textu sa filtruje cennik a dohladavaju sa kalkulacie, ktore obsahuju zadany text
- login screen - heslo je mozne zadat aj z klavesnice
===== 066_8_Kivy ====================================================================================================
- praca s chodmi a hostami, presuny medyi chodmi, hostami a stolmi. Oynacenie poloziek chodu, hosta, ...
- action panel ponuka pracu s oznacenymi polozkami objednavky
- jsou-li vyplneny ceny2 a cena3, pouzij ji pro 1/2 a 1/3 porce
- chody vyreseny (ještě do tisku)
- povinne zprávy při markovani kodem
- osetrene ceske aj slovenske hesla (12, resp. 20 znakov, aj adm hesla v oboch formatoch)
- doplnene spracovanie atributov polozky "volnacena","pohladavka","fstmenu","vazena"
- osetrene spravy - povinne/nepovinne priradene k itemu, aj vseobecne spravy zo setupu a manualna sprava vratane sw klavesnice
- long press na menu vyvola okno na zadanie poctu porcii, zlomkov a sprav
- doplnene chody a hostia - na long press aj s pomenovanim
- vpravo hore tlacitko na oznacenie/odznacenie vsetkeho
zmeny v data:
- UserLoginIn - doplneny kas
- Perm - doplnene jazykove mutacie
- doplnene PolozkyFstMenu, FstMenu, FstMenuKasa, UserPermission, PaymentPermission, DiscountPermission, LevelPermission
- UserIn - doplnene heslo_karta, user_id, is_admin, permits, payments, discounts, levels
- UserOut - doplnene user_id, is_admin, payments, discounts, levels - mozno tu chyba este permits
- UserLoginOut - doplnene user_id, is_admin, payments, discounts, levels, permits
- UCPol - pridane zpravy - budeme ich potrebovat kvoli cookovi, uhradam pohladavok, pripadne aj zmena spravy k uz nablokovanej polozke
- UcPolEdit - zpravy sa dedia od UcPol
===== 063_8_KivyRq/059_8_KivyAPI ====================================================================================================
- pokud udelas zpet z menu privátních zprav dlouhe, namarkuje se ti polozka
- vyber vse -> změna cenove hladiny -> uloz = položky zmizi z uctu (opraveno v Posdialog by Petr 21.4.)
- pokud při submenu na buttonu položky v leve casti objednávky (dlouhy stisk) (opraveno v PosDialog by Petr 22.4.)
===== 061,2_8_KivyRq/059_8_KivyAPI ====================================================================================================
- nektere obecne konstanty pro beh frontendu presunty do modulu konstanty
- pri neuspesnem startu umozni zadat IP API serveru
===== 060_8_KivyRq/059_8_KivyAPI ====================================================================================================
- pokud edituji definci stolu, nejde ulozit
===== 059_8_Kivy ====================================================================================================================
- dodělej endpoint pro nahrani uzivatelu a permision (Milan_users.py)
===== 058_8_Kivy ====================================================================================================================
- opravena chyba pri vyberu defaultni hladiny (front end havaroval pri pokusu nastavit hladinu na prazdne misto ci link na jinou stranku)
- opraveno menu pro vyber hladin, pridam scroll (u polozky i defaultni) pro zobrazeni neomezeneho poctu hladin
- editor mapy stolu nyni umistuje stul vlevo nahore
- doplneno menu editoru stolu (nova mistnost, smaz mistnost, do menu stolu doplnen delete stolu)
- menu mistnosti jak v editoru tak pri vyberu mistnosti je nyni scrollovaci (nemomezeny pocet mistnosti)
- defautni id stolu je nyni tvoreno kombinaci jmena mistnosti a cisla stolu (zabranuje to nechtenemu sdileni uctu v ruznych mistnostech)
- automaticke zarovnavani stolu do mrizky
===== 057_8_Kivy ====================================================================================================================
- vzorovy program pro nahrani mapy stolu z foodu Milan_mapastolu
- mapa stolu a jednoduchy editor, ktery ji ulozi na server primo v pokladne
- endpoint na mapu stolu
===== 056_8_Kivy ====================================================================================================================
- do info v menu uzaverky doplneny info o verzi serveru, frontendu a jmenu aktualni database pouzivane serverem
- endpoint na ucty co nejsou v uzaverce (test/vzor milan_ucty_notinclsrep)
- hotovo cena_puv
- v uctech je cislo skladu pro odtezovani
- dodělej endpoint pro nahrani mapy stolu (test/vzor milan_mapastolu)
- do info zobraz verzi front endu i serveru (server to muze vrátit při prihlaseni)
===== 055_8_Kivy ====================================================================================================================
- normalni platba, pokud je něco vybrano take zaplati jen vybrane (ale korektne, zbytek uctu tam zustane). Pak nemá smysl platba vybraneho?? nema
- vyndej ze server_sqlite heartbeat, at to muzes distribuovat oddelene
===== 054_8_Kivy ====================================================================================================================
- do serveru doplneny 2 endpointy pro nacteni uzaverky z FOODu. Vzor pouziti Milan_nacti_uzaverky
- mas-li oznacene nejake položky na uctu a udelas platbu vseho hotove, zaplati se vybrane a zbytek uctu zmizi
- lze definovat buttony pro ruzne sirky
- opravena migrace v DB pro pridani sirky button matice
- exituji 2 typy zprav. obecne (jdou pripojit ke kazde polozce) a privatni (jdou jen k te polozce, co je ma uvedene v ceniku). Privatni se mohou delit na povinne a ostatni. Obecne se aktivuji dlouhym stiskem na nazev v namarkovane polozce uctu (jsou tam i vsechy zpravy mazat s vyjimkou povinnych), povinne se musi automaticky zadat pri markovani. Pokud je aktivovano Fastitemmenu (dlouhy stisk na button v matici vlevo) a polozka obsahuje povinnou zpravu, nejprve se Zada povinna zprava a az potom se objevi FastItemMenu.
- povinne zprávy při markovani jsou vyreseny ale nemely by jit smazat a co s dlouhym stiskem?- implementuj do markovani nove data od Milana (Eany, povinne zprávy, permision pro markovani, atributy)
- uprav FastMenuItem pro smyslupne markovani (vic voleb), mazani zvoleného zvyrazneno
- vic zprav k jedne polozce, zprávy jdou i mazat (po jedne i všechny)
- na serveru je automaticka migrace jsonu v ceniku s propisem do DB (nemigracni verze je 048_8_kivy, kdyby to moc zdrzovalo). nove pole musi mit default hodnotu, aby Pydantic nezarval pred uskutecnenim migrace
- při prepnuti cenove hladiny nejsou na matici pro markovani aktualni ceny
- doplneny dluhy stisk na levou matici
- zmen markovani kodem dle atributu kod (nyní je to id_card)
- uprav cenik pro import z Food (jen rozsirit pro kod misto id_card, eany, spec zprávy prirazene k polozce (povinne/nepovinne) a atributy
- uprav dle vzoru z data.py rutiny pro zapis setupu (v setupu obecne zprávy)
- uprav dle vzoru z data.py rutiny pro zapis ceniku vcetne vzoru pro Milana
- při pocitani uzaverky musíš byt jediny zivy terminal (takze zadny ucet neni blokovan), nevim jestli je to nutne
- v ceniku je pos_pc resp. pos_mb misto prostého tuple list[tuple], coz umozni jednu položku umistit na libovolny počet stranek pomoci jednoho zaznamu v ceniku
- je-li vybran nejaky počet kusu (nejvic leve pole v položkách uctu), musíš při skrtani zprava do leva kontrolovat, aby vybrany počet kusu nebyl vetsi nez namarkovany
- zkontroluj, tisknou-li se bony do kuchyne nejen při operaci uloz, ale i při split, platba, castecna platba. Proste vždy když se objevi neodeslana položka na ucte. Když namarkujes nejake položky a soucasne nejake vyberes ke stornu a udelas storno polozek tak se opravdu vytisknou bony na neodeslane a storna na jiz předtím odeslane.
- na dlouhy stisk nove namarkovane polozky se pride message ze setupu
- uzaverkoveho reportu pridej seznam otevrenych uctu se sumou
- při uzaverce, když je pusteny jiny terminal, vznikne neosetreny runtimeerror
- cenova hladina jde menit i na odeslanych polozkach
- pri prepnuti defaultni hladiny se prepisi ceny na buttonech
- uzaverka zjevne pocita poslední ucet z minule uzaverky jako první nasledujici (prekryvaji se).
- serad uzavrene ucty v menu accountselect dle cisla uctu
- nabizej jen cisla uctu po poslední uzaverce
- ma generovani noveho cisla na serveru ochranu proti preteceni? Ma. Vyreseno.
- dodelana ochrana proti prihlaseni stejne id_ka se stejnym clientem, po logoutu se odstarni heartbeats
- uzaverka musí mit číslo uzaverky pro konkretni id_kas
- vylepsit popisky buttonu uctu jak v accountselect tak v closed_accountselect
- proved kontrolu odblokovani uctu po stornech (v closed_accountdialogu se to neobjevi, je to další atribut)
- funguje storno vcetne ochrany druhého stornovani a stornovani storna, funguje barevne rozliseni storen a stornovanych uctu
- hotova operace tisk kopie uctu, na serveru a v api_call je operace unblock dle ucisla
- operace v menu accountselect ted konci v controlleru v handle_closed_accountselect, zbyva dodělat jen z nej volane funkce
- spolecna rodicovska trida pro accountselect a closed_accountselect, na server pridano do jsonu uctu total_base_currency (pri sumdph spocte total v zakladni mene)
- hotove zobrazeni zaplacenych uctu k operacim v menu z accountselect
- pridan info pasek na TOP Posdialogu
- změna v refreshi accountselect, ted bezi porad dokud je activni i pod jinými okny
- do menu konfiguracni parametry (config.json) a take tisk vlastní IP adresy
- format uctu snesitelny
- doplneny všechny informace do uzavrenych uctu
- tisk uctu z Windows a RPI Debian na TCP printer funguje
- konfiguracni parametry (Android, Debian, Windows) -> vyzkouseno Windows, RPI Debian
- zablokovany ucet ma jinou barvu
- hotovy preview uctu
pouziva virtualni prostředí v_kivy nebo v311 na kivy UI (nutne zejmena na RPI debianu)
spousteni primo pod pythonem
v_kivy/Scripts/python.exe
idealni Python je v 3.11
pouzite externi moduly:
pydantic, pillow, qrcode, requests, fastapi, uvicorn, kivy
Zakladni princip.
Klient komunikuje s API serverem pomoci API callu. Všechny API calls jsou ulozeny v api_call.py
Je tam i mechanizmus pridelovani tokenu, autentifikace a identifikace privatnich tabulek zakazky.
System rozeznava dva typy prihlaseni. 1. k zakazce, jmenem a heslem. Při uspesnem prihlaseni
dostane klient od servera token a refresh token, který jiz obsahuje i identifinaci zakazky a tim
i pristup k privatnim tabulkam zakazky. Klient provádí heartbeet (na jinem socketu), server ho zapisuje do centralni
tabulky heartbeetu s aktuálním casem. Pokud server narazi na lock uctu, zkontroluje heartbeet (id_kas a
terminalu) kasy, pokud je expirovan, nebere ho v úvahu.
Aplikace pouziva stale stejne 2 sockety, dokud nevypadnou na timeout nebo je server nezahodi. Prvni je
pro heartbeet, druhy pro chod frontendu (i ten provadi periodicke dotazy na otevrene ucty, pokud pokladna
neni odhlasena (cisnik)
Dale se po startu prihlasuje cisnik (operator), jen PINem. Cisnik/user pak dostane seznam tzv. permitu, ktere
jsou pouzity pro autentifikaci operaci s omezenym pristupem. Funguje klasicke denni heslo Alta.
Nektere operace serveru defaultne lockuji ucty (v parametrech lze potlacit)
V budoucnu se na serveru da do extra modulu vse co souvisi s pristupem k DB (nyní SQLite), takze
budou v modulech striktne oddeleny endpointy (routy) od DB operaci.
Klient je rozdelen na spousteci modul, který obsahuje tridu controller. Zde je veskera obchodni
logika, ktera pracuje s daty ve forme, ve ktere jsou ulozeny na serveru. Server i klient pouzivaji stejny modul
data, kde jsou zakladni datove modely (cenik, ucet, setup...)
Z controlleru se vola modul accountselect (zde se zobrazuji otevrene ucty a zakladni menu pokladny, ve vrchni
casti je bud widget s otevrenymi stoly nebo mapa stolu zobrazene mistnosti.
Jeden stul muze byt zobrazen v nekolika mistnostech a mapa muze byt spolecna pro vice pokladen
Zatim neuzvazuji o sdileni stolu jednotlivymi pokladnami
Při vybrani uctu se z modulu posdialog instancuje trida posdialog. Ta pracuje s upravenymi definicemi
uctu, které jsou rozsireny o vlastnosti umožňující editaci (nektere jiné vlatnosti zase chybi)
Po ukonceni posdialogu se do controlleru vraci u_main, u_sec, a operace. Typicky u_main obsahuje
zbytek původního uctu po operaci, u_sec položky vybrane k operaci.
Po provedeni operace controller pomoci api calls ulozi novy stav na server.
Nektere operace (platba, prevody mezi ucty...), volaji další okna (widgety), jako například payment.
V soucasne chvili klient nepouziva k persistenci nic jiného, nez serverove API calls. Tzn., pokud se něco stane,
po restartu klienta dostaneme stav pred začátkem rozpracovane operace.
V budoucnu predpokladam po x vteřinách update rozpracovane operace na server. To ale trochu koliduje
s pravidlem, ze jediny kdo operuje s daty na serveru je controller. Nicmene Posdialog není modalni, nebude
to takovy problem screen managerem pustit update stavu. V podstate jde je o rozmarkovany ucet, mozna vyber
v posdialogu.
Krome accountselect dialogu pro vybrani otevreneho uctu existuje jeste closed_accountselect dialog, ktery
slouzi k vyberu uzavrenych uctu (kopie, storna apod.) Oba tyto dialogy maji stejneho rodice.
V soucasne chvili je vyzkousena portace na Windows, Linux (jak 86 tak ARM procesory) a MacOS. System vcetne servera dobře funguje na
Rabsberry, napr. P400. Samozrejme je vyzkousen i provoz přes HotSpot a VPN. Server na Hluce, klient v Motole.
Pro Android je s Kivy k dispozici dozer, jeste jsem nezkousel.
Ve chvili, kdy zacnes editovat ucet ktery neexistuje se serveru vytvori dummy a zablokuje se. Tim se predchazi aby nedoslo k soucasne
editaci stejneho noveho uctu.
ToDo:
- vyhazej ze serveru endpointy pro users, které nejsou potřeba
- rozdelit server na endpointy a DB operace
- implementuj pojmenovane stuly
- automaticky test
-vice pokladen, vice terminalu
-blokace, release
-relogin, refresh token
-start s novou databazi
- zkus jinou databazi (postgres nebo DuckDB)
- parametricky prohozeni ucty a matici v PosDialog z prave strany doleva nebo nahore a dole (vzajemne se prekryvajici)
+56
View File
@@ -0,0 +1,56 @@
#Konstanty
from kivy.metrics import dp
SCREEN_LOGIN = "login"
SCREEN_LOGIN_USER = "login_user"
SCREEN_ACCOUNT = "account"
SCREEN_POS = "pos"
MAX_LOGIN_FAILS = 3 # nebo treba 5
DEFAULT_CONFIG = {
"user": "SYSTEM",
"base_url": "http://127.0.0.1:8000",
"refresh_url": "http://127.0.0.1:8000/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "",
"bon_printer1": "",
"bon_printer2": "",
}
# --- ACCOUNTSELECT
COLS = 7
BTN_W = dp(150)
BTN_H = dp(120)
SP = dp(10)
PAD = dp(10)
#posdialog
# ---- POSDIALOG parametry buttonu matice polozek
MENU_COLS = 4
MENU_ROWS = 5
ACC_BTN_W = dp(110)
MENU_BTN_W = dp(70)#dp(130)
MENU_BTN_H = dp(90)
BOTTOM_BTN_H = dp(60)
# --- parametry uctu
ACC_QTY_W = dp(64) # pro "999/2" (6 znaků + rezerva)
ACC_PRICE_W = dp(88) # cena
ACC_ROW_H = dp(44)
API_TIMEOUT = 5
API_RETRIES = 1
API_DELAY = 0.5
HEART_BEAT:int = 10
# -------------------------------
# --- zaznam v databazi uzivatelu
#--------------------------------
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://127.0.0.1:8000",
"refresh_url": "http://127.0.0.1:8000/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+10
View File
@@ -0,0 +1,10 @@
{
"base_url": "http://127.0.0.1:8000",
"username": "Kobrle",
"password": "heslo",
"user": "SYSTEM",
"id_kas": "07",
"client_id": "print-agent-07",
"agent_id": "print-agent-07",
"printers": ""
}
+262
View File
@@ -0,0 +1,262 @@
import argparse
import getpass
import json
import logging
import os
import socket
import sys
import time
from pathlib import Path
os.environ.setdefault("KIVY_NO_ARGS", "1")
import requests
import data
from server_sqlite import process_print_job
logger = logging.getLogger("local_print_agent")
class AgentApiClient:
def __init__(
self,
*,
base_url: str,
client_id: str,
id_kas: str,
username: str,
password: str,
user: str = "",
):
self.base_url = base_url.rstrip("/")
self.client_id = client_id
self.id_kas = id_kas
self.username = username
self.password = password
self.user = user
self.token = ""
self.refresh_token = ""
self.session = requests.Session()
def _headers(self) -> dict:
headers = {"X-Client-ID": self.client_id}
if self.token:
headers["Authorization"] = f"Bearer {self.token}"
return headers
def _refresh_access_token(self) -> bool:
if not self.refresh_token:
return False
response = self.session.post(
f"{self.base_url}/refresh/",
headers={"X-Client-ID": self.client_id},
json={"refresh_token": f"Bearer {self.refresh_token}"},
timeout=10,
)
if response.status_code >= 400:
return False
payload = response.json()
self.token = payload.get("access_token") or payload.get("token") or self.token
return bool(self.token)
def request(self, method: str, endpoint: str, **kwargs):
kwargs.setdefault("timeout", 30)
kwargs["headers"] = {**kwargs.pop("headers", {}), **self._headers()}
response = self.session.request(method, f"{self.base_url}{endpoint}", **kwargs)
if response.status_code == 401 and self._refresh_access_token():
kwargs["headers"] = {**kwargs.pop("headers", {}), **self._headers()}
response = self.session.request(method, f"{self.base_url}{endpoint}", **kwargs)
if response.status_code >= 400:
try:
detail = response.json()
except ValueError:
detail = response.text
raise RuntimeError(f"{method} {endpoint} failed: {detail}")
try:
return response.json()
except ValueError:
return response.text
def login(self) -> tuple[str, str]:
response = self.request(
"POST",
"/login/",
json={
"username": self.username,
"password": self.password,
"id_kas": self.id_kas,
},
)
self.token = response["access_token"]
self.refresh_token = response["refresh_token"]
return response.get("version_API", ""), response.get("database_name", "")
def claim_jobs(self, *, agent_id: str, printers: list[str], limit: int) -> list[data.PrintJob]:
payload = data.PrintJobClaimRequest(
id_kas=self.id_kas,
agent_id=agent_id,
printers=printers,
limit=limit,
).model_dump(mode="json")
response = self.request("POST", "/print/jobs/claim/", json=payload)
return [data.PrintJob.model_validate(item) for item in response]
def update_job_status(
self,
job_id: int,
status: str,
*,
result: dict | None = None,
error: str = "",
) -> data.PrintJob:
payload = data.PrintJobStatusUpdate(
status=status,
result=result or {},
error=error or "",
).model_dump(mode="json")
response = self.request("POST", f"/print/jobs/{job_id}/status", json=payload)
return data.PrintJob.model_validate(response)
def _csv(value: str | None) -> list[str]:
return [item.strip() for item in str(value or "").split(",") if item.strip()]
def _load_config(path: str | None) -> dict:
if not path:
return {}
config_path = Path(path)
if not config_path.exists():
raise FileNotFoundError(f"Konfiguracny subor neexistuje: {config_path}")
return json.loads(config_path.read_text(encoding="utf-8"))
def _arg_value(args, config: dict, name: str, default=None):
value = getattr(args, name, None)
if value is not None and value != "":
return value
return config.get(name, default)
def build_client(args, config: dict) -> AgentApiClient:
password = _arg_value(args, config, "password", "")
if not password:
password = getpass.getpass("Heslo zakazky: ")
return AgentApiClient(
user=str(_arg_value(args, config, "user", "") or ""),
base_url=str(_arg_value(args, config, "base_url", "") or "").rstrip("/"),
client_id=str(_arg_value(args, config, "client_id", "") or ""),
id_kas=str(_arg_value(args, config, "id_kas", "") or ""),
username=str(_arg_value(args, config, "username", "") or ""),
password=str(password),
)
def process_once(
client: AgentApiClient,
*,
agent_id: str,
printers: list[str],
limit: int,
timeout: float,
) -> list[data.PrintJob]:
claimed = client.claim_jobs(
agent_id=agent_id,
printers=printers,
limit=limit,
)
processed: list[data.PrintJob] = []
for job in claimed:
try:
client.update_job_status(job.id, "printing")
result = process_print_job(job, timeout=timeout)
processed.append(
client.update_job_status(
job.id,
"printed",
result=result,
)
)
logger.info("Vytlaceny job %s printer=%s", job.id, job.printer_no)
except Exception as exc:
next_status = "failed_final" if job.attempts >= job.max_attempts else "retry_pending"
logger.exception("Tlac jobu %s zlyhala, status=%s", job.id, next_status)
processed.append(
client.update_job_status(
job.id,
next_status,
error=str(exc),
)
)
return processed
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Lokalny print agent pre Pokladna print_jobs frontu.",
)
parser.add_argument("--config", help="Volitelny JSON konfiguracny subor.")
parser.add_argument("--base-url", dest="base_url", help="URL aplikacneho servera, napr. http://server:8000")
parser.add_argument("--username", help="Login zakazky.")
parser.add_argument("--password", help="Heslo zakazky. Ak chyba, agent sa opyta v konzole.")
parser.add_argument("--user", default="", help="Meno pouzivatela pre kontext API.")
parser.add_argument("--id-kas", dest="id_kas", help="Cislo pokladne pouzite pri prihlaseni agenta.")
parser.add_argument("--client-id", dest="client_id", default="", help="Client/terminal id agenta.")
parser.add_argument("--agent-id", dest="agent_id", default="", help="Identifikator agenta vo fronte.")
parser.add_argument("--printers", default="", help="Ciarkou oddeleny zoznam prn_no, ktore agent obsluhuje. Prazdne znamena vsetky tlaciarne zakazky.")
parser.add_argument("--interval", type=float, default=2.0, help="Pauza medzi cyklami v sekundach.")
parser.add_argument("--limit", type=int, default=10, help="Max pocet jobov na jeden cyklus.")
parser.add_argument("--timeout", type=float, default=10.0, help="Timeout jednej tlace v sekundach.")
parser.add_argument("--once", action="store_true", help="Spracuje jeden cyklus a skonci.")
parser.add_argument("--log-level", default="INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR"])
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
logging.basicConfig(
level=getattr(logging, args.log_level),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
config = _load_config(args.config)
client = build_client(args, config)
if not client.base_url or not client.username or not client.id_kas:
raise SystemExit("Chyba base-url, username alebo id-kas.")
if not client.client_id:
client.client_id = f"print-agent-{socket.gethostname()}"
agent_id = str(_arg_value(args, config, "agent_id", "") or client.client_id)
printers = _csv(_arg_value(args, config, "printers", ""))
version, database = client.login()
logger.info(
"Print agent prihlaseny: server=%s db=%s login_id_kas=%s agent=%s printers=%s",
version,
database,
client.id_kas,
agent_id,
printers or "*",
)
while True:
processed = process_once(
client,
agent_id=agent_id,
printers=printers,
limit=max(1, min(int(args.limit or 10), 100)),
timeout=max(1.0, float(args.timeout or 10.0)),
)
if processed:
logger.info("Spracovanych jobov: %s", len(processed))
if args.once:
return 0
time.sleep(max(0.2, float(args.interval or 2.0)))
if __name__ == "__main__":
try:
raise SystemExit(main())
except KeyboardInterrupt:
logger.info("Print agent ukonceny.")
raise SystemExit(0)
+146
View File
@@ -0,0 +1,146 @@
{
"app.name": "Pokladna",
"button.ok": "OK",
"button.cancel": "Zrušit",
"button.back": "Zpět",
"button.close": "Zavřít",
"button.pay": "Platba",
"button.pay_selected": "Platba\nvybraného",
"button.save": "Uložit",
"button.search": "Hledat",
"button.code": "Kódem",
"button.loyalty_card": "Věrnostní karta",
"button.print": "Tisk",
"button.printer": "Tiskárna",
"button.execute_payment": "Provést platbu",
"button.receipt_preview": "Předúčet",
"button.send_email": "Poslat e-mailem",
"button.tip": "TIP",
"button.delete": "Smazat",
"button.storno": "Storno",
"login.title": "Přihlášení",
"login.password": "Heslo",
"login.invalid": "Neplatné přihlášení",
"account.bar": "Bar",
"account.open_tables": "Otevřené stoly",
"account.closed_receipts": "Účty",
"account.limits": "Limity",
"pos.action_panel": "Akce",
"pos.payment": "Platba",
"pos.item_storno": "Storno položky",
"pos.transfer_table": "Přesun na stůl",
"pos.message_for_item": "Zpráva pro položku",
"pos.price_level": "Cenová hladina",
"pos.vsetci": "Všichni",
"pos.guest": "Host",
"pos.course": "Chod",
"pos.table": "Stůl",
"pos.add": "Přidat",
"pos.prevod": "Převod",
"pos.prevod_bez_poloziek": "Nejsou vybrané žádné položky k převodu.",
"pos.uver_hlavicka": "Úvěrový záznam - výběr firmy",
"pos.uver_hint": "Hledat firmu, adresu, IČO, DIČ...",
"pos.uver_nova_firma": "Nová firma",
"pos.uver_nenajdena_firma": "Žiadna firma nevyhovuje hľadaniu.",
"pos.uverovy_zaznam": "Úverový záznam",
"pos.uver_firma": "Firma",
"pos.uver_adresa": "Adresa",
"pos.uver_ico": "IČO",
"pos.uver_icdph": "IČ DPH",
"pos.uver_dic": "DIČ",
"pos.uver_akcia": "Akcia",
"pos.uver_schvalil": "Schválil",
"pos.uver_povinne_meno": "Meno firmy je povinné.",
"pos.uver_povinne_schvalil": "Meno schvalovateľa je povinné.",
"pos.recepcia_vyber": "Výber recepcie",
"pos.recepcia_hladanie": "Hľadať recepciu ...",
"pos.recepcia_nenastavene": "Nie je nastavená žiadna recepcia",
"pos.recepcia_prefix": "Prefix",
"pos.recepcia_typ": "Typ",
"pos.recepcia_izba_skupina": "izba/skupina",
"pos.recepcia_hint": "Hľadať izbu, skupinu alebo hosťa...",
"pos.recepcia_cislo_izby": "Číslo izby",
"pos.recepcia_overit_izbu": "Overiť izbu",
"pos.recepcia_karta": "Card",
"pos.recepcia_ziadne_izby": "Žiadne izby na výber.",
"pos.recepcia": "Reception",
"pos.recepcia_no_charge": "Na túto izbu nie je možné naťažovať.",
"pos.recepcia_hladanie_hosta": "Hľadanie hosťa...",
"pos.recepcia_vyber_hosta": "Výber hosťa...",
"pos.recepcia_ziadny_host": "Žiadny hosť.",
"pos.hladat_polozku": "Hľadať položku...",
"pos.meno_hosta": "Jméno hosta",
"pos.nazov_chodu": "Název chodu",
"pos.sprava_pre": "Správa pre",
"pos.zadat_spravu": "Zadať správu",
"pos.sprava_povinna_musis_vybrat": "Musíte vybrať povinnú správu",
"pos.kasa": "Kasa",
"pos.terminal": "Terminál",
"pos.neplatna_cen_hladina": "Neplatná cenová hladina v nastavení zľavy.",
"pos.zlava": "sleva",
"pos.hladina_id": "hladina id",
"pos.neplatny_param_zlavy_dotaz_st": "Neplatný parameter zľavy pre dotaz_st.",
"pos.nenacitane_firmy": "Firmy pre úverový záznam sa nepodarilo načítať",
"pos.neulozena_firma": "Firmu sa nepodarilo uložiť",
"pos.recepcia_nenacitane": "Recepcie sa nepodarilo načítať",
"pos.recepcia_izby_nenacitane": "Izby sa nepodarilo načítať",
"pos.recepcia_nacitanie_karty": "Nacitanie hotelovej karty",
"pos.recepcia_cislo_karty": "Číslo karty",
"pos.recepcia_neoverena_karta": "Hotelovú kartu sa nepodarilo overiť",
"pos.recepcia_nenacitani_hostia": "Nepodarilo sa načítať hostí z izby",
"pos.recepcia_ziaden_host": "Na izbe sa nenašiel žiadny hosť.",
"pos.nie_je_co_platit": "Nie je čo platiť.",
"pos.def_cen_hlad": "Východzia cenová hladina",
"pos_polozka_xy_nie_je_v_cenniku": "Položka s kódem {code} nebyla nalezena v ceníku.",
"pos.kod_nenajdeny": "Kód nenájdený",
"pos.naozaj_zrusit_editaciu_uctu": "Naozaj chcete zrušiť editáciu účtu?",
"pos.neulozene_zmenu_sa_stratia": "Neuložené zmeny sa stratia",
"button.ano": "Ano",
"button.nie": "Ne",
"pos.zrusit_editaciu": "Zrušiť editáciu",
"pos.vyber_spravu_pre": "Vyber správu pre",
"pos.povinne_cislo_faktury": "Musíte zadať číslo faktúry",
"pos.pohladavka_musi_byt_sama": "Pohľadávku nie je možné kombinovať s inými položkami na jednom účte.",
"pos.uhrada_pohladavky": "Úhrada pohľadávky",
"pos.prazdny_ucet": "Účet je prázdny.",
"pos.nie_je_co_stornovat": "Nie je čo stornovať",
"pos.nic_nevybrane_na_storno": "Nie su vybrané žiadné položky na storno.",
"pos.limit_iba_cely": "Limitový stôl sa dá zaplatiť iba celý naraz.",
"pos.nevybrane_nic_na_platbu": "Nie su vybrané žiadné položky na platbu.",
"pos.vybrane_polozky_sa_nedaju_platit": "Vybrané položky sa nedajú pripraviť na platbu.",
"pos.chyba_cenika": "Chyba ceníku",
"pos.polozkanema_ziadnu_cenu": "Položka nemá žiadnu cenovú hladinu.\nNedá sa nablokovať.",
"pos.nedostupna_pre_hladinu": "Hladina {level} není pro tuto položku dostupná.\nPoužije se první dostupná cena.",
"pos.selected": "Vybráno",
"payment.step_discount": "Sleva / karta",
"payment.step_payments": "Platby",
"payment.amount_due": "K úhradě",
"payment.total": "Celková suma",
"payment.discount": "Sleva",
"payment.after_discount": "Suma po slevě",
"payment.paid": "Zaplaceno",
"payment.remaining": "Zůstává",
"payment.printer": "Tisk",
"payment.copies": "Kopií",
"payment.no_payment_selected": "Nebyla vybrána žádná platba.",
"receipt.title": "Účet",
"receipt.copy": "Kopie účtu",
"receipt.storno": "Storno účtu",
"receipt.payment_change": "Změna druhu platby",
"closed_receipts.title": "Účty",
"closed_receipts.current": "Aktuální účty",
"closed_receipts.date": "Datum",
"closed_receipts.print_copy": "Tisk kopie účtu",
"closed_receipts.change_payment": "Změnit platby",
"closed_receipts.enter_tip": "Zadání TIPu",
"limit.full_payment_only": "Limitový stůl lze zaplatit pouze celý najednou.",
"limit.transfer_mapping": "Přiřazení na limitový stůl",
"limit.courses": "Chody",
"limit.guests": "Hosté / hladiny",
"error.title": "Chyba",
"error.server": "Chyba serveru",
"common.empty": "Prázdné",
"common.none": "Žádné",
"common.yes": "Ano",
"common.no": "Ne"
}
+148
View File
@@ -0,0 +1,148 @@
{
"app.name": "POS",
"button.ok": "OK",
"button.cancel": "Cancel",
"button.back": "Back",
"button.close": "Close",
"button.pay": "Payment",
"button.pay_selected": "Pay\nselected",
"button.save": "Save",
"button.search": "Search",
"button.code": "By code",
"button.loyalty_card": "Loyalty card",
"button.print": "Print",
"button.printer": "Printer",
"button.execute_payment": "Complete payment",
"button.receipt_preview": "Proforma",
"button.send_email": "Send by e-mail",
"button.tip": "TIP",
"button.delete": "Delete",
"button.storno": "Void",
"login.title": "Login",
"login.password": "Password",
"login.invalid": "Invalid login",
"account.bar": "Bar",
"account.open_tables": "Open tables",
"account.closed_receipts": "Receipts",
"account.limits": "Limits",
"pos.action_panel": "Actions",
"pos.payment": "Payment",
"pos.item_storno": "Void item",
"pos.transfer_table": "Move to table",
"pos.message_for_item": "Item message",
"pos.price_level": "Price level",
"pos.vsetci": "All",
"pos.guest": "Guest",
"pos.course": "Course",
"pos.table": "Table",
"pos.add": "Add",
"pos.prevod": "Move",
"pos.prevod_bez_poloziek": "Nejsou vybrané žádné položky k převodu.",
"pos.uver_hlavicka": "Úverový záznam - výber firmy",
"pos.uver_hint": "Hľadať firmu, adresu, IČO, DIČ...",
"pos.uver_nova_firma": "Nová firma",
"pos.uver_nenajdena_firma": "Žiadna firma nevyhovuje hľadaniu.",
"pos.uverovy_zaznam": "Úverový záznam",
"pos.uver_firma": "Firma",
"pos.uver_adresa": "Adresa",
"pos.uver_ico": "IČO",
"pos.uver_icdph": "IČ DPH",
"pos.uver_dic": "DIČ",
"pos.uver_akcia": "Akcia",
"pos.uver_schvalil": "Schválil",
"pos.uver_povinne_meno": "Meno firmy je povinné.",
"pos.uver_povinne_schvalil": "Meno schvalovateľa je povinné.",
"pos.recepcia_vyber": "Výber recepcie",
"pos.recepcia_hladanie": "Hľadať recepciu ...",
"pos.recepcia_nenastavene": "Nie je nastavená žiadna recepcia",
"pos.recepcia_prefix": "Prefix",
"pos.recepcia_typ": "Typ",
"pos.recepcia_izba_skupina": "izba/skupina",
"pos.recepcia_hint": "Hľadať izbu, skupinu alebo hosťa...",
"pos.recepcia_cislo_izby": "Číslo izby",
"pos.recepcia_overit_izbu": "Overiť izbu",
"pos.recepcia_karta": "Karta",
"pos.recepcia_ziadne_izby": "Žiadne izby na výber.",
"pos.recepcia": "Recepcia",
"pos.recepcia_no_charge":"Na túto izbu nie je možné naťažovať.",
"pos.recepcia_hladanie_hosta": "Hľadanie hosťa...",
"pos.recepcia_vyber_hosta": "Výber hosťa...",
"pos.recepcia_ziadny_host": "Žiadny hosť.",
"pos.hladat_polozku": "Hľadať položku...",
"pos.meno_hosta": "Meno hosťa",
"pos.nazov_chodu": "Názov chodu",
"pos.sprava_pre": "Správa pre",
"pos.zadat_spravu": "Zadať správu",
"pos.sprava_povinna_musis_vybrat": "Musíte vybrať povinnú správu",
"pos.kasa": "Kasa",
"pos.terminal": "Terminál",
"pos.neplatna_cen_hladina": "Neplatná cenová hladina v nastavení zľavy.",
"pos.zlava": "discount",
"pos.hladina_id": "hladina id",
"pos.neplatny_param_zlavy_dotaz_st": "Neplatný parameter zľavy pre dotaz_st.",
"pos.nenacitane_firmy": "Firmy pre úverový záznam sa nepodarilo načítať",
"pos.neulozena_firma": "Firmu sa nepodarilo uložiť",
"pos.recepcia_nenacitane": "Recepcie sa nepodarilo načítať",
"pos.recepcia_izby_nenacitane": "Izby sa nepodarilo načítať",
"pos.recepcia_nacitanie_karty": "Nacitanie hotelovej karty",
"pos.recepcia_cislo_karty": "Číslo karty",
"pos.recepcia_neoverena_karta": "Hotelovú kartu sa nepodarilo overiť",
"pos.recepcia_nenacitani_hostia": "Nepodarilo sa načítať hostí z izby",
"pos.recepcia_ziaden_host": "Na izbe sa nenašiel žiadny hosť.",
"pos.nie_je_co_platit": "Nie je čo platiť.",
"pos.def_cen_hlad": "Východzia cenová hladina",
"pos_polozka_xy_nie_je_v_cenniku": "Položka s kódem {code} nebyla nalezena v ceníku.",
"pos.kod_nenajdeny": "Kód nenájdený",
"pos.naozaj_zrusit_editaciu_uctu": "Naozaj chcete zrušiť editáciu účtu?",
"pos.neulozene_zmenu_sa_stratia": "Neuložené zmeny sa stratia",
"button.ano": "Yes",
"button.nie": "No",
"pos.zrusit_editaciu": "Zrušiť editáciu",
"pos.vyber_spravu_pre": "Vyber správu pre",
"pos.povinne_cislo_faktury": "Musíte zadať číslo faktúry",
"pos.pohladavka_musi_byt_sama": "Pohľadávku nie je možné kombinovať s inými položkami na jednom účte.",
"pos.uhrada_pohladavky": "Úhrada pohľadávky",
"pos.prazdny_ucet": "Účet je prázdny.",
"pos.nie_je_co_stornovat": "Nie je čostornovať",
"pos.nic_nevybrane_na_storno": "Nie su vybrané žiadné položky na storno.",
"pos.limit_iba_cely": "Limitový stôl sa dá zaplatiť iba celý naraz.",
"pos.nevybrane_nic_na_platbu": "Nie su vybrané žiadné položky na platbu.",
"pos.vybrane_polozky_sa_nedaju_platit": "Vybrané položky sa nedajú pripraviť na platbu.",
"pos.chyba_cenika": "Price list error",
"pos.polozkanema_ziadnu_cenu": "Položka nemá žiadnu cenovú hladinu.\nNedá sa nablokovať.",
"pos.nedostupna_pre_hladinu": "Hladina {level} nie je pre túto položku dostupná.\nPoužije sa prvá dostupná cena.",
"pos.selected": "Selected",
"payment.title": "Payment",
"payment.step_discount": "Discount / card",
"payment.step_payments": "Payments",
"payment.amount_due": "Amount due",
"payment.total": "Total",
"payment.discount": "Discount",
"payment.after_discount": "After discount",
"payment.paid": "Paid",
"payment.remaining": "Remaining",
"payment.printer": "Print",
"payment.copies": "Copies",
"payment.no_payment_selected": "No payment was selected.",
"receipt.title": "Receipt",
"receipt.copy": "Receipt copy",
"receipt.storno": "Receipt void",
"receipt.payment_change": "Payment type change",
"closed_receipts.title": "Receipts",
"closed_receipts.current": "Current receipts",
"closed_receipts.date": "Date",
"closed_receipts.print_copy": "Print receipt copy",
"closed_receipts.change_payment": "Change payments",
"closed_receipts.enter_tip": "Enter TIP",
"limit.full_payment_only": "A limit table can only be paid in full.",
"limit.transfer_mapping": "Limit table mapping",
"limit.courses": "Courses",
"limit.guests": "Guests / price levels",
"error.title": "Error",
"error.server": "Server error",
"common.empty": "Empty",
"common.none": "None",
"common.yes": "Yes",
"common.no": "No"
}
+153
View File
@@ -0,0 +1,153 @@
{
"app.name": "Pokladňa",
"button.ok": "OK",
"button.cancel": "Zrušiť",
"button.back": "Späť",
"button.close": "Zavrieť",
"button.pay": "Platba",
"button.pay_selected": "Platba\nvybraného",
"button.save": "Uložiť",
"button.search": "Hľadať",
"button.code": "Kódom",
"button.loyalty_card": "Vernostná karta",
"button.print": "Tlač",
"button.printer": "Tlačiareň",
"button.execute_payment": "Vykonať platbu",
"button.receipt_preview": "Predúčet",
"button.send_email": "Poslať e-mailom",
"button.tip": "TIP",
"button.delete": "Zmazať",
"button.storno": "Storno",
"login.title": "Prihlásenie",
"login.password": "Heslo",
"login.invalid": "Neplatné prihlásenie",
"account.bar": "Bar",
"account.open_tables": "Otvorené stoly",
"account.closed_receipts": "Účty",
"account.limits": "Limity",
"pos.action_panel": "Akcie",
"pos.payment": "Platba",
"pos.item_storno": "Storno položky",
"pos.transfer_table": "Presun na stôl",
"pos.message_for_item": "Správa pre položku",
"pos.vsetci": "Všetci",
"pos.price_level": "Cenová hladina",
"pos.guest": "Hosť",
"pos.course": "Chod",
"pos.table": "Stôl",
"pos.add": "Pridať",
"pos.prevod": "Prevod",
"pos.prevod_bez_poloziek": "Nie su vybrané žiadne položky na prevod.",
"pos.uver_hlavicka": "Úverový záznam - výber firmy",
"pos.uver_hint": "Hľadať firmu, adresu, IČO, DIČ...",
"pos.uver_nova_firma": "Nová firma",
"pos.uver_nenajdena_firma": "Žiadna firma nevyhovuje hľadaniu.",
"pos.uverovy_zaznam": "Úverový záznam",
"pos.uver_firma": "Firma",
"pos.uver_adresa": "Adresa",
"pos.uver_ico": "IČO",
"pos.uver_icdph": "IČ DPH",
"pos.uver_dic": "DIČ",
"pos.uver_akcia": "Akcia",
"pos.uver_schvalil": "Schválil",
"pos.uver_povinne_meno": "Meno firmy je povinné.",
"pos.uver_povinne_schvalil": "Meno schvalovateľa je povinné.",
"pos.uver_povinne_meno": "Meno firmy je povinné.",
"pos.recepcia_vyber": "Výber recepcie",
"pos.recepcia_hladanie": "Hľadať recepciu ...",
"pos.recepcia_nenastavene": "Nie je nastavená žiadna recepcia",
"pos.recepcia_prefix": "Prefix",
"pos.recepcia_typ": "Typ",
"pos.recepcia_izba_skupina": "izba/skupina",
"pos.recepcia_hint": "Hľadať izbu, skupinu alebo hosťa...",
"pos.recepcia_cislo_izby": "Číslo izby",
"pos.recepcia_overit_izbu": "Overiť izbu",
"pos.recepcia_karta": "Karta",
"pos.recepcia_ziadne_izby": "Žiadne izby na výber.",
"pos.recepcia": "Recepcia",
"pos.recepcia_no_charge":"Na túto izbu nie je možné naťažovať.",
"pos.recepcia_hladanie_hosta": "Hľadanie hosťa...",
"pos.recepcia_vyber_hosta": "Výber hosťa...",
"pos.recepcia_ziadny_host": "Žiadny hosť.",
"pos.hladat_polozku": "Hľadať položku...",
"pos.meno_hosta": "Meno hosťa",
"pos.nazov_chodu": "Názov chodu",
"pos.sprava_pre": "Správa pre",
"pos.zadat_spravu": "Zadať správu",
"pos.sprava_povinna_musis_vybrat": "Musíte vybrať povinnú správu",
"pos.kasa": "Kasa",
"pos.terminal": "Terminál",
"pos.neplatna_cen_hladina": "Neplatná cenová hladina v nastavení zľavy.",
"pos.zlava": "zľava",
"pos.hladina_id": "hladina id",
"pos.neplatny_param_zlavy_dotaz_st": "Neplatný parameter zľavy pre dotaz_st.",
"pos.nenacitane_firmy": "Firmy pre úverový záznam sa nepodarilo načítať",
"pos.neulozena_firma": "Firmu sa nepodarilo uložiť",
"pos.recepcia_nenacitane": "Recepcie sa nepodarilo načítať",
"pos.recepcia_izby_nenacitane": "Izby sa nepodarilo načítať",
"pos.recepcia_nacitanie_karty": "Nacitanie hotelovej karty",
"pos.recepcia_cislo_karty": "Číslo karty",
"pos.recepcia_neoverena_karta": "Hotelovú kartu sa nepodarilo overiť",
"pos.recepcia_nenacitani_hostia": "Nepodarilo sa načítať hostí z izby",
"pos.recepcia_ziaden_host": "Na izbe sa nenašiel žiadny hosť.",
"pos.nie_je_co_platit": "Nie je čo platiť.",
"pos.def_cen_hlad": "Východzia cenová hladina",
"pos_polozka_xy_nie_je_v_cenniku": "Položka s kódem {code} nebyla nalezena v ceníku.",
"pos.kod_nenajdeny": "Kód nenájdený",
"pos.naozaj_zrusit_editaciu_uctu": "Naozaj chcete zrušiť editáciu účtu?",
"pos.neulozene_zmenu_sa_stratia": "Neuložené zmeny sa stratia",
"button.ano": "Áno",
"button.nie": "Nie",
"pos.zrusit_editaciu": "Zrušiť editáciu",
"pos.vyber_spravu_pre": "Vyber správu pre",
"pos.povinne_cislo_faktury": "Musíte zadať číslo faktúry",
"pos.pohladavka_musi_byt_sama": "Pohľadávku nie je možné kombinovať s inými položkami na jednom účte.",
"pos.uhrada_pohladavky": "Úhrada pohľadávky",
"pos.prazdny_ucet": "Účet je prázdny.",
"pos.nie_je_co_stornovat": "Nie je čostornovať",
"pos.nic_nevybrane_na_storno": "Nie su vybrané žiadné položky na storno.",
"pos.limit_iba_cely": "Limitový stôl sa dá zaplatiť iba celý naraz.",
"pos.nevybrane_nic_na_platbu": "Nie su vybrané žiadné položky na platbu.",
"pos.vybrane_polozky_sa_nedaju_platit": "Vybrané položky sa nedajú pripraviť na platbu.",
"pos.chyba_cenika": "Chyba cenníka",
"pos.polozkanema_ziadnu_cenu": "Položka nemá žiadnu cenovú hladinu.\nNedá sa nablokovať.",
"pos.nedostupna_pre_hladinu": "Hladina {level} nie je pre túto položku dostupná.\nPoužije sa prvá dostupná cena.",
"pos.selected": "Vybrané",
"payment.title": "Platba",
"payment.step_discount": "Zľava / karta",
"payment.step_payments": "Platby",
"payment.amount_due": "K úhrade",
"payment.total": "Celková suma",
"payment.discount": "Zľava",
"payment.after_discount": "Suma po zľave",
"payment.paid": "Zaplatené",
"payment.remaining": "Zostáva",
"payment.printer": "Tlač",
"payment.copies": "Kópií",
"payment.no_payment_selected": "Nebola vybraná žiadna platba.",
"receipt.title": "Účet",
"receipt.copy": "Kópia účtu",
"receipt.storno": "Storno účtu",
"receipt.payment_change": "Zmena druhu platby",
"closed_receipts.title": "Účty",
"closed_receipts.current": "Aktuálne účty",
"closed_receipts.date": "Dátum",
"closed_receipts.print_copy": "Tlač kópie účtu",
"closed_receipts.change_payment": "Zmeniť platby",
"closed_receipts.enter_tip": "Zadanie TIPu",
"limit.full_payment_only": "Limitový stôl sa dá zaplatiť iba celý naraz.",
"limit.transfer_mapping": "Priradenie na limitový stôl",
"limit.courses": "Chody",
"limit.guests": "Hostia / hladiny",
"error.title": "Chyba",
"error.server": "Chyba servera",
"common.empty": "Prázdne",
"common.none": "Žiadne",
"common.yes": "Áno",
"common.no": "Nie"
}
+135
View File
@@ -0,0 +1,135 @@
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.metrics import dp
from kivy.logger import Logger
from kivy.app import App
from kivy.clock import Clock
from ui_utils import _popup_info
import numberpad
from kivy.uix.anchorlayout import AnchorLayout
from kivy.core.window import Window
MAX_LOGIN_FAILS = 3 # nebo treba 5
class LoginUserScreen(Screen):
def __init__(self, controller, on_success, **kwargs):
super().__init__(**kwargs)
self.fail_count = 0
self.controller = controller
self.on_success = on_success
root = BoxLayout(orientation="vertical")
self.lbl_info = Label(
text="Zadej PIN číšníka",
size_hint_y=None,
height=40,
)
self.lbl_error = Label(
text="",
color=(1, 0, 0, 1),
size_hint_y=None,
height=30,
)
root.add_widget(self.lbl_info)
root.add_widget(self.lbl_error)
self.center_layout = AnchorLayout(anchor_x="center", anchor_y="center")
root.add_widget(self.center_layout)
self.pad = None
self._keyboard_bound = False
self.add_widget(root)
def _on_key_down(self, window, key, scancode, codepoint, modifiers):
if self.manager and self.manager.current != self.name:
return False
if self.pad:
if key == 27:
self.pad._cancel()
return True # 🔥 zabráni zatvoreniu appky
if key in range(48, 58): # top row
self.pad._press(chr(key))
return True
elif key in range(256, 266): # numpad
self.pad._press(str(key - 256))
return True
elif chr(key)=='*' or key == 268:
self.pad._press("*")
return True
elif chr(key)=='.' or key == 266:
self.pad._press(".")
return True
elif key > 300:
print("pass")
pass
return True
elif codepoint and (codepoint.isalpha() or codepoint.isdigit()):
self.pad._press(codepoint)
return True
elif key == 8:
self.pad._press('')
return True
elif key in (13, 40, 10, 271): # Enter / Numpad Enter
self.pad._accept()
return True
else:
return False
else:
return False
def on_enter(self):
self._bind_keyboard()
self.lbl_error.text = ""
if self.pad:
self.center_layout.remove_widget(self.pad)
self.pad = numberpad.NumberPad(
mode="code",
max_len=10,
on_accept=self._accept,
on_cancel=self._cancel,
)
self.pad.size_hint = (None, None)
self.pad.size = (dp(300), dp(400))
self.center_layout.add_widget(self.pad)
def on_leave(self):
self._unbind_keyboard()
if self.pad:
self.center_layout.remove_widget(self.pad)
self.pad = None
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 _accept(self, pin: str):
try:
self.controller.login_user(pin)
# úspěch → reset čítače
self.fail_count = 0
Logger.info("LOGIN USER OK")
self.on_success()
except Exception as e:
self.fail_count += 1
Logger.warning(
f"LOGIN FAILED ({self.fail_count}): {type(e).__name__}: {e}"
)
self.pad.clear()
# příliš mnoho pokusů → konec aplikace
if self.fail_count >= MAX_LOGIN_FAILS:
Logger.error("MAX LOGIN FAILS REACHED")
_popup_info("Chyba", "Příliš mnoho chybných pokusů.\nAplikace bude ukončena.")
Clock.schedule_once(lambda *_: App.get_running_app().stop(), 2)
def _cancel(self):
# v POS obvykle znamená ukončení aplikace
Logger.info("LOGIN CANCEL")
App.get_running_app().stop()
+1575
View File
File diff suppressed because it is too large Load Diff
+10481
View File
File diff suppressed because it is too large Load Diff
+730
View File
@@ -0,0 +1,730 @@
from kivy.metrics import dp
from kivy.graphics import Color, RoundedRectangle
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.metrics import dp, Metrics
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen
from kivy.logger import Logger
from ui_utils import ucet_ui_type, UCET_COLORS
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from ui_utils import _popup_info
import time
import data
MAP_H = 900
MAP_W = 1400
# =====================================================
# TABLE WIDGET (jede nad data.Table)
# =====================================================
class TableWidget(Widget):
def __init__(self, table: data.Table, state=None, on_press=None, **kwargs):
super().__init__(**kwargs)
self.table = table
self.state = state
self.on_press = on_press
self.edit_mode = False
self.dragging = False
self._last_touch_time = 0
with self.canvas.before:
self.color = Color(1, 1, 1, 1)
self.rect = RoundedRectangle(
pos=self.pos,
size=self.size,
radius=[(0, 0)] * 4
)
self.label = Label(
text="",
color=(1, 1, 1, 1),
halign="center",
valign="middle",
font_size=dp(16),
)
self.add_widget(self.label)
self.bind(pos=self._update, size=self._update)
self._update_color()
self._update_text()
# ---------------- COLOR ----------------
def _update_color(self):
u = self.state
if not u:
self.color.rgba = UCET_COLORS["normal"]
return
ui_type = ucet_ui_type(u)
self.color.rgba = UCET_COLORS.get(ui_type, UCET_COLORS["open"])
# ---------------- TEXT ----------------
def _update_text(self):
u = self.state
txt = f"{self.table.name}\n[{self.table.id}]"
if u:
if u.blocked_by:
txt += "\nBLOK"
elif not u.closed_at:
txt += "\nOBSAZENO"
self.label.text = txt
# ---------------- DRAW ----------------
def _update(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
r = min(self.width, self.height) * self.table.radius
self.rect.radius = [r, r, r, r]
self.label.pos = self.pos
self.label.size = self.size
self.label.text_size = self.size
# ---------------- CLICK ----------------
def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
return super().on_touch_down(touch)
# ===== POKLADNA =====
if not self.edit_mode:
if self.on_press:
self.on_press(self.table.id)
return True
# ===== EDITOR =====
now = time.time()
# double tap → edit
if now - self._last_touch_time < 0.3:
self.dragging = False
self.open_edit_popup()
return True
self._last_touch_time = now
self.dragging = True
return True
def on_touch_move(self, touch):
if self.edit_mode and self.dragging:
parent = self.parent
if not parent:
return True
new_x = touch.x - self.width / 2
new_y = touch.y - self.height / 2
# clamp
new_x = max(0, min(new_x, parent.width - self.width))
new_y = max(0, min(new_y, parent.height - self.height))
self.pos = (new_x, new_y)
self.table.pos_x = int(new_x / Metrics.density)
self.table.pos_y = int(new_y / Metrics.density)
return True
return super().on_touch_move(touch)
def on_touch_up(self, touch):
if self.dragging:
grid = dp(20)
self.table.pos_x = int((self.table.pos_x // grid) * grid)
self.table.pos_y = int((self.table.pos_y // grid) * grid)
self.dragging = False
return super().on_touch_up(touch)
def _check_id_duplicate(self, instance, value):
new_id = value.strip()
if not new_id:
instance.background_color = (1, 1, 1, 1)
return
old_id = str(self.table.id).strip()
duplicate = False
rooms_with_id = []
try:
provider = self.parent.parent.parent.provider
rooms = provider.get_rooms() if provider else []
except Exception:
rooms = []
for room in rooms:
room_name = getattr(room, "room_name", "Room")
for t in getattr(room, "stoly", []):
if str(t.id).strip() == new_id and new_id != old_id:
duplicate = True
rooms_with_id.append(room_name)
break
if duplicate:
instance.background_color = (1, 0.3, 0.3, 1)
_popup_info("Duplicitní ID",f"ID existuje v: {', '.join(rooms_with_id)}")
else:
instance.background_color = (1, 1, 1, 1)
def open_edit_popup(self):
from kivy.uix.scrollview import ScrollView
# ================= ROOT =================
root = BoxLayout(orientation="vertical", spacing=dp(5), padding=dp(5))
scroll = ScrollView(size_hint=(1, 1))
content = BoxLayout(
orientation="vertical",
spacing=dp(5),
size_hint_y=None
)
content.bind(minimum_height=content.setter("height"))
scroll.add_widget(content)
root.add_widget(scroll)
# ================= INPUTY =================
id_in = TextInput(text=str(self.table.id), multiline=False)
id_in.bind(text=self._check_id_duplicate)
name_in = TextInput(text=self.table.name, multiline=False)
x_in = TextInput(text=str(self.table.pos_x), multiline=False)
y_in = TextInput(text=str(self.table.pos_y), multiline=False)
w_in = TextInput(text=str(self.table.width), multiline=False)
h_in = TextInput(text=str(self.table.height), multiline=False)
r_in = TextInput(text=str(self.table.radius), multiline=False)
def row(label, widget):
r = BoxLayout(size_hint_y=None, height=dp(50))
r.add_widget(Label(text=label, size_hint_x=0.4))
r.add_widget(widget)
return r
content.add_widget(row("ID", id_in))
content.add_widget(row("Název", name_in))
content.add_widget(row("X", x_in))
content.add_widget(row("Y", y_in))
content.add_widget(row("Šířka", w_in))
content.add_widget(row("Výška", h_in))
content.add_widget(row("Radius", r_in))
# ================= POPUP =================
popup = Popup(
title="Edit stolu",
content=root,
size_hint=(0.6, 0.85),
auto_dismiss=False,
)
# ================= HELPERS =================
def _num(txt, default=0):
txt = (txt or "").strip().replace(",", ".")
if not txt:
return default
return float(txt)
# ================= SAVE =================
def save(_):
try:
self.table.id = id_in.text.strip()
self.table.name = name_in.text.strip()
self.table.pos_x = int(_num(x_in.text))
self.table.pos_y = int(_num(y_in.text))
self.table.width = int(_num(w_in.text))
self.table.height = int(_num(h_in.text))
self.table.radius = _num(r_in.text)
self.pos = (
dp(self.table.pos_x),
dp(self.table.pos_y),
)
self.size = (
dp(self.table.width),
dp(self.table.height),
)
self.dragging = False
self._update_text()
self._update()
popup.dismiss()
except Exception as e:
print("Chyba při ukládání:", e)
# ================= DELETE =================
def delete_table(_):
confirm_layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
confirm_layout.add_widget(Label(
text=f"Opravdu smazat stůl?\n\n{self.table.name}",
halign="center"
))
btn_yes = Button(text="ANO smazat")
btn_no = Button(text="Ne")
confirm_layout.add_widget(btn_yes)
confirm_layout.add_widget(btn_no)
confirm_popup = Popup(
title="Smazání stolu",
content=confirm_layout,
size_hint=(0.5, 0.4),
auto_dismiss=False,
)
def do_delete(_):
if self.parent:
self.parent.remove_widget(self)
confirm_popup.dismiss()
popup.dismiss()
btn_yes.bind(on_press=do_delete)
btn_no.bind(on_press=lambda *_: confirm_popup.dismiss())
confirm_popup.open()
# ================= BUTTONY =================
btn_save = Button(text="Uložit", size_hint_y=None, height=dp(50))
btn_cancel = Button(
text="Zrušit",
size_hint_y=None,
height=dp(50),
background_color=(0.6, 0.6, 0.6, 1),
)
btn_delete = Button(
text="Smazat stůl",
size_hint_y=None,
height=dp(50),
background_color=(0.8, 0.2, 0.2, 1),
)
btn_row = BoxLayout(
orientation="horizontal",
spacing=dp(5),
size_hint_y=None,
height=dp(50),
)
btn_row.add_widget(btn_save)
btn_row.add_widget(btn_cancel)
btn_row.add_widget(btn_delete)
root.add_widget(btn_row)
# ================= BINDY =================
btn_save.bind(on_press=save)
btn_delete.bind(on_press=delete_table)
btn_cancel.bind(on_press=lambda *_: popup.dismiss())
popup.bind(on_dismiss=lambda *_: setattr(self, "dragging", False))
popup.open()
# =====================================================
# PROVIDER (vrací data.Room přímo)
# =====================================================
class RoomMapProvider:
def __init__(self, controller=None):
self.controller = controller
self.current_room = None
def set_current_room(self, room_name):
self.current_room = room_name
def get_table_states(self):
if not self.controller:
return {}
ucty = self.controller.load_stoly(closed=False) or []
return {str(u.stul): u for u in ucty}
def get_rooms(self):
if not self.controller or not self.controller.mapa_stolu:
return []
rooms_out = []
for r in self.controller.mapa_stolu.rooms:
# ---------------- ROOM ----------------
if isinstance(r, dict):
room_name = r.get("room_name")
raw_tables = r.get("stoly", [])
else:
room_name = getattr(r, "room_name", None)
raw_tables = getattr(r, "stoly", [])
tables = []
# ---------------- TABLES ----------------
for t in raw_tables:
if isinstance(t, dict):
tables.append(data.Table(
id=t.get("id"),
name=t.get("name"),
pos_x=t.get("pos_x", 0),
pos_y=t.get("pos_y", 0),
width=t.get("width", 100),
height=t.get("height", 100),
radius=t.get("radius", 0.0),
))
else:
# už je to data.Table → použij rovnou
tables.append(t)
rooms_out.append(data.Room(
room_name=room_name,
stoly=tables
))
limit_room = self._limit_room()
if limit_room:
rooms_out.append(limit_room)
return rooms_out
def _limit_room(self):
if not self.controller:
return None
if hasattr(self.controller, "limits_room_enabled") and not self.controller.limits_room_enabled():
return None
active = str(self.current_room or "").strip().lower() == "limity"
if active and hasattr(self.controller, "load_limit_tables"):
limits = self.controller.load_limit_tables(force=True) or []
elif hasattr(self.controller, "cached_limit_tables"):
limits = self.controller.cached_limit_tables() or []
else:
limits = []
if active:
Logger.info(f"RoomMapProvider: refreshing Limity room with {len(limits)} tables")
cols = 4
w = 260
h = 110
sp_x = 24
sp_y = 22
start_x = 30
start_y = 760
tables = []
for idx, item in enumerate(limits):
col = idx % cols
row = idx // cols
tables.append(data.Table(
id=item.table_id,
name=item.name or item.menolimit or item.table_id,
pos_x=start_x + col * (w + sp_x),
pos_y=max(20, start_y - row * (h + sp_y)),
width=w,
height=h,
radius=0.05,
))
return data.Room(room_name="Limity", stoly=tables)
def _fallback_rooms(self):
return [
data.Room(
room_name="Test, mapa neexistuje",
stoly=[
data.Table(
id="1",
name="VIP",
pos_x=150,
pos_y=500,
width=100,
height=100,
radius=1.0,
),
data.Table(
id="2",
name="Bar",
pos_x=380,
pos_y=420,
width=100,
height=100,
radius=0.0,
),
],
)
]
# =====================================================
# MAP WIDGET
# =====================================================
class RoomTableMapWidget(BoxLayout):
def __init__(self, *, provider, on_select, **kwargs):
super().__init__(orientation="vertical", **kwargs)
self.provider = provider
self.on_select = on_select
self.current_room = None
self.scroll = ScrollView(
size_hint=(1, 1),
do_scroll_x=True,
do_scroll_y=True,
bar_width=dp(10),
)
self.map_area = FloatLayout(
size_hint=(None, None),
size=(dp(MAP_W), dp(MAP_H)),
)
self.scroll.add_widget(self.map_area)
self.add_widget(self.scroll)
# ---------------- REFRESH ----------------
def refresh(self):
self.map_area.clear_widgets()
if hasattr(self.provider, "set_current_room"):
self.provider.set_current_room(self.current_room)
rooms = self.provider.get_rooms()
states = self.provider.get_table_states() if hasattr(self.provider, "get_table_states") else {}
if not self.current_room and rooms:
self.current_room = self._get_room_name(rooms[0])
room = next((r for r in rooms if self._get_room_name(r) == self.current_room), None)
if not room:
return
for t in room.stoly:
u = None if getattr(self, "edit_mode", False) else states.get(str(t.id))
widget = TableWidget(
t,
state=u,
on_press=self._select,
)
widget.edit_mode = getattr(self, "edit_mode", False)
widget.size_hint = (None, None)
widget.size = (dp(t.width), dp(t.height))
widget.pos = (
dp(t.pos_x),
dp(t.pos_y)
)
self.map_area.add_widget(widget)
# ---------------- HELPERS ----------------
def _get_room_name(self, room):
return getattr(room, "room_name", None) or getattr(room, "name", "Room")
# ---------------- SELECT ----------------
def _select(self, table_id):
if self.on_select:
self.on_select(table_id)
class MapaEditor(Screen):
def __init__(self, controller, back_screen, **kwargs):
super().__init__(**kwargs)
self.controller = controller
self.back_screen = back_screen
# ===== ROOT =====
root = BoxLayout(orientation="vertical")
# ===== MAPA =====
provider = controller.get_table_map_provider()
self.map_widget = RoomTableMapWidget(
provider=provider,
on_select=None,
)
self.map_widget.edit_mode = True
root.add_widget(self.map_widget)
# ===== TOOLBAR DOLE =====
toolbar = BoxLayout(
size_hint_y=None,
height=dp(70),
spacing=5,
padding=5,
)
btn_add = Button(text="+ Stůl")
btn_add_room = Button(text="+ Místnost")
btn_save_room = Button(text="Uložit\nmístnost")
btn_delete_room = Button(text="Smazat\nmístnost")
btn_load_room = Button(text="Načíst\nmístnost")
btn_save_all = Button(text="Uložit vše\nna server")
btn_back = Button(text="Zpět uložit\njen do paměti")
btn_cancel = Button(text="Zrušit")
# bindy
btn_add.bind(on_press=self._add_table)
btn_add_room.bind(on_press=self._add_room)
btn_save_room.bind(on_press=self._save_room)
btn_delete_room.bind(on_press=self._delete_room)
btn_load_room.bind(on_press=self._load_room_dialog)
btn_save_all.bind(on_press=self._save_all)
btn_back.bind(on_press=self._go_back)
btn_cancel.bind(on_press=self._cancel)
# přidání tlačítek
toolbar.add_widget(btn_add)
toolbar.add_widget(btn_add_room)
toolbar.add_widget(btn_save_room)
toolbar.add_widget(btn_delete_room)
toolbar.add_widget(btn_load_room)
toolbar.add_widget(btn_save_all)
toolbar.add_widget(btn_back)
toolbar.add_widget(btn_cancel)
root.add_widget(toolbar)
# ===== FINAL =====
self.add_widget(root)
# načtení mapy
self.map_widget.refresh()
def _go_back(self, *_):
from kivy.app import App
self._save_room()
app = App.get_running_app()
main = app.root.get_screen(self.back_screen)
if hasattr(main, "map_widget"):
main.map_widget.refresh()
app.root.current = self.back_screen
app.root.remove_widget(self)
def _delete_room(self, *_):
room_name = self.map_widget.current_room
if not room_name:
print("Není vybraná místnost")
return
# ===== potvrzovací dialog =====
layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
layout.add_widget(Label(
text=f"Opravdu smazat místnost?\n\n{room_name}",
halign="center"
))
btn_yes = Button(text="ANO smazat")
btn_no = Button(text="Ne")
layout.add_widget(btn_yes)
layout.add_widget(btn_no)
popup = Popup(
title="Smazání místnosti",
content=layout,
size_hint=(0.5, 0.4),
auto_dismiss=False,
)
def do_delete(_):
rooms = self.controller.mapa_stolu.rooms
# ===== smaž místnost =====
new_rooms = []
for r in rooms:
rn = r["room_name"] if isinstance(r, dict) else r.room_name
if rn != room_name:
new_rooms.append(r)
self.controller.mapa_stolu.rooms = new_rooms
# ===== přepnutí na jinou =====
if new_rooms:
first = new_rooms[0]
self.map_widget.current_room = (
first["room_name"] if isinstance(first, dict) else first.room_name
)
else:
self.map_widget.current_room = None
self.map_widget.refresh()
popup.dismiss()
print(f"Smazána místnost: {room_name}")
btn_yes.bind(on_press=do_delete)
btn_no.bind(on_press=lambda *_: popup.dismiss())
popup.open()
def _add_room(self, *_):
layout = BoxLayout(orientation="vertical", spacing=5, padding=5)
name_in = TextInput(
text="Nová místnost",
multiline=False,
)
btn_ok = Button(text="Vytvořit")
btn_cancel = Button(text="Zrušit")
layout.add_widget(Label(text="Název místnosti"))
layout.add_widget(name_in)
layout.add_widget(btn_ok)
layout.add_widget(btn_cancel)
popup = Popup(
title="Nová místnost",
content=layout,
size_hint=(0.5, 0.4),
auto_dismiss=False,
)
def create(_):
name = name_in.text.strip()
if not name:
return
# ===== kontrola duplicity =====
for r in self.controller.mapa_stolu.rooms:
rn = r["room_name"] if isinstance(r, dict) else r.room_name
if rn == name:
print("Místnost již existuje")
return
new_room = data.Room(
room_name=name,
stoly=[],
)
# ===== přidání =====
self.controller.mapa_stolu.rooms.append(new_room)
# ===== přepnutí =====
self.map_widget.current_room = name
self.map_widget.refresh()
popup.dismiss()
print(f"Vytvořena místnost: {name}")
btn_ok.bind(on_press=create)
btn_cancel.bind(on_press=lambda *_: popup.dismiss())
popup.open()
def _add_table(self, *_):
room = self.map_widget.current_room or "Room"
# ===== existující ID v aktuální místnosti =====
existing_ids = {
str(w.table.id).split("|")[-1]
for w in self.map_widget.map_area.children
}
i = 1
while str(i) in existing_ids:
i += 1
short_id = str(i)
new_id = f"{room}|{short_id}"
# ===== vytvoření stolu =====
t = data.Table(
id=new_id,
name=f"Stůl {short_id}",
pos_x=10,
pos_y=MAP_H - 120,
width=100,
height=100,
radius=0.2,
)
# ===== widget =====
w = TableWidget(t, state=None, on_press=None)
w.edit_mode = True
w.size_hint = (None, None)
w.size = (dp(t.width), dp(t.height))
w.pos = (dp(t.pos_x), dp(t.pos_y))
self.map_widget.map_area.add_widget(w)
w._update()
print(f"Přidán stůl {new_id}")
def _save_room(self, *_):
room_name = self.map_widget.current_room
if not room_name:
print("Není vybraná místnost")
return
tables = []
for w in reversed(self.map_widget.map_area.children):
t = w.table
t.pos_x = int(w.pos[0] / Metrics.density)
t.pos_y = int(w.pos[1] / Metrics.density)
t.width = int(w.size[0] / Metrics.density)
t.height = int(w.size[1] / Metrics.density)
tables.append(t)
for room in self.controller.mapa_stolu.rooms:
room_name_value = room.room_name if hasattr(room, "room_name") else room["room_name"]
if room_name_value == room_name:
if hasattr(room, "stoly"):
room.stoly = tables
else:
room["stoly"] = tables
break
print(f"Uložena místnost: {room_name}")
def _save_all(self, *_):
from ui_utils import _popup_info
try:
self._save_room()
ok, resp = self.controller.save_mapa_stolu()
if ok:
_popup_info("Mapa", "Mapa uložena na server")
else:
_popup_info("Chyba", str(resp))
except Exception as e:
_popup_info("Chyba", str(e))
def _cancel(self, *_):
from kivy.app import App
app = App.get_running_app()
app.root.current = self.back_screen
app.root.remove_widget(self)
def _load_room_dialog(self, *_):
btn_h = dp(50)
# ===== ROOT =====
root = BoxLayout(
orientation="vertical",
spacing=5,
padding=5,
)
popup = Popup(
title="Načíst místnost",
content=root,
size_hint=(0.5, 0.6),
auto_dismiss=False,
)
# FIXNÍ ZPĚT
btn_back = Button(
text="Zpět",
size_hint_y=None,
height=btn_h,
)
btn_back.bind(on_press=popup.dismiss)
root.add_widget(btn_back)
# 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=5,
)
room_list.bind(minimum_height=room_list.setter("height"))
for room in self.controller.mapa_stolu.rooms:
room_name = room["room_name"] if isinstance(room, dict) else room.room_name
btn = Button(
text=room_name,
size_hint_y=None,
height=btn_h,
)
def load_room(instance, name=room_name):
self.map_widget.current_room = name
self.map_widget.refresh()
popup.dismiss()
btn.bind(on_press=load_room)
room_list.add_widget(btn)
scroll.add_widget(room_list)
root.add_widget(scroll)
popup.open()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+430
View File
@@ -0,0 +1,430 @@
BEGIN;
TRUNCATE _zoznam RESTART IDENTITY CASCADE;
INSERT INTO "public"."_zoznam"(
"key" ,"value" ,"typ","var_typ","comment" ,"supervisor","poradie","skupina","var_valid" ,"var_ponuka" ,"var_kontr" ,"comment2","j0","j1","j2","j3","j4","inp_mask","pristup")
VALUES
---
--- CCD
---
(E'ccd_com',E'0',E'param',E'N',E'Com port citacky ciaroveho kodu ',1,1 ,E'CCD ',E'',E'',E'',E'',E'Com port citacky ciaroveho kodu ',E'Com port citacky ciaroveho kodu ',E'',E'',E'',E'',0),
(E'ccd_baud',E'4800',E'param',E'N',E'Baud rate citacky ciaroveho kodu ',1,2 ,E'CCD ',E'',E',19200,9600,4800,2400,1200 ',E'ccd_com;<>;0 ',E'',E'Baud rate citacky ciaroveho kodu ',E'Baud rate citacky ciaroveho kodu ',E'',E'',E'',E'',0),
(E'ccd_bit',E'8',E'param',E'N',E'Dat. bity citacky ciaroveho kodu ',1,3 ,E'CCD ',E'',E',5,6,7,8 ',E'ccd_com;<>;0 ',E'',E'Dat. bity citacky ciaroveho kodu ',E'Dat. bity citacky ciaroveho kodu ',E'',E'',E'',E'',0),
(E'ccd_par',E'0',E'param',E'N',E'Parita citacky ciaroveho kodu ',1,4 ,E'CCD ',E'',E',0,1,2 ',E'ccd_com;<>;0 ',E'',E'Parita citacky ciaroveho kodu ',E'Parita citacky ciaroveho kodu ',E'',E'',E'',E'',0),
(E'ccd_stop',E'1',E'param',E'N',E'Stopbity citacky ciaroveho kodu ',1,5 ,E'CCD ',E'',E',0,1,2 ',E'ccd_com;<>;0 ',E'',E'Stopbity citacky ciaroveho kodu ',E'Stopbity citacky ciaroveho kodu ',E'',E'',E'',E'',0),
---
--- COOK
---
(E'cook_new_interval_color',E'"#228ae6"',E'param',E'C',E'Aká má byť farba nových položiek? ',0,0 ,E'COOK',E'',E'',E'',E'',E'Aká má byť farba nových položiek?',E'Aká má byť farba nových položiek?',E'',E'',E'',E'',0),
(E'cook_new_interval_minutes',E'5',E'param',E'N',E'Ako dlho má byť položka ozačená ako nová? ',0,0 ,E'COOK',E'',E'',E'',E'',E'Ako dlho má byť položka ozačená ako nová?',E'Ako dlho má byť položka ozačená ako nová?',E'',E'',E'',E'',0),
(E'cook_done_expire_minutes',E'5',E'param',E'N',E'Koľko minút sa zobrazuje hotové jedlo?',0,0 ,E'COOK',E'',E'',E'',E'',E'Koľko minút sa zobrazuje hotové jedlo?',E'Koľko minút sa zobrazuje hotové jedlo?',E'',E'',E'',E'',0),
(E'cook_notification_sound_enable',E'.F.',E'param',E'L',E'Zapnúť zvukovú notifikáciu na novú objednávku?',0,0 ,E'COOK',E'',E'',E'',E'',E'Zapnúť zvukovú notifikáciu na novú objednávku?',E'Zapnúť zvuk notifikáciu na novú objednávku?',E'',E'',E'',E'',0),
(E'cook_notification_sound',E'"default"',E'param',E'C',E'Typ zvukovej notifikácie na novú objednávku',0,0 ,E'COOK',E'',E'',E'',E'',E'Typ zvukovej notifikácie na novú objednávku',E'Typ zvuk notifikácie na novú objednávku',E'',E'',E'',E'',0),
---
--- E-SHOP
---
(E'eshop_auto_progress',E'.F.',E'param',E'L',E'Automaticky nastaviť stav eshop objednávky na v procese? ',0,0 ,E'ESHOP',E'',E'',E'',E'',E'Automaticky nastaviť stav eshop objednávky?',E'Automaticky nastaviť stav eshop objednávky?',E'',E'',E'',E'',0),
---
--- FISKAL
---
(E'oznacenie',E'"FOOD600"',E'param',E'C',E'Oznacenie pokladne podla certifikat ',0,3 ,E'FISKAL ',E'',E'',E'',E'',E'Oznacenie pokladne podla certifikat ',E'Oznacenie pokladne podla certifikat ',E'',E'',E'',E'',0),
(E'is_fiskal',E'.T.',E'param',E'L',E'.T. - je to fiskalizovana kasa, .F. nie je fiskalizovana ',1,1 ,E'FISKAL ',E'',E'',E'',E'',E'.T. - je to fiskalizovana kasa, .F. nie je fiskali',E'.T. - je to fiskalizovana kasa, .F. nie je fiskali',E'',E'',E'',E'',0),
(E'fiskal_typ',E'"ALTO"',E'fiskal',E'C',E'Typ fiskalneho modulu "UPOS", "BOWA", "ALTO", "" ',1,2 ,E'FISKAL ',E'',E',ALTO,BOWA,UPOS,ALTO289 ',E'',E'Typ fiskalneho modulu \nHodnoty: \nALTO - alto fiskal, podla stareho zakona sofverovy(DEFAULT NASTAVENIE)\nBOWA - fiskal od firmy BOWA\nUPOS - fiskal od firmy UPOS\n',E'Typ fiskalneho modulu "UPOS", "BOWA", "ALTO", "" ',E'Typ fiskalneho modulu "UPOS", "BOWA", "ALTO", "" ',E'',E'',E'',E'',0),
(E'fiskal_sp_pohladavka',E'"\'@\'"',E'param',E'C',E'Zoznam spartov oznacujuci uhrady pohladavok ',1,25,E'FISKAL ',E'',E'',E'',E'Zoznam špartov, kotré uznačujú skupiny slúžiace na úhradu pohľadávok.',E'Zoznam spartov oznacujuci uhrady pohladavok ',E'Zoznam spartov oznacujuci uhrady pohladavok ',E'',E'',E'',E'multi;spart_kas;@',0),
(E'spart_zaloha',E'"\'@\'"',E'param',E'C',E'Zoznam spartov pre zalohu ',0,22,E'FISKAL ',E'',E'',E'',E'',E'Zoznam spartov pre zalohu ',E'Zoznam spartov pre zalohu ',E'Zoznam spartov pre zalohu ',E'Zoznam spartov pre zalohu ',E'Zoznam spartov pre zalohu ',E'multi;spart_kas;@',0),
(E'spart_obaly',E'"\'@\'"',E'param',E'C',E'Zoznam spartov pre obaly ',0,23,E'FISKAL ',E'',E'',E'',E'',E'Zoznam spartov pre obaly ',E'Zoznam spartov pre obaly ',E'Zoznam spartov pre obaly ',E'Zoznam spartov pre obaly ',E'Zoznam spartov pre obaly ',E'multi;spart_kas;@',0),
(E'ekasa',E'.T.',E'param',E'L',E'eKasa ',0,21,E'FISKAL',E'',E'',E'',E'',E'eKasa',E'eKasa',E'',E'',E'',E'',0),
(E'info_warning_periodicity_seconds',E'"3600"',E'param',E'C',E'Ako často v sekundách zobraziť upozornenia z AFS ?',0,23 ,E'FISKAL',E'',E'',E'',E'',E'Ako často v sekundách zobraziť upozornenia z AFS ?',E'Ako často v sekundách zobraziť upozornenia z AFS ?',E'',E'',E'',E'',0),
(E'error_500001_assume_ekas_registration_failed',E'.t.',E'param',E'L',E'Pri chybe registracie úctu na FS neklásť otázky?',0,0 ,E'FISKAL',E'',E'',E'',E'',E'Pri chybe registracie úctu na FS neklásť otázky?',E'Pri chybe registracie úctu na FS neklásť otázky?',E'',E'',E'',E'',0),
(E'error_500003_assume_card_payment_failed',E'.t.',E'param',E'L',E'Pri chybe komunikácie s term. neklásť otázky?',0,0 ,E'FISKAL',E'',E'',E'',E'',E'Pri chybe komunikácie s term. neklásť otázky?',E'Pri chybe komunikácie s term. neklásť otázky?',E'',E'',E'',E'',0),
(E'payment_allow_without_bill_printing_',E'.f.',E'param',E'L',E'Je mozne uzavriet platbu bez tlace uctu?',0,0 ,E'FISKAL',E'',E'',E'',E'',E'Je mozne uzavriet platbu bez tlace uctu?',E'Je mozne uzavriet platbu bez tlace uctu?',E'',E'',E'',E'',0),
(E'payment_default_print_bill_',E'.t.',E'param',E'L',E'Ak sa netlaci ucet, predvolit v otazke ANO?',0,0 ,E'FISKAL',E'',E'',E'',E'',E'Ak sa netlaci ucet, predvolit v otazke ANO?',E'Ak sa netlaci ucet, predvolit v otazke ANO?',E'',E'',E'',E'',0),
(E'payment_default_email_bill_',E'.f.',E'param',E'L',E'Predvolene posielať účty emailom?',0,0 ,E'FISKAL',E'',E'',E'',E'',E'Predvolene posielať účty emailom?',E'Predvolene posielať účty emailom?',E'',E'',E'',E'',0),
(E'fiskalpro_index_from_cisfplat',E'.f.',E'param',E'L',E'FiskalPRO - posielat ako index platby cisfplat?',0,0 ,E'FISKAL',E'',E'',E'',E'',E'FiskalPRO - posielat ako index platby cisfplat?',E'FiskalPRO - posielat ako index platby cisfplat?',E'',E'',E'',E'',0),
---
--- HOTEL
---
(E'is_hores',E'.F.',E'param',E'L',E'Vazba na HORES (HRMC) ',0,1 ,E'HOTEL',E'',E'',E'',E'',E'Vazba na HORES (HRMC) ',E'Vazba na HORES (HRMC) ',E'',E'',E'',E'',0),
(E'typ_hotel',E'1',E'param',E'N',E'Typ recepcie ',0,2 ,E'HOTEL',E'',E',6,10,17,18,21 ',E'',E'6 - Opera, 10 - Protel, 17 - Hores10,18 - Previo,21 - Mews',E'Typ recepcie ',E'Typ recepcie ',E'',E'',E'',E'',0),
(E'is_uzprenh',E'.F.',E'param',E'L',E'Automat.prenos tržieb do Horesu ',0,3 ,E'HOTEL',E'',E'',E'',E'',E'Automat.přenos tržeb do Horesu ',E'Automat.přenos tržeb do Horesu ',E'',E'',E'',E'',0),
(E'is_uzprenhtimeatr',E'.f.',E'param',E'L',E'.T. - rozne nastavnie rastrov pre rozne casy (tmatr1) -uzav.',0,4 ,E'HOTEL',E'',E'',E'',E'',E'.T. - rozne nastavnie rastrov pre rozne casy (tmat',E'.T. - rozne nastavnie rastrov pre rozne casy (tmat',E'',E'',E'',E'',0),
(E'rastr_hot',E'.F.',E'param',E'L',E'.F. sumárny prenos po rastroch, .T. prenos po položkách ',0,5 ,E'HOTEL',E'',E'',E'',E'',E'',E'',E'',E'',E'',E'',0),
(E'hotel_karta_typ',E'"SALTO"',E'param',E'C',E'Typ kódovania hotelovej karty ',0,6 ,E'HOTEL',E'',E',SALTO,MAG',E'',E'',E'Typ kódovania hotelovej karty',E'Typ kódovania hotelovej karty',E'',E'',E'',E'',0),
(E'a8_scanner',E'.T.',E'param',E'L',E'Čítanie hotelových kariet na A8 ',0,7 ,E'HOTEL',E'',E'',E'',E'',E'Čítanie hotelových kariet na A8',E'Čítanie hotelových kariet na A8',E'',E'',E'',E'',0),
(E'hotel_karta_length',E'14',E'param',E'N',E'Počet znakov hotelovej karty ',0,8 ,E'HOTEL',E'',E'',E'',E'',E'Počet znakov hotelovej karty',E'Počet znakov hotelovej karty',E'',E'',E'',E'',0),
(E'is_horskup',E'.T.',E'param',E'L',E'Naťažovať do Horesu na skupiny ',0,9 ,E'HOTEL ',E'',E'',E'',E'',E'Naťažovať do Horesu na skupiny',E'Naťažovať do Horesu na skupiny',E'',E'',E'',E'',0),
(E'is_zapltiz',E'.T.',E'param',E'L',E'Povolené naťažovanie do Horesu na zaplatené účty.',0,10 ,E'HOTEL',E'',E'',E'',E'',E'Povolené naťažovanie do Horesu na zaplatené účty.',E'Povolené naťažovanie do Horesu na zaplatené účty.',E'',E'',E'',E'',0),
---
--- KARTY
---
(E'karta_baud',E'9600',E'param',E'N',E'Baud rate čítačky hotelových kariet ',1,2 ,E'KARTY ',E'',E',19200,9600,4800,2400,1200 ',E'karty_typ;!EMPT; ',E'',E'Baud rate čítačky hotelových kariet ',E'Baud rate čítačky hotelových kariet ',E'',E'',E'',E'',0),
(E'karta_bit',E'8',E'param',E'N',E'Dat. bity čítačky hotelových kariet ',1,3 ,E'KARTY ',E'',E',5,6,7,8 ',E'karty_typ;!EMPT; ',E'',E'Dat. bity čítačky hotelových kariet ',E'Dat. bity čítačky hotelových kariet ',E'',E'',E'',E'',0),
(E'karta_com',E'0',E'param',E'N',E'Com port čítačky hotelových kariet ',1,4 ,E'KARTY ',E'',E'',E'karty_typ;!EMPT; ',E'',E'Com port čítačky hotelových kariet ',E'Com port čítačky hotelových kariet ',E'',E'',E'',E'',0),
(E'karta_par',E'0',E'param',E'N',E'Parita čítačky hotelových kariet ',1,5 ,E'KARTY ',E'',E',0,1,2 ',E'karty_typ;!EMPT; ',E'',E'Parita čítačky hotelových kariet ',E'Parita čítačky hotelových kariet ',E'',E'',E'',E'',0),
(E'karta_stop',E'1',E'param',E'N',E'Stopbity čítačky hotelových kariet ',1,6 ,E'KARTY ',E'',E',0,1,2 ',E'karty_typ;!EMPT; ',E'',E'Stopbity čítačky hotelových kariet ',E'Stopbity čítačky hotelových kariet ',E'',E'',E'',E'',0),
(E'pwd_baud',E'4800',E'param',E'N',E'Baud rate ctecky hesel ',1,8 ,E'KARTY ',E'',E',19200,9600,4800,2400,1200 ',E'pwd_typ;!EMPT; ',E'',E'Baud rate ctecky hesel ',E'Baud rate ctecky hesel ',E'',E'',E'',E'',0),
(E'pwd_bit',E'8',E'param',E'N',E'Dat. bity ctecky hesel ',1,9 ,E'KARTY ',E'',E',5,6,7,8 ',E'pwd_typ;!EMPT; ',E'',E'Dat. bity ctecky hesel ',E'Dat. bity ctecky hesel ',E'',E'',E'',E'',0),
(E'pwd_com',E'0',E'param',E'N',E'Com port ctecky hesel ',1,10,E'KARTY ',E'',E'',E'pwd_typ;!EMPT; ',E'',E'Com port ctecky hesel ',E'Com port ctecky hesel ',E'',E'',E'',E'',0),
(E'pwd_par',E'0',E'param',E'N',E'Parita ctecky hesel ',1,11,E'KARTY ',E'',E',0,1,2 ',E'pwd_typ;!EMPT; ',E'',E'Parita ctecky hesel ',E'Parita ctecky hesel ',E'',E'',E'',E'',0),
(E'pwd_stop',E'1',E'param',E'N',E'Stopbity ctecky hesel ',1,12,E'KARTY ',E'',E',0,1,2 ',E'pwd_typ;!EMPT; ',E'',E'Stopbity ctecky hesel ',E'Stopbity ctecky hesel ',E'',E'',E'',E'',0),
---
--- OBJEDNAVKA
---
(E'kc_spart',E'"UHF"',E'param',E'C',E'Spart pre polozky s volnou cenou ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Spart pro obecnou korunovou polozku ',E'Spart pro obecnou korunovou polozku ',E'',E'',E'',E'multi;spart_kas;UHF',0),
(E'def_cenhla',E'"1"',E'param',E'C',E'Default úroveň ceny ',0,11,E'OBJEDNAVKA',E'',E'',E'',E'',E'Default úroveň ceny ',E'Default úroveň ceny ',E'',E'',E'',E'',0),
(E'is_sumobj',E'.t.',E'param',E'L',E'sumarizovat objednavky ',0,10,E'OBJEDNAVKA',E'',E'',E'',E'',E'sumarizovat objednavky ',E'sumarizovat objednavky ',E'',E'',E'',E'',0),
(E'is_objnul',E'.F.',E'param',E'L',E'objednavka s nulovou cenou ',0,8 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'objednavka s nulovou cenou ',E'objednavka s nulovou cenou ',E'',E'',E'',E'',0),
(E'is_zapobj',E'.F.',E'param',E'L',E'Objednavka se zapornym poctem ',1,9 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Objednavka se zapornym poctem ',E'Objednavka se zapornym poctem ',E'',E'',E'',E'',0),
(E'is_saldo',E'.T.',E'param',E'L',E'Zobrazovat mezisoucet v objednavce ',0,29,E'OBJEDNAVKA',E'',E'',E'',E'',E'Zobrazovat mezisoucet v objednavce ',E'Zobrazovat mezisoucet v objednavce ',E'',E'',E'',E'',0),
(E'is_fulltch',E'.T.',E'param',E'L',E'Plny popis klapek ',0,30,E'OBJEDNAVKA',E'',E'',E'',E'',E'Plny popis klapek ',E'Plny popis klapek ',E'',E'',E'',E'',0),
---Pokud to bude respektovat nastavení u skupin (E'koef_c_2',E'0.5',E'param',E'N',E'Koeficient ceny polovicni porce ',0,26,E'OBJEDNAVKA',E'',E'',E'',E'',E'Koeficient ceny polovicni porce ',E'Koeficient ceny polovicni porce ',E'',E'',E'',E'',0),
---Pokud to bude respektovat nastavení u skupin (E'koef_c_3',E'0.33',E'param',E'N',E'Koeficient ceny tretinova porce ',0,27,E'OBJEDNAVKA',E'',E'',E'',E'',E'Koeficient ceny tretinova porce ',E'Koeficient ceny tretinova porce ',E'',E'',E'',E'',0),
---Pokud to bude respektovat nastavení u skupin (E'koef_c_4',E'0.25',E'param',E'N',E'Koeficient ceny ctvrtinova porce ',0,28,E'OBJEDNAVKA',E'',E'',E'',E'',E'Koeficient ceny ctvrtinova porce ',E'Koeficient ceny ctvrtinova porce ',E'',E'',E'',E'',0),
(E'des_m_obj',E'0',E'param',E'N',E'Pocet desetin pro kolik v objednavc ',0,31,E'OBJEDNAVKA',E'',E'',E'',E'',E'Pocet desetin pro kolik v objednavc ',E'Pocet desetin pro kolik v objednavc ',E'',E'',E'',E'',0),
(E'is_stolchl',E'.F.',E'param',E'L',E'Sú default cen.hl. pre stoly? ',0,33,E'OBJEDNAVKA',E'',E'',E'',E'',E'Sú default cen.hl. pre stoly? ',E'Sú default cen.hl. pre stoly? ',E'',E'',E'',E'',0),
(E'stob_lim',E'0',E'param',E'N',E'Limit na storno objednavek [min] ',0,50,E'OBJEDNAVKA',E'',E'',E'',E'',E'Limit na storno objednavek [min] ',E'Limit na storno objednavek [min] ',E'',E'',E'',E'',0),
--- přesunuto (E'vah_ident',E'"21"',E'param',E'C',E'Identifikace vazeneho zbozi na car. ',1,51,E'OBJEDNAVKA',E'',E'',E'',E'',E'Identifikace vazeneho zbozi na car. ',E'Identifikace vazeneho zbozi na car. ',E'',E'',E'',E'',0),
--- přesunuto (E'vah_lnplu',E'5',E'param',E'N',E'Delka kodu vazeneho zbozi na car.ko ',1,52,E'OBJEDNAVKA',E'',E'',E'',E'',E'Delka kodu vazeneho zbozi na car.ko ',E'Delka kodu vazeneho zbozi na car.ko ',E'',E'',E'',E'',0),
--- přesunuto (E'vah_lnvah',E'5',E'param',E'N',E'Delka kodu vahy na car.kodu ',1,53,E'OBJEDNAVKA',E'',E'',E'',E'',E'Delka kodu vahy na car.kodu ',E'Delka kodu vahy na car.kodu ',E'',E'',E'',E'',0),
(E'tmatr2',E'"O"',E'param',E'C',E'Hodnota pro interval 2 ',0,16,E'OBJEDNAVKA',E'',E'',E'',E'',E'Hodnota pro interval 2 ',E'Hodnota pro interval 2 ',E'',E'',E'',E'',0),
(E'tmatr1_chl',E'1',E'param',E'N',E'Cenová hladina pre čas.atribút 1 ',0,15,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cenová hladina pre čas.atribút 1 ',E'Cenová hladina pre čas.atribút 1 ',E'',E'',E'',E'',0),
(E'pouzit_cas_posledpol',E'.F.',E'param',E'L',E'Pri doobjednani použiť čas poslednej položky? ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'Pri doobjednani použiť čas posledne doobjednanej položky?',E'Pri doobjednani použiť čas poslednej položky? ',E'Pri doobjednani použiť čas poslednej položky? ',E'Pri doobjednani použiť čas poslednej položky? ',E'Pri doobjednani použiť čas poslednej položky? ',E'Pri doobjednani použiť čas poslednej položky? ',E'',0),
(E'ini_page_n',E'1',E'param',E'N',E'Číslo stranky klapek jako první ',0,7 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Číslo stranky klapek jako první ',E'Číslo stranky klapek jako první ',E'',E'',E'',E'',0),
(E'tmatr4',E'"V"',E'param',E'C',E'Hodnota pro interval 4 ',0,22,E'OBJEDNAVKA',E'',E'',E'',E'',E'Hodnota pro interval 4 ',E'',E'',E'',E'',E'',0),
(E'objednavka_tlacitko1',E'"Hotove"',E'param',E'C',E'Variabilne nastavenie tlacitka 1 v objed. ',0,41,E'OBJEDNAVKA',E'',E'',E'',E'',E'Variabilne nastavenie tlacitka 1 v objed. ',E'Variabilne nastavenie tlacitka 1 v objed. ',E'',E'',E'',E'uni;druhy_pl;Hotove',0),
(E'tmatr2_chl',E'2',E'param',E'N',E'Cenová hladina pre čas.atribút 2 ',0,18,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cenová hladina pre čas.atribút 2 ',E'Cenová hladina pre čas.atribút 2 ',E'',E'',E'',E'',0),
(E'tmatr1_on',E'"00:00:00"',E'param',E'C',E'Cas pro zapnuti intervalu 1 ',0,14,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cas pro zapnuti intervalu 1 ',E'Cas pro zapnuti intervalu 1 ',E'',E'',E'',E'',0),
(E'tmatr2_on',E'"10:00:00"',E'param',E'C',E'Cas zapnuti intervalu 2 ',0,17,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cas zapnuti intervalu 2 ',E'Cas zapnuti intervalu 2 ',E'',E'',E'',E'',0),
(E'def_tmatr',E'"O"',E'param',E'C',E'Default time atribut ',0,12,E'OBJEDNAVKA',E'',E'',E'',E'',E'Default time atribut ',E'Default time atribut ',E'',E'',E'',E'',0),
(E'is_mody',E'.F.',E'param',E'L',E'Používajú sa módy (R,O,V) ',1,32,E'OBJEDNAVKA',E'',E'',E'',E'',E'Používajú sa módy (R,O,V) ',E'Používajú sa módy (R,O,V) ',E'',E'',E'',E'',0),
(E'tmatr1',E'"R"',E'param',E'C',E'Hodnota pro interval 1 ',0,13,E'OBJEDNAVKA',E'',E'',E'',E'',E'Hodnota pro interval 1 ',E'Hodnota pro interval 1 ',E'',E'',E'',E'',0),
(E'def_cenhlastob_lim',E'"1"',E'param',E'C',E'Default úroveň ceny ',0,11,E'OBJEDNAVKA',E'',E'',E'',E'',E'Default úroveň ceny ',E'Default úroveň ceny ',E'',E'',E'',E'',0),
(E'tmatr3_chl',E'3',E'param',E'N',E'Cenová hladina pre čas.atribút 3 ',0,21,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cenová hladina pre čas.atribút 3 ',E'Cenová hladina pre čas.atribút 3 ',E'',E'',E'',E'',0),
(E'tmatr3_on',E'"18:00:00"',E'param',E'C',E'Cas pro zapnuti intervalu 3 ',0,20,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cas pro zapnuti intervalu 3 ',E'Cas pro zapnuti intervalu 3 ',E'',E'',E'',E'',0),
(E'tmatr5_on',E'"23:59:59"',E'param',E'C',E'Cas pro zapnuti intervalu 5 ',0,25,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cas pro zapnuti intervalu 5 ',E'',E'',E'',E'',E'',0),
(E'tmatr5',E'"V"',E'param',E'C',E'Hodnota pro interval 5 ',0,24,E'OBJEDNAVKA',E'',E'',E'',E'',E'Hodnota pro interval 5 ',E'',E'',E'',E'',E'',0),
--- duplicita (E'des_m_obj',E'0',E'param',E'N',E'Pocet desetin pro kolik v objednavc ',0,31,E'OBJEDNAVKA',E'',E'',E'',E'',E'Pocet desetin pro kolik v objednavc ',E'Pocet desetin pro kolik v objednavc ',E'',E'',E'',E'',0),
(E'objednavka_tlacitko3',E'""',E'param',E'C',E'Variabilne nastavenie tlacitka "Platba so zlavou" v objed. ',0,43,E'OBJEDNAVKA',E'',E'',E'',E'',E'Variabilne nastavenie tlacitka "Platba so zlavou" ',E'Variabilne nastavenie tlacitka "Platba so zlavou" ',E'',E'',E'',E'uni;druhy_pl',0),
(E'tmatr3',E'"V"',E'param',E'C',E'Hodnota pro interval 3 ',0,19,E'OBJEDNAVKA',E'',E'',E'',E'',E'Hodnota pro interval 3 ',E'Hodnota pro interval 3 ',E'',E'',E'',E'',0),
(E'objednavka_tlacitko2',E'""',E'param',E'C',E'Variabilne nastavenie tlacitka "Subtotal" v objed. ',0,42,E'OBJEDNAVKA',E'',E'',E'',E'',E'Variabilne nastavenie tlacitka "Subtotal" v objed.',E'Variabilne nastavenie tlacitka "Subtotal" v objed.',E'',E'',E'',E'uni;druhy_pl',0),
(E'tmatr4_on',E'"23:59:58"',E'param',E'C',E'Cas pro zapnuti intervalu 4 ',0,23,E'OBJEDNAVKA',E'',E'',E'',E'',E'Cas pro zapnuti intervalu 4 ',E'',E'',E'',E'',E'',0),
(E'menu_spart',E'"FST"',E'param',E'C',E'Spart oznacujici fastfood menu ',1,3 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Spart oznacujici fastfood menu ',E'Spart oznacujici fastfood menu ',E'',E'',E'',E'uni;spart_kas;FST',0),
(E'obj_fstmenu_radit_sp',E'true',E'param',E'L',E'V objednavke radit a spocitat fst menu podla nastavenia skup',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'V objednavke radit a spocitat fst menu podla nastp',E'V objednavke radit a spocitat fst menu podla nastp',E'V objednavke radit a spocitat fst menu podla nastp',E'',E'',E'',0),
(E'mnusp600',E'"MNU"',E'param',E'C',E'Spart označujúci menu Food600 ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Spart označujúci menu Food600 ',E'Spart označujúci menu Food600 ',E'Spart označujúci menu Food600 ',E'Spart označujúci menu Food600 ',E'Spart označujúci menu Food600 ) ',E'uni;spart_kas;MNU',0),
--- přesunuto (E'hk_sl_spl',E'"XXX"',E'param',E'C',E'Zoznam spartov, na ktoré sa nedáva zľava ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Zoznam spartov, na ktoré sa nedáva zľava',E'Zoznam spartov, na ktoré sa nedáva zľava',E'',E'',E'',E'multi;spart_kas;@',0),
(E'bon_show_prices',E'.T.',E'param',E'L',E'Má bon zobrazovať sumu položky? ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Má bon zobrazovať sumu položky?',E'Má bon zobrazovať sumu položky?',E'',E'',E'',E'',0),
(E'is_stulres_plat',E'.F.',E'param',E'L',E'Má zakázať čašníkovi vyplatiť stôl iného? ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Má zakázať čašníkovi vyplatiť stôl iného?',E'Má zakázať čašníkovi vyplatiť stôl iného?',E'',E'',E'',E'',0),
(E'is_stulres',E'.F.',E'param',E'L',E'Má zakázať čašníkovi meniť položky iného? ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Má zakázať čašníkovi meniť položky iného?',E'Má zakázať čašníkovi meniť položky iného?',E'',E'',E'',E'',0),
(E'binary_revert_card_read',E'.F.',E'param',E'L',E'Majú sa binárne invertovať načítané karty? ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Majú sa binárne invertovať načítané karty?',E'Majú sa binárne invertovať načítané karty?',E'',E'',E'',E'',0),
(E'search_item_size',E'3',E'param',E'N',E'Na koľko polí sa má zobraziť výsledok hľadania? ',0,0 ,E'OBJEDNAVKA',E'',E'1,2,3,4,5,6,7 ',E'',E'Udáva sa na aký násobok 3 sa majú zobraziť klapky po vyhľadávaní. Ak je param väčší ako 7 tak to nastaví na 7',E'Na koľko polí sa má zobraziť výsledok hľadania?',E'Na koľko polí sa má zobraziť výsledok hľadania?',E'',E'',E'',E'',0),
(E'orderline_pager',E'.F.',E'param',E'L',E'Používa sa pager na privolávanie zákazníkov ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Používa sa pager pre zákazníkov?',E'Používa sa pager pre zákazníkov?',E'',E'',E'',E'',0),
(E'auto_chod',E'.F.',E'param',E'L',E'Automaticky natiahnuť chod z položky? ',0,9 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Automaticky natiahnuť chod z položky?',E'Automaticky natiahnuť chod z položky?',E'',E'',E'',E'',0),
(E'is_koef_portion',E'.T.',E'param',E'L',E'Počítať cenu čiastkových porcií koeficientom? ',0,9 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Počítať cenu čiastkových porcií koeficientom?',E'Počítať cenu čiastkových porcií koeficientom?',E'',E'',E'',E'',0),
(E'last_added_item_count',E'3',E'param',E'N',E'Počet zobrazených riadkov v objednávke na mobile ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Počet zobrazených riadkov v objednávke na mobile',E'Počet zobrazených riadkov v objednávke na mobile',E'',E'',E'',E'',0),
(E'order_line_swipeable_group',E'.t.',E'param',E'L',E'Je možné množstvo menit ťahaním prsta? ',0,19,E'OBJEDNAVKA',E'',E'',E'',E'',E'Množstvo je možné meniť cez swipe',E'Množstvo je možné meniť cez swipe',E'',E'',E'',E'',0),
(E'last_added_item_count',E'3',E'param',E'N',E'Počet zobrazených riadkov v objednávke na mobile ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Počet zobrazených riadkov v objednávke na mobile',E'Počet zobrazených riadkov v objednávke na mobile',E'',E'',E'',E'',0),
(E'ean_match_without_prefix_start_digits',E'""',E'param',E'C',E'Čím začína ean predváženého tovaru ak nemá prefix?',0,10 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'',E'Čím začína ean predváženého tovaru ak nemá prefix?',E'',E'',E'',E'',0),
(E'ean_match_without_prefix',E'.f.',E'param',E'L',E'Rozoznávať predvážený tovar bez prefixu?',0,10 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'',E'Rozoznávať predvážený tovar bez prefixu?',E'',E'',E'',E'',0),
(E'locators',E'.f.',E'param',E'L',E'Povolit lokátory / pagers?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Povolit lokátory / pagers?',E'Povolit lokátory / pagers?',E'',E'',E'',E'',0),
(E'last_added_items_change',E'.t.',E'param',E'L',E'Dá sa meniť nerozkliknutá objednávka ťahom?',0,13 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Dá sa meniť nerozkliknutá objednávka ťahom?',E'Dá sa meniť nerozkliknutá objednávka ťahom?',E'',E'',E'',E'',0),
(E'allow_portions_overcount',E'.t.',E'param',E'L',E'Umožniť nablokovať viac porcií ako je dostupných?',0,16 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Umožniť nablokovať viac porcií ako je dostupných?',E'Umožniť nablokovať viac porcií ako je dostupných?',E'',E'',E'',E'',0),
(E'app_order_bottom_buttons_sort',E'"[1,2,3,4,5,6,7,8,9,10,11]"',E'param',E'C',E'Poradie ikoniek pri rozkliknutej objednavke na mobile',0,16 ,E'OBJEDNAVKA',E'',E'',E'',E'plus - 1, porcia - 2, zľava - 3, zmazanie - 4, platba - 5, presun na stôl - 6, cenová hladina - 7, správa - 8, presun na chod - 9, presun na hosťa - 10, vybrať všetko - 11',E'Poradie ikoniek v objednávke na mobile',E'Poradie ikoniek v objednavke na mobile',E'',E'',E'',E'',0),
(E'on_table_close_ask_for_items_delete',E'.T.',E'param',E'L',E'Pýtať sa pri zatvorení stola na mazanie nepotvrdených pol.',0,60 ,E'OBJEDNAVKA',E'',E'',E'',E'Pýtať sa obsluhy pri zatvorení stola na mazanie nepotvrdených položiek',E'Pýtať sa pri zatvorení stola či zmazať nepotvrdené',E'Pýtať sa pri zatvorení stola či zmazať nepotvrdené',E'',E'',E'',E'',0),
(E'on_table_close_delete_items',E'.F.',E'param',E'L',E'Zmazať nepotvrd.pol.pri zatvorení stola(ak sa to nepýtame)',0,61 ,E'OBJEDNAVKA',E'',E'',E'',E'Zmazať nepotvrdené položky pri zatvorení stola (ak sa to nepýtame obsluhy)',E'Zmaž nepotv.pol.pri zatvorení stola(ak sa nepýtame',E'Zmaž nepotv.pol.pri zatvorení stola(ak sa nepýtame',E'',E'',E'',E'',0),
(E'hide_button_chystajchod',E'.F.',E'param',E'L',E'Skryť tlačítko Chystaj chod?',0,62 ,E'OBJEDNAVKA',E'',E'',E'',E'Skryť tlačítko Chystaj chod?',E'Skryť tlačítko Chystaj chod?',E'Skryť tlačítko Chystaj chod?',E'',E'',E'',E'',0),
(E'force_rename_foodcourse',E'.F.',E'param',E'L',E'Pomenovanie chodov pri ich zakladaní?',0,65 ,E'OBJEDNAVKA',E'',E'',E'',E'Pomenovanie chodov pri ich zakladaní?',E'Pomenovanie chodov pri ich zakladaní?',E'Pomenovanie chodov pri ich zakladaní?',E'',E'',E'',E'',0),
(E'alert_on_code_not_found',E'.f.',E'param',E'L',E'Zobraziť výstrahu ak sa nenájde kód?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Zobraziť výstrahu ak sa nenájde kód?',E'Zobraziť výstrahu ak sa nenájde kód?',E'',E'',E'',E'',0),
(E'continue_on_code_not_found',E'.f.',E'param',E'L',E'Pokračovať v objedávke ak sa nenašiel kód?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Pokračovať v objedávke ak sa nenašiel kód?',E'Pokračovať v objedávke ak sa nenašiel kód?',E'',E'',E'',E'',0),
(E'hk_spdob',E'"DOB"',E'param',E'C',E'Spart pre dobitie kreditu',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Spart pre dobitie kreditu',E'Spart pre dobitie kreditu',E'',E'',E'',E'uni;spart_kas;DOB',0),
(E't_bon_txt_pred_spr',E'"Pozn.:"',E'param',E'C',E'Text, ktorý sa ma tlačiť pre správou na bone. ',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Text, ktorý sa ma tlačiť pre správou na bone.',E'Text, ktorý sa ma tlačiť pre správou na bone.',E'',E'',E'',E'',0),
(E'stay_in_order_on_order_confirm_for_all_tables',E'.F.',E'param',E'L',E'Ostať v OBJ po potvrdení položiek (bežné stoly)?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Ostať v OBJ po potvrdení (bežné stoly)?',E'Ostať v OBJ po potvrdení (bežné stoly)?',E'',E'',E'',E'',0),
(E'stay_in_order_on_order_confirm_for_temp_tables',E'.F.',E'param',E'L',E'Ostať v OBJ po potvrdení položiek (jednorázové stoly)?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Ostať v OBJ po potvrdení (jednorázové stoly)?',E'Ostať v OBJ po potvrdení (jednorázové stoly)?',E'',E'',E'',E'',0),
--- tohle asi nepotřebujeme (E'order_autoconfirm_items_in_all_tables',E'.F.',E'param',E'L',E'Automaticky potvrdzovať objednané položky (bežné stoly)?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Automaticky potvrdzovať objednané položky?',E'Automaticky potvrdzovať objednané položky?',E'',E'',E'',E'',0),
--- tohle asi nepotřebujeme (E'order_autoconfirm_items_in_temp_tables',E'.F.',E'param',E'L',E'Automaticky potvrdzovať objed.položky (jednorázové stoly)?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Automaticky potvrdzovať objednané položky?',E'Automaticky potvrdzovať objednané položky?',E'',E'',E'',E'',0),
--- tohle asi nepotřebujeme (E'order_autoconfirm_items_in_bar',E'.F.',E'param',E'L',E'Automaticky potvrdzovať objednané položky (bar)?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Automaticky potvrdzovať objednané položky?',E'Automaticky potvrdzovať objednané položky?',E'',E'',E'',E'',0),
--- tohle asi nepotřebujeme (E'payment_ignore_selected_orderlines',E'.F.',E'param',E'L',E'Vzdy vyplacat cely ucet?',0,0 ,E'OBJEDNAVKA',E'',E'',E'',E'Ignorovat vybrane polozky pri vyplacani ?\ntrue = vzdy bude vyplateny cely ucet\nfalse (default) = ak su vybrane polozky, do plaby pojdu iba vybrane polozky',E'Ignorovat vybrane polozky pri vyplacani',E'Ignorovat vybrane polozky pri vyplacani',E'',E'',E'',E'',0),
(E'is_chlbar',E'.F.',E'param',E'L',E'Dotaz na cenove hladiny v bar.rezim ',0,2 ,E'OBJEDNAVKA',E'',E'',E'',E'',E'Dotaz na cenove hladiny v bar.rezim ',E'Dotaz na cenove hladiny v bar.rezim ',E'',E'',E'',E'',0),
---
--- ROZNE
---
(E'quick_sync_enable_by',E'"table_id"',E'param',E'C',E'Spôsob synchronizácie pre mobilné pokladne',0,1 ,E'ROZNE',E'',E'table_id,last_updated,all ',E'',E'',E'Spôsob synchronizácie pre mobilné pokladne',E'Spôsob synchronizácie pre mobilné pokladne',E'',E'',E'',E'',0),
(E'quick_sync_for_desktop_enable_by',E'"all"',E'param',E'C',E'Spôsob synchronizácie pre desktopové pokladne',0,1 ,E'ROZNE',E'',E'table_id,last_updated,all ',E'',E'',E'Spôsob synchronizácie pre desktopové pokladne',E'Spôsob synchronizácie pre desktopové pokladne',E'',E'',E'',E'',0),
--- Presunuto do objednávky (E'is_chlbar',E'.F.',E'param',E'L',E'Dotaz na cenove hladiny v bar.rezim ',0,2 ,E'ROZNE ',E'',E'',E'',E'',E'Dotaz na cenove hladiny v bar.rezim ',E'Dotaz na cenove hladiny v bar.rezim ',E'',E'',E'',E'',0),
(E'len_nefis_platby',E'.F.',E'param',E'L',E'.T. - na kase sa daju pouzit len nefiskalne platby ',0,3,E'ROZNE ',E'',E'',E'',E'.T. - na kase sa daju pouzit len nefiskalne platby',E'.T. - na kase sa daju pouzit len nefiskalne platb ',E'.T. - na kase sa daju pouzit len nefiskalne platb ',E'.T. - na kase sa daju pouzit len nefiskalne platb ',E'.T. - na kase sa daju pouzit len nefiskalne platb ',E'.T. - na kase sa daju pouzit len nefiskalne platb ',E'',0),
(E'max_suma_kartou',E'-1',E'param',E'N',E'Maximalna suma, ktoru je mozne platit kartou. -1-neobmedzene',0,4,E'ROZNE ',E'',E'',E'',E'Maximalna suma, ktoru je mozne platit kartou. -1-neobmedzene',E'Maximalna suma, ktoru je mozne platit kartou. -1-n',E'Maximalna suma, ktoru je mozne platit kartou. -1-n',E'Maximalna suma, ktoru je mozne platit kartou. -1-n',E'Maximalna suma, ktoru je mozne platit kartou. -1-n',E'Maximalna suma, ktoru je mozne platit kartou. -1-n',E'',0),
(E'max_suma_hotovost',E'-1',E'param',E'N',E'Maximalna suma, ktoru je mozne platit v hotovosti. -1-neobme',0,5,E'ROZNE ',E'',E'',E'',E'Maximalna suma, ktoru je mozne platit v hotovosti. -1-neobmedzene',E'Maximalna suma, ktoru je mozne platit v hotovsti. ',E'Maximalna suma, ktoru je mozne platit v hotovsti. ',E'Maximalna suma, ktoru je mozne platit v hotovsti. ',E'Maximalna suma, ktoru je mozne platit v hotovsti. ',E'Maximalna suma, ktoru je mozne platit v hotovsti. ',E'',0),
(E'desktop_tables_list_columns_count',E'2',E'param',E'N',E'Počet stĺpcov v miestnosti jednorázové stoly',0,6 ,E'ROZNE',E'',E'',E'',E'',E'Počet stĺpcov v miestnosti jednorázové stoly',E'Počet stĺpcov v miestnosti jednorázové stoly',E'',E'',E'',E'',0),
(E'disable_rescale_tables',E'.f.',E'param',E'L',E'Zakázať auto zmenu veľkosti stolov na PC?',0,7 ,E'ROZNE',E'',E'',E'',E'',E'Zakázať auto zmenu veľkosti stolov na PC?',E'Zakázať auto zmenu veľkosti stolov na PC?',E'',E'',E'',E'',0),
(E'sc_saver',E'100000',E'param',E'N',E'Pocet [s] , kdy se odhlasi ',0,8,E'ROZNE ',E'',E'',E'',E'',E'Pocet [s] , kdy se odhlasi ',E'Pocet [s] , kdy se odhlasi ',E'',E'',E'',E'',0),
(E'neodpocitavat_zoz_sp',E'"SRV"',E'param',E'C',E'Zoznam spartov, kt.netreba odpocitavat zo skladov ',0,9,E'ROZNE',E'',E'',E'',E'',E'Zoznam spartov,kt.netreba odpocitavat zo skladov',E'Zoznam spartov,kt.netreba odpocitavat zo skladov',E'',E'',E'',E'multi;spart_kas;@',0),
(E'is_limspra',E'.T.',E'param',E'L',E'Povolené spracovanie limitov ',0,10,E'ROZNE',E'',E'',E'',E'',E'Povolené spracovanie limitov',E'Povolené spracovanie limitov',E'',E'',E'',E'',0),
(E'is_bazen',E'.F.',E'param',E'L',E'Bazénový režim ',0,11,E'ROZNE',E'',E'',E'',E'',E'Bazénový režim',E'Bazénový režim',E'',E'',E'',E'',0),
(E'is_filter',E'.F.',E'param',E'L',E'Rajónový režim ',0,12,E'ROZNE',E'',E'',E'',E'',E'Rajónový režim',E'Rajónový režim',E'',E'',E'',E'',0),
(E'is_filter_hide_others_tables_all',E'.F.',E'param',E'L',E'Rajónový režim - zobraz iba vlastné stoly',0,13,E'ROZNE',E'',E'',E'',E'',E'Rajónový režim - zobraz iba moje stoly',E'Rajónový režim - zobraz iba moje stoly',E'',E'',E'',E'',0),
(E'is_filter_hide_others_tables_temp',E'.F.',E'param',E'L',E'Rajónový režim - zobraz iba vlastné jednorázové stoly',0,14,E'ROZNE',E'',E'',E'',E'',E'Rajónový režim - zobraz iba moje jednoráz. stoly',E'Rajónový režim - zobraz iba moje jednoráz. stoly',E'',E'',E'',E'',0),
(E'change_orderline_ownership_with_opentable', E'.F.', E'param',E'L', E'Rajónový režim - zmena vlastnika stolu aj s obj', 0, 15, E'ROZNE', E'', E'', E'', E'', E'Rajónový režim - zmena vlastnika stolu aj s obj', E'Rajónový režim - zmena vlastnika stolu aj s obj', E'', E'', E'', E'', 0),
(E'is_dan',E'.T.',E'param',E'L',E'Platca DPH ',0,16,E'ROZNE',E'',E'',E'',E'',E'Platca DPH',E'Platca DPH',E'',E'',E'',E'',0),
(E'foodie_font',E'0',E'param',E'N',E'Veľkosť fontu v objednávke ',0,17,E'ROZNE',E'',E'',E'',E'0 - automatická voľba fontu',E'Veľkosť fontu v objednávke',E'Veľkosť fontu v objednávke',E'',E'',E'',E'',0),
(E'print_tip_confirmation',E'.f.',E'param',E'L',E'Tlačí sa potvrdenie pri zemene TIPu?',0,18 ,E'ROZNE',E'',E'',E'',E'',E'Tlačí sa potvrdenie o TIPe pri tlači účtu?',E'Tlačí sa potvrdenie pri zemene TIPu?',E'',E'',E'',E'',0),
(E'print_tip_confirmation_with_every_bill',E'.f.',E'param',E'L',E'Tlačí sa potvrdenie o TIPe pri tlači účtu? ',0,19 ,E'ROZNE',E'',E'',E'',E'',E'Tlačí sa potvrdenie o TIPe pri tlači účtu?',E'Tlačí sa potvrdenie o TIPe pri tlači účtu?',E'',E'',E'',E'',0),
(E'scrollbar_indicator_width',E'5',E'param',E'N',E'Šírka scroll barov. Klasický 5, širší 25. ',0,20 ,E'ROZNE',E'',E'',E'',E'',E'Šírka scroll barov. Klasický 5, širší 25.',E'Šírka scroll barov. Klasický 5, širší 25.',E'',E'',E'',E'',0),
(E'own_delivery_eshop_ids',E'"3"',E'param',E'C',E'Zoznam typov eshopov,kt.sa maju spravat ako vlastna donaska',1,21 ,E'ROZNE',E'',E'',E'',E'',E'Typy eshopu,kt.sa maju spravat ako vlastna donaska',E'Typy eshopu,kt.sa maju spravat ako vlastna donaska',E'',E'',E'',E'',0),
(E'own_delivery_quickpay_methods',E'""',E'param',E'C',E'Vlastna donaska - quickpay platobne metody',0,22 ,E'ROZNE',E'',E'',E'',E'Ciarkami oddelene (max 3) nazvy platieb z druhy_pl, kt. sa pouziju ako quickpay pir vlastnej donaske',E'Donaska - quickpay platobne metody',E'Donaska - quickpay platobne metody',E'',E'',E'',E'',0),
(E'own_delivery_columns',E'""',E'param',E'C',E'Vlastna donaska - stlpce v zozname objednavok',0,23 ,E'ROZNE',E'',E'',E'',E'Ciarkami oddelene oznacenia stlpcov, ktore maju byt v zozname objednavok:\n\ncustomer_name - meno zakaznika\ncustomer_phone - telefonne cislo zakaznika\ncustomer_email - e-mail zakaznika\naddress - adresa\naddress_with_customer_name - meno zakaznika spolu s adresou\naddress_with_customer_name_last - adresa spolu s menom zakaznika na konci\nnotes - vsetky poznamky oddelene znakom ";"\nnote_address - poznamka priradena k adrese\nnote_customer - poznamka priradena k zakaznikovi\nnote_order_all - vsetky poznamky priradene k obejdnavke oddelene znakom ";"\nnote_order_1 - prva poznamka k objednavke\nnote_order_2 - druha poznamka k objednavke\norder_price - cena objedanvky\norder_date - datum objednavky\norder_datetime - datum a cas objednavky\norder_status - stav objednavky\norder_author - casnik, ktory vytvoril objednavku\n',E'Donaska - stlpce v zozname objednavok',E'Donaska - stlpce v zozname objednavok',E'',E'',E'',E'',0),
(E'own_delivery_cp_search_term_to_new_user_phone',E'.F.',E'param',E'L',E'Vlastna donaska - kopir. hladany text do noveho zakaznika?',0,24 ,E'ROZNE',E'',E'',E'',E'Ci sa ma text, ktory obsluha zadala pri hladani zakaznika prekopirovat do policka telefon ak nenajdu zakaznika a kliknu ze chcu zalozit noveho (aby to nemuseli vypisovat znovu)',E'Donaska - kopir. hladany text do noveho zakaznika?',E'Donaska - kopir. hladany text do noveho zakaznika?',E'',E'',E'',E'',0),
(E'allow_duplicates_in_category_contents',E'.T.',E'param',E'L',E'Povolit viacero rovnakych klapiek na jednej stranke?',0,25 ,E'ROZNE',E'',E'',E'',E'',E'Povolit viac rovnakych klapiek na jednej stranke?',E'Povolit viac rovnakych klapiek na jednej stranke?',E'',E'',E'',E'',0),
(E'obj_zobraz_plu_keyb',E'.F.',E'param',E'L',E'Namiesto klapiek zobrazovat PLU keyborad ',0,26,E'ROZNE ',E'',E'',E'',E'',E'Namiesto klapiek zobrazovat PLU keyborad ',E'',E'Namiesto klapiek zobrazovat PLU keyborad ',E'Namiesto klapiek zobrazovat PLU keyborad ',E'Namiesto klapiek zobrazovat PLU keyborad ',E'',0),
(E'men_cen_man',E'""',E'param',E'C',E'Zámena managerov pri vytváraní cenníka pokladne ',0,27,E'ROZNE ' ,E'',E'',E'',E'',E'Zámena managerov pri vytváraní cenníka pokladne ',E'Zámena managerov pri vytváraní cenníka pokladne ',E'',E'',E'',E'',0),
(E'men_cen_prn',E'""',E'param',E'C',E'Zámena tlačiarní pri vytváraní cenníka pokladne ',0,28,E'ROZNE ' ,E'',E'',E'',E'',E'Zámena tlačiarní pri vytváraní cenníka pokladne ',E'Zámena tlačiarní pri vytváraní cenníka pokladne ',E'',E'',E'',E'',0),
(E'ignore_limit_items',E'.F.',E'param',E'L',E'Majú sa ignorovať limity na mobiloch? ',0,0 ,E'ROZNE',E'',E'',E'',E'',E'Majú sa ignorovať limity na mobiloch?',E'Majú sa ignorovať limity na mobiloch?',E'',E'',E'',E'',0),
(E'mobilne_terminaly',E'0',E'param',E'N',E'Počet predplatených mobilných terminálov ',1,0 ,E'ROZNE',E'',E'',E'',E'',E'Počet predplatených mobilných terminálov',E'Počet predplatených mobilných terminálov',E'',E'',E'',E'',0),
(E'show_plu_on_item', E'.F.',E'param', E'L',E'Zobraziť na klapke jej PLU?',0,29,E'ROZNE',E'',E'',E'',E'true = na klapke bude zobrazené aj PLU položky priradenej k danej klapke\nfalse - na klapke nebude zobrazené plu (deafult)',E'Zobraziť na klapke jej PLU?',E'Zobraziť na klapke jej PLU?',E'',E'',E'',E'',0),
(E'items_search_order_by',E'"name"',E'param',E'C',E'Vyhladavanie poloziek - Radenie vysledkov podla...',0,30 ,E'ROZNE',E'',E'name,price,plu',E'',E'name = zoradovanie podla nazvu\nprice = zoradovanie podla ceny\nplu = zoradovanie podla PLU',E'Vyhladavanie poloziek - Radenie vysledkov podla...',E'Vyhladavanie poloziek - Radenie vysledkov podla...',E'',E'',E'',E'',0),
(E'items_search_order_direction',E'"asc"',E'param',E'C',E'Vyhladavanie poloziek - Smer radenia vysledkov',0,31 ,E'ROZNE',E'',E'asc,desc',E'',E'asc = zoradovanie vzostupne (a->z, 0->9)\ndesc = zoradovanie zostupne (z->a, 9->0)',E'Vyhladavanie poloziek - Smer radenia vysledkov',E'Vyhladavanie poloziek - Smer radenia vysledkov',E'',E'',E'',E'',0),
---
--- UCET
---
(E'card_pay_no_term',E'"qweqwe"',E'param',E'C',E'Platba pre kartu bez terminálu ',0,1 ,E'UCET',E'',E'',E'',E'',E'Platba pre kartu bez terminálu',E'',E'',E'',E'',E'uni;druhy_pl;qweqwe',0),
(E'bar_pay_in_cash',E'"Hotove"',E'param',E'C',E'Platba pre bar v hotovosti ',0,2 ,E'UCET',E'',E'',E'',E'',E'Platba pre bar v hotovosti',E'Platba pre bar v hotovosti',E'',E'',E'',E'uni;druhy_pl;Hotove',0),
(E'is_platdel',E'.T.',E'param',E'L',E'Pouzivat deleni plateb ',0,3 ,E'UCET ',E'',E'',E'',E'',E'Pouzivat deleni plateb ',E'Pouzivat deleni plateb ',E'',E'',E'',E'',0),
(E'zkr_mena',E'CZK',E'param',E'C',E'Zkratka oznacujici menu ',0,4 ,E'UCET ',E'',E'',E'',E' ',E'Zkratka oznacujici menu ',E'Zkratka oznacujici menu ',E' ',E' ',E' ',E' ',0),
(E'discount_mode',E'"bill"',E'param',E'C',E'Spôsob vypisovania zľavy na účte ',0,5 ,E'UCET ',E'',E'bill,item ',E'',E' ',E'Spôsob vypisovania zľavy na účte',E'Spôsob vypisovania zľavy na účte',E' ',E' ',E' ',E' ',0),
(E'is_zaok50',E'.F.',E'param',E'L',E'Pouzivat zaokrohlovani na padiky ',0,6 ,E'UCET ',E'',E'',E'',E'',E'Pouzivat zaokrohlovani na padiky ',E'Pouzivat zaokrohlovani na padiky ',E'',E'',E'',E'',0),
(E'is_zaokjho',E'.F.',E'param',E'L',E'Zaokrouhlit je hotovost ',0,7 ,E'UCET ',E'',E'',E'is_zaok50;=;.T. ',E'',E'Zaokrouhlit je hotovost ',E'Zaokrouhlit je hotovost ',E'',E'',E'',E'',0),
(E'terminal_storno',E'3',E'param',E'N',E'Typ storna pre terminály ',0,8 ,E'UCET',E'',E'0,1,2,3 ',E'',E'0 - peniaze sa vracajú v hotovosti\n1 - peniaze sa vracajú cez terminál na kartu\n2 - storno je zakázané\n3 - storno bez komunikácie s terminálom (treba nastaviť platbu do CARD_PAY_NO_TERM)',E'Typ storna pre terminály',E'Typ storna pre terminály',E'',E'',E'',E'',0),
(E'is_subtot',E'.F.',E'param',E'L',E'Má sa na nefiškálne bločky tlačiť jednotková cena? ',0,9 ,E'UCET',E'',E'',E'',E'',E'Má sa na nefiškálne bločky tlačiť jednotková cena?',E'Má sa na nefiškálne bločky tlačiť jednotková cena?',E'',E'',E'',E'',0),
(E'is_couvert',E'.F.',E'param',E'L',E'Má sa pri platbe zadať počet hostí? ',0,10,E'UCET',E'',E'',E'',E'',E'Má sa pri platbe zadať počet hostí?',E'Má sa pri platbe zadať počet hostí?',E'',E'',E'',E'',0),
(E'is_couvert_force',E'.F.',E'param',E'L',E'Má v payscreene zobraziť okno na zadanie počtu hostí?',0,11,E'UCET',E'',E'',E'',E'',E'V payscreene zobraziť okno na zadanie počtu hostí',E'V payscreene zobraziť okno na zadanie počtu hostí',E'',E'',E'',E'',0),
(E't_fast_polozky',E'.T.',E'param',E'L',E'Tlačiť položky FST menu s nulovou cenou? ',0,12,E'UCET',E'',E'',E'',E'',E'Tlacit polozky FST menu s nulovou cenou?',E'Tlacit polozky FST menu s nulovou cenou?',E'',E'',E'',E'',0),
(E'fst_ako_spocit_na_uc',E'1',E'param',E'N',E'Ako spočítavať fast food menu na účte? ',0,13,E'UCET ',E'',E'0,1,2 ',E'',E'0 - nespočítavať\n1 - spočítať menu s rovnakými položkami\n2 - spočítať rovnaké menu bez ohľadu na vybrané položky ',E'Spočítavať fast food menu na účte? ',E'Spočítavať fast food menu na účte? ',E'Spočítavať fast food menu na účte? ',E'',E'',E'',0),
(E'is_tipdot',E'.t.',E'param',E'L',E'Je povolen TIP ',0,14,E'UCET ',E'',E'',E'',E'',E'Je povolen TIP ',E'Je povolen TIP ',E'',E'',E'',E'',0),
(E'tip_dph',E'1.0',E'param',E'N',E'Sadzba DPH pre TIP ',0,15,E'UCET',E'',E'',E'',E'',E'',E'',E'',E'',E'',E'',0),
(E'is_tipvzdy',E'.F.',E'param',E'L',E'TIP a TOTAL vždy (nie len s kreditkami) ',0,16 ,E'UCET',E'',E'',E'',E'',E'TIP a TOTAL vzdy (ne jen u kred)',E'TIP a TOTAL vzdy (ne jen u kred)',E'',E'',E'',E'',0),
(E'nkop_edit',E'.f.',E'param',E'L',E'Editovateľný počet kópií účtenky ',0,17,E'UCET',E'',E'',E'',E'',E'Editovateľný počet kópií účtenky',E'Editovateľný počet kópií účtenky',E'',E'',E'',E'',0),
(E'nkop_edit_quickpay',E'.f.',E'param',E'L',E'Okno na zadanie počtu kópii v quicklpay?',0,18,E'UCET',E'',E'',E'',E'',E'Okno na zadanie počtu kópii v quicklpay?',E'Okno na zadanie počtu kópii v quicklpay?',E'',E'',E'',E'',0),
(E'require_cancel_reason',E'.f.',E'param',E'L',E'Vyžadovať dôvod storna?',0,19 ,E'UCET',E'',E'',E'',E'',E'Vyžadovať dôvod storna?',E'Vyžadovať dôvod storna?',E'',E'',E'',E'',0),
(E'is_pockoppreducet',E'.F.',E'param',E'L',E'Otazka na poc. kopii pri preducte. Nutne nastavit nkopkre ',0,20,E'UCET ',E'',E'',E'',E'',E'Otazka na poc. kopii pri preducte. Nutne nastavit ',E'Otazka na poc. kopii pri preducte. Nutne nastavit ',E'',E'',E'',E'',0),
(E'print_bon_on_cancel',E'.T.',E'param',E'L',E'Tlačiť storno objednávky pri storne účtu? ',0,21,E'UCET',E'',E'',E'',E'',E'Tlačiť storno objednávky pri storne účtu?',E'Tlačiť storno objednávky pri storne účtu?',E'',E'',E'',E'',0),
(E't_uc_info',E'.f.',E'param',E'L',E'Tlačiť na účty info v cudzej mene?',0,22 ,E'UCET',E'',E'',E'',E'',E'Tlačiť na účty info v cudzej mene?',E'Tlačiť na účty info v cudzej mene?',E'',E'',E'',E'',0),
(E't_uc_mena',E'"EUR"',E'param',E'C',E'Skratka info meny na účty',0,23 ,E'UCET',E'',E'',E'',E'',E'Skratka info meny na účty',E'Skratka info meny na účty',E'',E'',E'',E'',0),
(E't_uc_npr',E'"@"',E'param',E'C',E'Sparty, ktoré sa netlačia na úcet (ak suma=0)',0,24 ,E'UCET',E'',E'',E'',E'',E'Sparty, ktoré sa netlačia na úcet (ak suma=0)',E'Sparty, ktoré sa netlačia na úcet (ak suma=0)',E'',E'',E'',E'',0),
(E'update_vat_on_payment_until',E'"2025-02-02"',E'param',E'C',E'Dátum do kedy robiť zmenu dph pred platbou.',0,25 ,E'UCET',E'',E'',E'',E'',E'Dátum do kedy robiť zmenu dph pred platbou.',E'Dátum do kedy robiť zmenu dph pred platbou.',E'',E'',E'',E'',0),
(E'show_payment_after_pay',E'.f.',E'param',E'L',E'Zobraziť okno s výdavkom ?',0,26 ,E'UCET',E'',E'',E'',E'',E'',E'Zobraziť okno s výdavkom ?',E'',E'',E'',E'',0),
(E'reauth_before_cancel',E'.F.',E'param',E'L',E'Pýtať si heslo pred stornom?',0,27 ,E'UCET',E'',E'',E'',E'Pýtať si heslo pred stornom?',E'Pýtať si heslo pred stornom?',E'Pýtať si heslo pred stornom?',E'',E'',E'',E'',0),
(E'reauth_before_cancel_attribute_sales_to_new',E'.T.',E'param',E'L',E'Storno na novo prihláseného čašníka?',0,28 ,E'UCET',E'',E'',E'',E'Storno na novo prihláseného čašníka?',E'Storno na novo prihláseného čašníka?',E'Storno na novo prihláseného čašníka?',E'',E'',E'',E'',0),
(E'reauth_before_payment',E'.F.',E'param',E'L',E'Pýtať si heslo pred platbou?',0,29 ,E'UCET',E'',E'',E'',E'Pýtať si heslo pred platbou?',E'Pýtať si heslo pred platbou?',E'Pýtať si heslo pred platbou?',E'',E'',E'',E'',0),
(E'reauth_before_payment_attribute_sales_to_new',E'.T.',E'param',E'L',E'Účet na novo prihláseného čašníka?',0,30 ,E'UCET',E'',E'',E'',E'Účet na novo prihláseného čašníka?',E'Účet na novo prihláseného čašníka?',E'Účet na novo prihláseného čašníka?',E'',E'',E'',E'',0),
(E'reprint_current_closure_bill_via',E'"fiskal"',E'param',E'C',E'Kópiu účtu z aktuálnej uzávierky tlačiť cez?',0,31 ,E'UCET',E'',E'fiskal,foodie',E'',E'',E'Kópiu účtu z aktuálnej uzávierky tlačiť cez?',E'Kópiu účtu z aktuálnej uzávierky tlačiť cez?',E'',E'',E'',E'',0),
(E'reprint_past_closure_bill_via',E'"foodie"',E'param',E'C',E'Kópiu účtu zo starej uzávierky tlačiť cez?',0,32 ,E'UCET',E'',E'fiskal,foodie',E'',E'',E'Kópiu účtu zo starej uzávierky tlačiť cez?',E'Kópiu účtu zo starej uzávierky tlačiť cez?',E'',E'',E'',E'',0),
(E'print_cancel_bill_always_on_prnno',E'""',E'param',E'C',E'Nefiškálne storno účty tlačiť vždy na tlačiareň',0,33 ,E'UCET',E'',E'',E'',E'',E'Nefiškálne storno účty tlačiť vždy na prn_no',E'Nefiškálne storno účty tlačiť vždy na prn_no',E'',E'',E'',E'',0),
(E'print_reprint_bill_always_on_prnno',E'""',E'param',E'C',E'Kópiu nefiškálnych účtov tlačiť vždy na tlačiareň',0,34 ,E'UCET',E'',E'',E'',E'',E'Kópiu nefiškálnych účtov tlačiť vždy na prn_no',E'Kópiu nefiškálnych účtov tlačiť vždy na prn_no',E'',E'',E'',E'',0),
(E'payment_attribute_sales_to_author',E'.F.',E'param',E'L',E'Účet na časníka ktorý nablokoval položky?',0,35 ,E'UCET',E'',E'',E'',E'Účet na časníka ktorý nablokoval položky?',E'Účet na časníka ktorý nablokoval položky?',E'Účet na časníka ktorý nablokoval položky?',E'',E'',E'',E'',0),
(E'nonfiscal_bill_dont_cancel_on_print_error',E'.T.',E'param',E'L',E'Nerob auto storo pri chybe tlaci nefiskal. uctu?',0,36 ,E'UCET',E'',E'',E'',E'Ak nastala chyba tlače nefiškálneho dokladu,\nmá sa pokračovať bez robenia automatického storna?\nTrue = ano, nerob automaticke storno\nFalse - urob automaticke storno a vrat polozky na stol',E'Nerob auto storo pri chybe tlaci nefiskal. uctu?',E'Nerob auto storo pri chybe tlaci nefiskal. uctu?',E'',E'',E'',E'',0),
---
--- UZAVIERKA
---
(E't_uz_ucet',E'.F.',E'param',E'L',E'report účtů v uzávěrce? ',0,13,E'UZAVIERKA' ,E'',E'',E'',E'',E'report účtů v uzávěrce? ',E'report účtů v uzávěrce? ',E'',E'',E'',E'',0),
(E't_uz_trzdr',E'.F.',E'param',E'L',E'rozdělení tržeb dle druhů ',0,14,E'UZAVIERKA' ,E'',E'',E'',E'',E'rozdělení tržeb dle druhů ',E'rozdělení tržeb dle druhů ',E'',E'',E'',E'',0),
(E't_uz_harek',E'.F.',E'param',E'L',E'Je harek v uzaverce? ',0,15,E'UZAVIERKA' ,E'',E'',E'',E'Vytlacit na uzavierke zoznam predanych poloziek?',E'Je harek v uzaverce? ',E'Je harek v uzaverce? ',E'',E'',E'',E'',0),
(E't_uz_dph',E'.F.',E'param',E'L',E'Uzav. tisk po DPH ',0,18,E'UZAVIERKA' ,E'',E'',E'',E'',E'Uzav. tisk po DPH ',E'Uzav. tisk po DPH ',E'',E'',E'',E'',0),
(E't_uz_man',E'.T.',E'param',E'L',E'uzav. tisk po managerech ',0,19,E'UZAVIERKA' ,E'',E'',E'',E'',E'uzav. tisk po managerech ',E'uzav. tisk po managerech ',E'',E'',E'',E'',0),
(E't_uz_cenhl',E'.T.',E'param',E'L',E'Uzav. tisk po cenovych hladinach ',0,20,E'UZAVIERKA' ,E'',E'',E'',E'',E'Uzav. tisk po cenovych hladinach ',E'Uzav. tisk po cenovych hladinach ',E'',E'',E'',E'',0),
(E't_uz_ras',E'.F.',E'param',E'L',E'Tisky po rastrech ',0,21,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tisky po rastrech ',E'Tisky po rastrech ',E'',E'',E'',E'',0),
(E'zrc_sp_l',E'"\'@\'"',E'param',E'C',E'Sparty, ktere se tisknou na zrcadlo ',0,45,E'UZAVIERKA' ,E'',E'',E'',E'',E'Sparty, ktere se tisknou na zrcadlo ',E'Sparty, ktere se tisknou na zrcadlo ',E'',E'',E'',E'multi;spart_kas;@',0),
(E't_uz_zrc',E'.F.',E'param',E'L',E'Uzav. tisk zrcadla ',0,22,E'UZAVIERKA' ,E'',E'',E'',E'',E'Uzav. tisk zrcadla ',E'Uzav. tisk zrcadla ',E'',E'',E'',E'',0),
(E't_uz_man_dph',E'.F.',E'param',E'L',E'Uzav. tisk po managerech a DPH ',0,19,E'UZAVIERKA' ,E'',E'',E'',E'',E'Uzav. tisk po managerech a DPH ',E'Uzav. tisk po managerech a DPH ',E'',E'',E'',E'',0),
(E't_uz_hjen',E'.F.',E'param',E'L',E'Jen hotovost bez sumy celkem ',0,24,E'UZAVIERKA' ,E'',E'',E'',E'',E'Jen hotovost bez sumy celkem ',E'Jen hotovost bez sumy celkem ',E'',E'',E'',E'',0),
(E't_uz_spcis',E'.F.',E'param',E'L',E'Uz.tisk po spartech a cisnicich ',0,25,E'UZAVIERKA' ,E'',E'',E'',E'',E'Uz.tisk po spartech a cisnicich ',E'Uz.tisk po spartech a cisnicich ',E'',E'',E'',E'',0),
(E't_uz_dphsm',E'.F.',E'param',E'L',E'Tisk sumare DPH ',0,26,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tisk sumare DPH ',E'Tisk sumare DPH ',E'',E'',E'',E'',0),
(E't_uz_puctu',E'.F.',E'param',E'L',E'Tisk poctu uctu v uzaverce ',0,27,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tisk poctu uctu v uzaverce ',E'Tisk poctu uctu v uzaverce ',E'',E'',E'',E'',0),
(E't_uz_stzur',E'.F.',E'param',E'L',E'Tisk zurnalu storen v uzaverce ',0,28,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tisk zurnalu storen v uzaverce ',E'Tisk zurnalu storen v uzaverce ',E'',E'',E'',E'',0),
(E't_uzcis_pl',E'.T.',E'param',E'L',E'Tisk i bezhot.plateb v uzav cisniku ',0,29,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tisk i bezhot.plateb v uzav cisniku ',E'Tisk i bezhot.plateb v uzav cisniku ',E'',E'',E'',E'',0),
(E'har_spart',E'""',E'param',E'C',E'Sparty, kt.maju byt v harku na uzav ',0,16,E'UZAVIERKA' ,E'',E'',E't_uz_harek;=;.T. ',E'',E'Sparty, kt.maju byt v harku na uzav ',E'Sparty, kt.maju byt v harku na uzav ',E'',E'',E'',E'',0),
(E't_uz_casni',E'.T.',E'param',E'L',E'Tlacit uzavierku po casnikoch ',0,30,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlacit uzavierku po casnikoch ',E'Tlacit uzavierku po casnikoch ',E'',E'',E'',E'',0),
(E't_uz_cshot',E'.T.',E'param',E'L',E'Tlacit uzavierku po casnikoch hotov ',0,31,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlacit uzavierku po casnikoch hotov ',E'Tlacit uzavierku po casnikoch hotov ',E'',E'',E'',E'',0),
(E't_uz_stmin',E'.F.',E'param',E'L',E'Tisk storen v minulych uzaverkach ',0,32,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tisk storen v minulych uzaverkach ',E'Tisk storen v minulych uzaverkach ',E'',E'',E'',E'',0),
(E't_uz_ptran',E'.F.',E'param',E'L',E'Pocet transakci (transakce=novy stu ',1,33,E'UZAVIERKA' ,E'',E'',E'',E'',E'Pocet transakci (transakce=novy stu ',E'Pocet transakci (transakce=novy stu ',E'',E'',E'',E'',0),
(E't_uz_vicek',E'.F.',E'param',E'L',E'Tisk vicenasobne pouzitych karet v ',0,34,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tisk vicenasobne pouzitych karet v ',E'Tisk vicenasobne pouzitych karet v ',E'',E'',E'',E'',0),
(E't_uz_kombi',E'.F.',E'param',E'L',E'Tlacit UZ podla man,druh_pl,dan ',0,35,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlacit UZ podla man,druh_pl,dan ',E'Tlacit UZ podla man,druh_pl,dan ',E'',E'',E'',E'',0),
(E't_uzcis_ma',E'.F.',E'param',E'L',E'Tlac podla managerov u uz. casnikov ',0,36,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlac podla managerov u uz. casnikov ',E'Tlac podla managerov u uz. casnikov ',E'',E'',E'',E'',0),
--- není to duplicta k men_cen_man (E'men_sp_man',E'""',E'param',E'C',E'Zmena exportu do cenara pri uzavierke pokladne ',0,4 ,E'UZAVIERKA' ,E'',E'',E'',E'',E'Zmena exportu do cenara pri uzavierke pokladne ',E'Zmena exportu do cenara pri uzavierke pokladne ',E'',E'',E'',E'',0),
(E'is_denstat',E'.T.',E'param',E'L',E'.T. - robi sa denne statisticka uzavierka, .F. - nerobi sa ',0,2 ,E'UZAVIERKA' ,E'',E'',E'',E'',E'.T. - robi sa denne statisticka uzavierka, .F. - n',E'.T. - robi sa denne statisticka uzavierka, .F. - n',E'',E'',E'',E'',0),
(E't_uz_spdph',E'.F.',E'param',E'L',E'Uz.tisk po spartech a sadzbach DPH ',0,37,E'UZAVIERKA' ,E'',E'',E'',E'',E'Uz.tisk po spartech a sadzbach DPH ',E'Uz.tisk po spartech a sadzbach DPH ',E'',E'',E'',E'',0),
(E't_uz_poh_drpl',E'.F.',E'param',E'L',E'Tlačiť na uzávierke rozdelenie poľadavok podľa cas. a drpl. ',0,38,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlačiť na uzávierke rozdelenie poľadavok podľa cas',E'Tlačiť na uzávierke rozdelenie poľadavok podľa cas',E'',E'',E'',E'',0),
(E't_uz_vkl_drpl',E'.F.',E'param',E'L',E'Tlačiť na uzávierke rozdelenie vklad/vyber podľa cas. a plat',0,39,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlačiť na uzávierke rozdelenie vklad/vyber podľa c',E'Tlačiť na uzávierke rozdelenie vklad/vyber podľa c',E'',E'',E'',E'',0),
(E't_uz_trz_vkl_drpl',E'.F.',E'param',E'L',E'Tlačiť na uzáv.rozdel. trržby+vklad/vyber podľa cas. a plat.',0,40,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlačiť na uzáv.rozdel. trržby+vklad/vyber podľa ca',E'Tlačiť na uzáv.rozdel. trržby+vklad/vyber podľa ca',E'',E'',E'',E'',0),
(E't_uz_vklad_vyber',E'.F.',E'param',E'L',E'Tlačiť na uzávierke zoznam vkladov a vyberov ',0,41,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlačiť na uzávierke zoznam vkladov a vyberov ',E'Tlačiť na uzávierke zoznam vkladov a vyberov ',E'',E'',E'',E'',0),
(E't_uz_tlacpredajalko',E'.F.',E'param',E'L',E'Pri uzav. tlaci predaj alokoholu ',0,42,E'UZAVIERKA' ,E'',E'',E'',E'',E'Pri uzav. tlaci pradj alokoholu ',E'Pri uzav. tlaci pradj alokoholu ',E'',E'',E'',E'',0),
(E't_uz_editpredajalko',E'.F.',E'param',E'L',E'Pri uzav. zobrazi formular na zadavanie/opravu predaj alka ',0,43,E'UZAVIERKA' ,E'',E'',E'',E'',E'Pri uzav. zobrazi formular na zadavanie/opravu pre',E'Pri uzav. zobrazi formular na zadavanie/opravu pre',E'',E'',E'',E'',0),
(E'spotreba_po_druhoch',E'.T.',E'param',E'L',E'Report spotreby grupovat po druhoch ',0,48,E'UZAVIERKA' ,E'',E'',E'',E'',E'spotreba_po_druhoch ',E'spotreba_po_druhoch ',E'',E'',E'',E'',0),
(E't_uz_objed',E'.F.',E'param',E'L',E'Tlač vytvorenych objednavok v uzavierke ',0,44,E'UZAVIERKA' ,E'',E'',E'',E'Tlač vytvorenych objednavok v uzavierke. Polozky, ktore maju k_odpoctu "C".',E'Tlač vytvorenych objednavok v uzavierke ',E'Tlač vytvorenych objednavok v uzavierke ',E'Tlač vytvorenych objednavok v uzavierke ',E'Tlač vytvorenych objednavok v uzavierke',E'Tlač vytvorenych objednavok v uzavierke',E'',0),
(E't_uz_drpl',E'.T.',E'param',E'L',E'Tlač tržby po druhoch platby ',0,45,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač tržby po druhoch platby',E'Tlač tržby po druhoch platby',E'',E'',E'',E'',0),
(E't_uz_odovzdanie',E'.T.',E'param',E'L',E'Tlač sumy na odovzdanie na uzávierku ',0,21,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač sumy na odovzdanie na uzávierku',E'Tlač sumy na odovzdanie na uzávierku',E'',E'',E'',E'',0),
(E't_uz_fisk_platby',E'.T.',E'param',E'L',E'Tlač reportu po fiškálnych platbách ',0,21,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač reportu po fiškálnych platbách',E'Tlač reportu po fiškálnych platbách',E'',E'',E'',E'',0),
(E'uzav_odvod',E'"0"',E'param',E'C',E'Výber pri uzávierke ',0,21,E'UZAVIERKA' ,E'',E'',E'',E'0 - nerobiť výber\n1 - automaticky vybrať celú tržbu + vklady pre nastavené platidlá\n2 - zobraziť obsluhe možnosť zadať prevod do nového dňa, aby ráno nemuseli robiť vklad\n',E'Výber pri uzávierke',E'Výber pri uzávierke',E'',E'',E'',E'',0),
(E't_uz_drpldan',E'.T.',E'param',E'L',E'Tlač reportu po platbách a daniach na uzávierku ',0,21,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač reportu po platbách a daniach na uzávierku',E'Tlač reportu po platbách a daniach na uzávierku',E'',E'',E'',E'',0),
(E't_uz_drplfisdan',E'.T.',E'param',E'L',E'Tlač reportu po fiškálnych platbách a daniach ',0,21,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač reportu po fiškálnych platbách a daniach',E'Tlač reportu po fiškálnych platbách a daniach',E'',E'',E'',E'',0),
(E't_uz_dph_fis',E'.F.',E'param',E'L',E'Tlač reportu po DPH - fiškálne platby ',0,21,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač reportu po DPH - fiškálne platby',E'Tlač reportu po DPH - fiškálne platby',E'',E'',E'',E'',0),
(E't_uz_terminal',E'.f.',E'param',E'L',E'Platby po termináloch v uzávierke ',0,1 ,E'UZAVIERKA' ,E'',E'',E'',E'',E'Platby po termináloch v uzávierke',E'Platby po termináloch v uzávierke',E'',E'',E'',E'',0),
(E'is_uzostul',E'.t.',E'param',E'L',E'Je povolená uzávierka s otvorenými stolmi? ',0,1 ,E'UZAVIERKA' ,E'',E'',E'',E'',E'Je povolená uzávierka s otvorenými stolmi?',E'Je povolená uzávierka s otvorenými stolmi?',E'',E'',E'',E'',0),
(E't_uz_mena',E'.f.',E'param',E'L',E'Tlač reportu po menách na uzávierke',0,46 ,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač reportu po menách na uzávierke',E'Tlač reportu po menách na uzávierke',E'',E'',E'',E'',0),
(E't_uz_stoly',E'.f.',E'param',E'L',E'Tlač otvorených stolov na uzávierke',0,46 ,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač otvorených stolov na uzávierke',E'Tlač otvorených stolov na uzávierke',E'',E'',E'',E'',0),
(E't_uz_puctu_cas',E'.F.',E'param',E'L',E'Tlač počtu účtov po čašníkoch',0,27,E'UZAVIERKA' ,E'',E'',E'',E'',E'Tlač počtu účtov po čašníkoch',E'Tlač počtu účtov po čašníkoch',E'',E'',E'',E'',0),
---
--- PERIFERIE (DISPLAY, PRICECHECKER, RECYKLOMAT, SUFLIK, VAHY)
---
--- DISPLAY
(E'external_display',E'.f.',E'param',E'L',E'Používa sa externý display?',0,1 ,E'DISPLAY',E'',E'',E'',E'',E'',E'Používa sa externý display?',E'',E'',E'',E'',0),
(E'external_display_thankyou_message',E'"Ďakujeme za Váš nákup"',E'param',E'C',E'Sprava dakujeme',0,2 ,E'DISPLAY',E'',E'',E'',E'',E'',E'Sprava dakujeme',E'',E'',E'',E'',0),
(E'external_display_default_message',E'"Vitajte"',E'param',E'C',E'Uvodna sprava',0,3 ,E'DISPLAY',E'',E'',E'',E'',E'',E'Uvodna sprava',E'',E'',E'',E'',0),
(E'external_display_logout_message',E'"Pokladňa mimo prevádzku"',E'param',E'C',E'Správa pri odhlásenej pokladni',0,4 ,E'DISPLAY',E'',E'',E'',E'',E'',E'Správa pri odhlásenej pokladni',E'',E'',E'',E'',0),
(E'external_display_show_total_on_table_open',E'.t.',E'param',E'L',E'Vypis iba sumu total, alebo vsetky polozky',0,5 ,E'DISPLAY',E'',E'',E'',E'',E'',E'Vypis iba sumu total, alebo vsetky polozky',E'',E'',E'',E'',0),
--- PRICECHECKER
(E'pricechecker_template_logo_url',E'""',E'param',E'C',E'URL adresa k obrazku s logom prevadzky',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'URL adresa k obrazku s logom prevadzky',E'URL adresa k obrazku s logom prevadzky',E'',E'',E'',E'',0),
(E'pricechecker_template_background_image',E'""',E'param',E'C',E'URL adresa k obrazku, ktory sa pouzije ako pozadie',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'URL adresa k obrazku, ktory sa pouzije ako pozadie',E'URL adresa k obrazku, ktory sa pouzije ako pozadie',E'',E'',E'',E'',0),
(E'pricechecker_template_background_color',E'"#ffffff"',E'param',E'C',E'Farba pozadia v RGB hex formate',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'Farba pozadia v RGB hex formate',E'Farba pozadia v RGB hex formate',E'',E'',E'',E'',0),
(E'pricechecker_template_text_background_color',E'"#ffffff"',E'param',E'C',E'Farba pozadia text.elementov v RGB hex formate',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'Farba pozadia text.elementov v RGB hex formate',E'Farba pozadia text.elementov v RGB hex formate',E'',E'',E'',E'',0),
(E'pricechecker_template_text_background_alpha',E'0.75',E'param',E'N',E'Priehladnost pozadia textovych elementov',0,23 ,E'PriceCheck',E'',E'',E'',E'Priehladnost pozadia textovych elementov v rozmedzi 0-1 (desatinne cislo) - 0 je uplne priehladne, 1 - uplne nepriehladne',E'Priehladnost pozadia textovych elementov',E'Priehladnost pozadia textovych elementov',E'',E'',E'',E'',0),
(E'pricechecker_template_text_border_color',E'"#000000"',E'param',E'C',E'Farba oramovania text.elementov v RGB hex formate',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'Farba oramovania text.elementov v RGB hex formate',E'Farba oramovania text.elementov v RGB hex formate',E'',E'',E'',E'',0),
(E'pricechecker_template_text_color',E'"#000000"',E'param',E'C',E'Farba textu v RBG hex formate',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'Farba textu v RBG hex formate',E'Farba textu v RBG hex formate',E'',E'',E'',E'',0),
(E'pricechecker_template_element_background_color',E'"#ffffff"',E'param',E'C',E'Farba pozadia netext.elementov v RGB hex formate',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'Farba pozadia netext.elementov v RGB hex formate',E'Farba pozadia netext.elementov v RGB hex formate',E'',E'',E'',E'',0),
(E'pricechecker_template_element_foreground_color',E'"#000000"',E'param',E'C',E'Farba netextovych elementov v RBG hex formate',0,23 ,E'PriceCheck',E'',E'',E'',E'',E'Farba netextovych elementov v RBG hex formate',E'Farba netextovych elementov v RBG hex formate',E'',E'',E'',E'',0),
(E'pricechecker_template_reset_timeout',E'10',E'param',E'N',E'Čas zobrazenia informácie o cene produktu',0,23 ,E'PriceCheck',E'',E'',E'',E'Cas v sekundach, po ktorom sa vrati pricechecker z informacie o cene produktu na hlavny screen (i.e. kolko je zobrazena informacia o cene nacitaneho produktu)',E'Čas zobrazenia informácie o cene produktu',E'Čas zobrazenia informácie o cene produktu',E'',E'',E'',E'',0),
(E'pricechecker_default_pricelevel',E'""',E'param',E'C',E'Cenova hladina, v ktorej ukazuje pricechecker ceny',0,23 ,E'PriceCheck',E'',E'',E'',E'default hodnota - nenastavene, alebo prazdny string. ak je prazdny string, pouzije pricechecker automaticky cenovu hladinu definovanu v K32.def_cenhla',E'Cenova hladina v ktorej ukazuje pricechecker ceny',E'Cenova hladina v ktorej ukazuje pricechecker ceny',E'',E'',E'',E'',0),
(E'pricechecker_allowed_pricelevels',E'""',E'param',E'C',E'Cenové hladiny, ktoré má pricechecker zobrazovať',0,23 ,E'PriceCheck',E'',E'',E'',E'príklad hodnôt: {"1": "Zakladna cena", "3": "Akciova cena", "4": "Zamestnanecka cena"}',E'Cenové hladiny, ktoré má pricechecker zobrazovať',E'Cenové hladiny, ktoré má pricechecker zobrazovať',E'',E'',E'',E'',0),
--- RECYKLOMAT
(E'recyclomat',E'.f.',E'param',E'L',E'Povoliť recyklomat',0,0,E'RECYKLOMAT',E'',E'',E'',E'',E'',E'Povoliť recyklomat',E'',E'',E'',E'',0),
(E'recyclomat_code_length',E'0',E'param',E'N',E'Dĺžka recyklomat kódu',0,0,E'RECYKLOMAT',E'',E'',E'',E'',E'Dĺžka recyklomat kódu',E'Dĺžka recyklomat kódu',E'',E'',E'',E'',0),
(E'recyclomat_code_prefix',E'"33"',E'param',E'C',E'Prefix recyklomat kódu (hodnoty 01-99)',0,0,E'RECYKLOMAT',E'',E'',E'',E'',E'Prefix recyklomat kódu (hodnoty 01-99)',E'Prefix recyklomat kódu (hodnoty 01-99)',E'',E'',E'',E'',0),
(E'recyclomat_article_id',E'0',E'param',E'N',E'ID polozky, ktora ma byt nablokovana',0,0,E'RECYKLOMAT',E'',E'',E'',E'',E'ID polozky, ktora ma byt nablokovana',E'ID polozky, ktora ma byt nablokovana',E'',E'',E'',E'',0),
(E'recyclomat_allowed_ids','"xxx"',E'param',E'C',E'ID automatov oddelene bodkociarkou',0,0,E'RECYKLOMAT',E'',E'',E'',E'',E'ID automatov na prevadzke oddelene bodkociarkou',E'ID automatov na prevadzke oddelene bodkociarkou',E'',E'',E'',E'',0),
--- SUFLIK
(E'sup_op',E'0',E'param',E'N',E'Suplik= 0 neni, 1..4 Com, 5 kartou ',0,1 ,E'SUFLIK ',E'',E'',E'',E'',E'Suplik= 0 neni, 1..4 Com, 5 kartou ',E'Suplik= 0 neni, 1..4 Com, 5 kartou ',E'',E'',E'',E'',0),
--- VAHY
(E'vah_sp_l',E'"\'@\'"',E'param',E'C',E'Vahy - seznam spartu pro vazeni ',0,7 ,E'VAHY ',E'',E'',E'vah_comm;<>;0 ',E'',E'Vahy - seznam spartu pro vazeni ',E'Vahy - seznam spartu pro vazeni ',E'',E'',E'',E'multi;spart_kas;VAH',0),
(E'vah_baud',E'9600',E'param',E'N',E'Vahy - baud rate ',0,3 ,E'VAHY ',E'',E',19200,9600,4800,2400,1200 ',E'vah_comm;<>;0 ',E'',E'Vahy - baud rate ',E'Vahy - baud rate ',E'',E'',E'',E'',0),
(E'vah_datb',E'8',E'param',E'N',E'Vahy - datove bity ',0,5 ,E'VAHY ',E'',E',5,6,7,8 ',E'vah_comm;<>;0 ',E'',E'Vahy - datove bity ',E'Vahy - datove bity ',E'',E'',E'',E'',0),
(E'vah_dotaz',E'.T.',E'param',E'L',E'Ptát se při markování váženého zboží ',0,9 ,E'VAHY ',E'',E'',E'vah_comm;<>;0 ',E'',E'Ptát se při markování váženého zboží ',E'Ptát se při markování váženého zboží ',E'',E'',E'',E'',0),
(E'vah_tara0',E'.F.',E'param',E'L',E'Automaticky nulovat táru po opuštění objednávky ',0,13,E'VAHY ',E'',E'',E'',E'',E'Automaticky nulovat táru po opuštění objednávky ',E'Automaticky nulovat táru po opuštění objednávky ',E'',E'',E'',E'',0),
(E'vah_typ',E'"CAS"',E'param',E'C',E'Typ vahy:DS650,TOLEDO,CAS ',1,2 ,E'VAHY ',E'',E',DS650,TOLEDO,CAS ',E'vah_comm;<>;0 ',E'',E'Typ vahy:DS650,TOLEDO,CAS ',E'Typ vahy:DS650,TOLEDO,CAS ',E'',E'',E'',E'',0),
(E'vah_parita',E'0',E'param',E'N',E'Vahy - parita ',0,6 ,E'VAHY ',E'',E',0,1,2 ',E'vah_comm;<>;0 ',E'',E'Vahy - parita ',E'Vahy - parita ',E'',E'',E'',E'',0),
(E'vah_delit',E'1',E'param',E'N',E'Delitel pro prepocet vahy na kq (dkg..) ',1,8 ,E'VAHY ',E'',E'',E'vah_comm;<>;0 ',E'',E'Delitel pro prepocet vahy na kq (dkg..) ',E'Delitel pro prepocet vahy na kq (dkg..) ',E'',E'',E'',E'',0),
(E'vah_comm',E'0',E'param',E'N',E'Vahy - comm ',0,1 ,E'VAHY ',E'',E'',E'',E'',E'Vahy - comm ',E'Vahy - comm ',E'',E'',E'',E'',0),
(E'vah_interv',E'200',E'param',E'N',E'Interval dotazovani na vahu [ms] ',1,10,E'VAHY ',E'',E'',E'vah_comm;<>;0 ',E'',E'Interval dotazovani na vahu [ms] ',E'Interval dotazovani na vahu [ms] ',E'',E'',E'',E'',0),
(E'vah_bezkoe',E'.F.',E'param',E'L',E'Zadavat primo jednotky misto vahy ',0,11,E'VAHY ',E'',E'',E'',E'',E'Zadavat primo jednotky misto vahy ',E'Zadavat primo jednotky misto vahy ',E'',E'',E'',E'',0),
(E'vah_online',E'.F.',E'param',E'L',E'Ukazovanie vahy online pocas vazenia ',0,12,E'VAHY ',E'',E'',E'',E'',E'Ukazovanie vahy online pocas vazenia ',E'Ukazovanie vahy online pocas vazenia ',E'',E'',E'',E'',0),
(E'vah_zmenmn',E'.F.',E'param',E'L',E'Mozne menit vahu na vazenom tovare ',0,14,E'VAHY ',E'',E'',E'',E'',E'Mozne menit vahu na vazenom tovare ',E'Mozne menit vahu na vazenom tovare ',E'',E'',E'',E'',0),
(E'vah_stopb',E'1',E'param',E'N',E'Vahy - stop bity ',0,4 ,E'VAHY ',E'',E',0,1,2 ',E'vah_comm;<>;0 ',E'',E'Vahy - stop bity ',E'Vahy - stop bity ',E'',E'',E'',E'',0),
(E'foodman_weight_items',E'0_0;1_0.15;2_0.20;3_0.35' ,E'param',E'C',E'Váhy - taniere ',0,15,E'VAHY',E'',E'',E'',E'',E'Váhy - taniere',E'Váhy - taniere',E'',E'',E'',E'',0),
(E'vah_protocol',E'nci.pos',E'param',E'C',E'Váhy - komunikačný protokol ',0,16,E'VAHY',E'',E'nci.pos,cas,4.2.25,tscale ',E'',E'',E'Váhy - komunikačný protokol',E'Váhy - komunikačný protokol',E'',E'',E'',E'',0),
(E'weight_scanner',E'.F.',E'param',E'L',E'Používa sa váho-skener ',0,17,E'VAHY',E'',E'',E'',E'',E'Používa sa váho-skener',E'Používa sa váho-skener',E'',E'',E'',E'',0),
(E'weight_scanner_url',E'"127.0.0.1:5001"',E'param',E'C',E'IP:PORT na komunikáciu s váho-skenerom ',0,18,E'VAHY',E'',E'',E'weight_scanner;=;.T. ',E'',E'IP:PORT na komunikáciu s váho-skenerom ',E'IP:PORT na komunikáciu s váho-skenerom ',E'',E'',E'',E'',0),
(E'weight_scanner_auto_confirm',E'.f.',E'param',E'L',E'Váho-skener autopotvrdzovanie ',0,19,E'VAHY',E'',E'',E'weight_scanner;=;.T. ',E'',E'Váho-skener autopotvrdzovanie',E'Váho-scaner autopotvrdzovanie',E'',E'',E'',E'',0),
(E'peripherals_socket_url',E'"127.0.0.1:5001"',E'param',E'C',E'IP:PORT na komunikáciu cez AltoPripherals',0,10 ,E'VAHY',E'',E'',E'',E'',E'',E'IP:PORT na komunikáciu cez AltoPripherals',E'',E'',E'',E'',0),
(E'weight_dialog_allow_tare',E'.f.',E'param',E'L',E'Zobrazit tlačítko TARE v okne na váženie tovaru?',0,21 ,E'VAHY',E'',E'',E'',E'',E'Zobrazit tlačítko TARE v okne na váženie tovaru?',E'Zobrazit tlačítko TARE v okne na váženie tovaru?',E'',E'',E'',E'',0),
(E'weight_dialog_tare_send',E'.f.',E'param',E'L',E'Posielať príkaz TARE do váhy?',0,22 ,E'VAHY',E'',E'',E'',E'',E'Posielať príkaz TARE do váhy?',E'Posielať príkaz TARE do váhy?',E'',E'',E'',E'',0),
(E'weight_dialog_allow_null',E'.f.',E'param',E'L',E'Zobrazit tlačítko RESET v okne na váženie tovaru?',0,23 ,E'VAHY',E'',E'',E'',E'',E'Zobrazit tlačítko RESET v okne na váženie tovaru?',E'Zobrazit tlačítko RESET v okne na váženie tovaru?',E'',E'',E'',E'',0),
(E'vah_ident',E'"21"',E'param',E'C',E'Identifikace vazeneho zbozi na car. ',1,51,E'VAHY',E'',E'',E'',E'',E'Identifikace vazeneho zbozi na car. ',E'Identifikace vazeneho zbozi na car. ',E'',E'',E'',E'',0),
(E'vah_lnplu',E'5',E'param',E'N',E'Delka kodu vazeneho zbozi na car.ko ',1,52,E'VAHY',E'',E'',E'',E'',E'Delka kodu vazeneho zbozi na car.ko ',E'Delka kodu vazeneho zbozi na car.ko ',E'',E'',E'',E'',0),
(E'vah_lnvah',E'5',E'param',E'N',E'Delka kodu vahy na car.kodu ',1,53,E'VAHY',E'',E'',E'',E'',E'Delka kodu vahy na car.kodu ',E'Delka kodu vahy na car.kodu ',E'',E'',E'',E'',0),
---
--- VERNOSTNE SYSTEMY (D-EDGE, LOYALMAN, MORETHANDINING, RON)
---
(E'loyalty_system',E'"loyalman"',E'param',E'C',E'Typ pouziteho vernostneho systemu',0,2 ,E'LOYALTY',E'',E'd-edge,loyalman,morethandining,ron',E'',E'',E'Typ pouziteho vernostneho systemu',E'Typ pouziteho vernostneho systemu',E'',E'',E'',E'',0),
--- D-EDGE
(E'd_edge_user',E'"altoPOS.Webservice"',E'param',E'C',E'D-Edge username',0,2 ,E'D-EDGE',E'',E'',E'',E'',E'D-Edge username',E'D-Edge username',E'',E'',E'',E'',0),
(E'd_edge_pass',E'"46b88408-d614-4"',E'param',E'C',E'D-Edge password',0,3 ,E'D-EDGE',E'',E'',E'',E'',E'D-Edge password',E'D-Edge password',E'',E'',E'',E'',0),
(E'd_edge_enabled',E'.f.',E'param',E'L',E'Povolit D-Edge?',0,1 ,E'D-EDGE',E'',E'',E'',E'',E'Povolit D-Edge?',E'Povolit D-Edge?',E'',E'',E'',E'',0),
(E'd_edge_property_id',E'1',E'param',E'N',E'D-Edge Property ID',0,4 ,E'D-EDGE',E'',E'',E'',E'',E'D-Edge Property ID',E'D-Edge Property ID',E'',E'',E'',E'',0),
-- LOYALMAN
(E'loyalman_full_users_info' ,E'.T.',E'param',E'L',E'Zobraziť info o zákaznikovi ',0,3 ,E'LoyalMan',E'',E'',E'',E'',E'Zobraziť info o zákaznikov',E'Zobraziť info o zákaznikov',E'',E'',E'',E'',0),
(E'enable_loyalman',E'.t.',E'param',E'L',E'Povolená komunikácia s LoyalManom ',0,1 ,E'LoyalMan',E'',E'',E'',E'',E'Povolená komunikácia s LoyalManom',E'Povolená komunikácia s LoyalManom',E'',E'',E'',E'',0),
(E'loyalman_discount_text_on_bill',E'""',E'param',E'C',E'Názov loyalman zľavy na účte',0,2 ,E'LoyalMan',E'',E'',E'',E'Zadefinovaný text bude vypísaný na účte. Ak je zadefinovaný text: "#discount_package#" tak vypíše názov balíka alebo od loyalman verzie 1.3.4 text na účet. Ak je prázdne vypíše default text definovaný na kase ("LoyalMan zľava na účet")',E'Názov loyalman zľavy na účte',E'Názov loyalman zľavy na účte',E'',E'',E'','', 0),
(E'loyalman_discount_fastmenu_enabled',E'.F.',E'param',E'L',E'Má sa dávať loyalman zľava na fastmenu?',0,3 ,E'LoyalMan',E'',E'',E'',E'true = aj na fast menu bude aplikovaná loyalman zľava\nfalse = na fast menu nebude aplikovaná loyalman zľava (default)',E'Má sa dávať loyalman zľava na fastmenu?',E'Má sa dávať loyalman zľava na fastmenu?',E'',E'',E'','', 0),
-- MORETHANDINING
(E'morethandining_enabled',E'.f.',E'param',E'L',E'Povolená komunikácia s MTD',0,1 ,E'MTD',E'',E'',E'',E'',E'Povolená komunikácia s MTD',E'Povolená komunikácia s MTD',E'',E'',E'',E'',0),
(E'morethandining_url',E'"http://www.morethandining.com/interface"',E'param',E'C',E'URL pre komunikaciu s MTD API',0,2 ,E'MTD',E'',E'http://www.morethandining.com/interface,http://demo.morethandining.cdi.cz/interface',E'',E'',E'URL pre komunikaciu s MTD API',E'URL pre komunikaciu s MTD API',E'',E'',E'',E'',0),
(E'morethandining_restaurant',E'""',E'param',E'C',E'Nazov prevadzky v MTD systeme',0,3 ,E'MTD',E'',E'',E'',E'',E'Nazov prevadzky v MTD systeme',E'Nazov prevadzky v MTD systeme',E'',E'',E'','', 0),
(E'morethandining_ftp_host',E'""',E'param',E'C',E'MTD FTP Server - hostname/IP',0,4 ,E'MTD',E'',E'',E'',E'',E'MTD FTP Server - hostname/IP',E'MTD FTP Server - hostname/IP',E'',E'',E'','', 0),
(E'morethandining_ftp_user',E'""',E'param',E'C',E'MTD FTP Server - username',0,5 ,E'MTD',E'',E'',E'',E'',E'MTD FTP Server - username',E'MTD FTP Server - username',E'',E'',E'','', 0),
(E'morethandining_ftp_password',E'""',E'param',E'C',E'MTD FTP Server - password',0,6 ,E'MTD',E'',E'',E'',E'',E'MTD FTP Server - password',E'MTD FTP Server - password',E'',E'',E'','', 0),
(E'morethandining_ftp_dir',E'""',E'param',E'C',E'MTD FTP Server - folder',0,7 ,E'MTD',E'',E'',E'',E'',E'MTD FTP Server - folder',E'MTD FTP Server - folder',E'',E'',E'','', 0),
(E'morethandining_druh_pl',E'""',E'param',E'C',E'Nazev z druhy_pl pouzity pre MTD zlavu',0,8 ,E'MTD',E'',E'',E'',E'musi existovat ako platobna metoda zadefinovana v druhy_pl',E'Nazev z druhy_pl pouzity pre MTD zlavu',E'Nazev z druhy_pl pouzity pre MTD zlavu',E'',E'',E'','', 0),
-- RON
(E'ron_wsdl_url',E'""',E'param',E'C',E'WSDL URL pre komunikáciu s RON ',0,2 ,E'RON',E'',E'',E'',E'',E'WSDL URL pre komunikáciu s RON',E'WSDL URL pre komunikáciu s RON',E'',E'',E'',E'',0),
---
--- KVERKOM
---
(E'kverkom_ws_host',E'""',E'param',E'C',E'KVERKOM WebSocket Server - hostname/IP',0,1,E'KVERKOM',E'',E'',E'',E'',E'KVERKOM WebSocket Server - hostname/IP',E'KVERKOM WebSocket Server - hostname/IP',E'',E'',E'','', 0),
(E'kverkom_ws_port',E'8765',E'param',E'N',E'KVERKOM WebSocket Server - port',0,2,E'KVERKOM',E'',E'',E'',E'',E'KVERKOM WebSocket Server - port',E'KVERKOM WebSocket Server - port',E'',E'',E'','', 0),
(E'kverkom_erp_host',E'"api-erp.kverkom.sk"',E'param',E'C',E'KVERKOM notifikacny server',0,3,E'KVERKOM',E'',E'',E'',E'Kverkom notifikacny server\napi-erp.kverkom.sk - produkcne prostredie (default)\napi-erp-i.kverkom.sk - testovacie prostredie)',E'KVERKOM notifikacny server',E'KVERKOM notifikacny server',E'',E'',E'','', 0),
(E'kverkom_mqtt_host',E'"mqtt.kverkom.sk"',E'param',E'C',E'KVERKOM MQTT Server - hostname/IP',0,4,E'KVERKOM',E'',E'',E'',E'Kverkom MQTT server\nmqtt.kverkom.sk - produkcne prostredie (default)\nmqtt-i.kverkom.sk - testovacie prostredie)',E'KVERKOM MQTT Server - hostname/IP',E'KVERKOM MQTT Server - hostname/IP',E'',E'',E'','', 0),
(E'kverkom_ca_bundle',E'"kverkom-prod-ca-bundle"',E'param',E'C',E'KVERKOM CA certifikat',0,5,E'KVERKOM',E'',E'kverkom-prod-ca-bundle,kverkom-test-ca-bundle',E'',E'Kverkom CA certifikat:\nkverkom-prod-ca-bundle - produkcne prostredie (default)\nkverkom-test-ca-bundle - testovacie prostredie',E'KVERKOM CA certifikat',E'KVERKOM CA certifikat',E'',E'',E'','', 0),
(E'kverkom_timeout',E'120',E'param',E'N',E'Dlzka cakania na notifikaciu o kverkom platbe',0,6,E'KVERKOM',E'',E'',E'',E'Kolko sekund cakat na uspesnu notifikaciu o kverkom platbe?\ndefault 120s',E'Dlzka cakania na notifikaciu o kverkom platbe',E'Dlzka cakania na notifikaciu o kverkom platbe',E'',E'',E'','', 0),
(E'kverkom_timeout_allow_prolong',E'".f."',E'param',E'L',E'Je povolene predlzit cakanie na notifikaciu?',0,7,E'KVERKOM',E'',E'',E'',E'Je povolene predlzit cakanie na kverkom notifikaciu o platbe?\n(default false)',E'Je povolene predlzit cakanie na notifikaciu?',E'Je povolene predlzit cakanie na notifikaciu?',E'',E'',E'','', 0),
(E'kverkom_storno',E'3',E'param',E'N',E'Typ storna pre kverkom',0,8,E'KVERKOM',E'',E'0,1,2,3',E'',E'0 - peniaze sa vracajú v hotovosti\n1 - storno s tlacou ziadosti o vratenie platby\n2 - storno je zakázané\n3 - storno bez tlace ziadosti o vratenie platby',E'Typ storna pre kverkom',E'Typ storna pre kverkom',E'',E'',E'',E'',0),
(E'kverkom_storno_no_print_druhpl',E'"HOTOVE"',E'param',E'C',E'Platba pri storne kverkom uctu',0,9,E'KVERKOM',E'',E'',E'',E'',E'''druh_pl, pri storne ak kverkom_storno=3',E'',E'',E'',E'',E'uni;druhy_pl;HOTOVE',0),
(E'kverkom_qr_type',E'"payme"',E'param',E'C',E'Typ QR kodu:',0,10,E'KVERKOM',E'',E'payme',E'',E'Typ generovaneho QR kodu - dostupne moznosti:\n - payme',E'Typ QR kodu:',E'Typ QR kodu:',E'',E'',E'','', 0),
(E'qr_border',E'4',E'param',E'N',E'QR ohranicenie stvorca',0,11,E'KVERKOM',E'',E'',E'',E'QR ohranicenie stvorca (default 4)',E'',E'',E'',E'',E'','', 0),
(E'qr_box_size',E'10',E'param',E'N',E'QR velkost stvorca',0,12,E'KVERKOM',E'',E'',E'',E'QR velkost stvorca (1-16, default 10)',E'',E'',E'',E'',E'','', 0),
(E'payme_url',E'"https://payme.sk/2"',E'param',E'C',E'payme base url',0,13,E'KVERKOM',E'',E'',E'',E'payme base URL (https://payme.sk/2)',E'',E'',E'',E'',E'','', 0),
---
--- ZLAVY
---
(E'is_sleva_rychla_pl',E'.F.',E'param',E'L',E'Ma sa pokladna pytat na zlavu po rychlom tlacitku?',0,0 ,E'ZLAVY ',E'',E'',E'',E'Ma sa pokladna pytat na zlavu po rychlom tlacitku?',E'Ma sa pokladna pytat na zlavu po rychlom tlacitku ',E'Ma sa pokladna pytat na zlavu po rychlom tlacitku ',E'Ma sa pokladna pytat na zlavu po rychlom tlacitku',E'Ma sa pokladna pytat na zlavu po rychlom tlacitku',E'Ma sa pokladna pytat na zlavu po rychlom tlacitku',E'',0),
(E'enable_discount_due_to_expiration_date',E'.f.',E'param',E'L',E'Povolené dať zľavu na položku pri konci záruky?',0,1 ,E'ZLAVY ',E'',E'',E'',E'',E'',E'Povolené dať zľavu na položku pri konci záruky?',E'',E'',E'',E'',0),
(E'enable_discount_due_to_expiration_date_percentage',E'0',E'param',E'N',E'Zľava pri konci záruky',0,1 ,E'ZLAVY ',E'',E'',E'',E'',E'',E'Zľava pri konci záruky',E'',E'',E'',E'',0),
(E'hh_price_level_discount',E'.f.',E'param',E'L',E'Happy Hour cenová hladina sa správa ako zľava',0,2 ,E'ZLAVY',E'',E'',E'',E'',E'Happy Hour cenová hladina sa správa ako zľava',E'Happy Hour cenová hladina sa správa ako zľava',E'',E'',E'',E'',0),
(E'hh_percentual_discount',E'.f.',E'param',E'L',E'Happy Hour percentuálna zlava sa správa ako zľava',0,3 ,E'ZLAVY',E'',E'',E'',E'',E'Happy Hour percentuálna zlava sa správa ako zľava',E'Happy Hour percentuálna zlava sa správa ako zľava',E'',E'',E'',E'',0),
(E'hh_x_k_discount',E'.f.',E'param',E'L',E'Happy Hour x+k sa správa ako zľava',0,4 ,E'ZLAVY',E'',E'',E'',E'',E'Happy Hour x+k sa správa ako zľava',E'Happy Hour x+k sa správa ako zľava',E'',E'',E'',E'',0),
(E'hk_sl_spl',E'"XXX"',E'param',E'C',E'Zoznam spartov, na ktoré sa nedáva zľava ',0,0 ,E'ZLAVY',E'',E'',E'',E'',E'Zoznam spartov, na ktoré sa nedáva zľava',E'Zoznam spartov, na ktoré sa nedáva zľava',E'',E'',E'',E'multi;spart_kas;@',0),
---
--- FOODIE SYSTEMOVE
---
(E'heartbeat_show_api_unavail_after_x_missing',E'1',E'param',E'N',E'Kolko neprejdenych heartbaatov = chyba?',1,1 ,E'FOODIE SYS',E'',E'',E'',E'',E'Kolko neprejdenych heartbaatov = chyba?',E'Kolko neprejdenych heartbaatov = chyba?',E'',E'',E'',E'',0),
(E'heartbeat_interval',E'5',E'param',E'N',E'Ako casto (v sekundach) chodi heartbeat?',1,2 ,E'FOODIE SYS',E'',E'',E'',E'Definuje ako casto (v sekundach) chodi heartbeat request z foodieho na API server pre zistovanie jeho dostupnosti\ndefault hodnota = 5\n0 = nikdy neposielat heartbeat requesty',E'Ako casto (v sekundach) chodi heartbeat?',E'Ako casto (v sekundach) chodi heartbeat?',E'',E'',E'',E'',0),
(E'heartbeat_timeout',E'5',E'param',E'N',E'Kolko sekund cakat na odpoved z heartbeatu?',1,3 ,E'FOODIE SYS',E'',E'',E'',E'Po kolkych sekundach od odoslania heartbeat poziadavky vyhodnoti Foodie,\nze je API server nedostupny.\ndefault hodnota = 7',E'Kolko sekund cakat na odpoved z heartbeatu?',E'Kolko sekund cakat na odpoved z heartbeatu?',E'',E'',E'',E'',0),
(E'frontend_logging',E'.F.',E'param',E'L',E'Logovať operácie FE? (zmazanie čiernych riadkov)',0,4 ,E'FOODIE SYS',E'',E'',E'',E'Logovať operácie FE? (zmazanie čiernych riadkov)',E'Logovať operácie FE? (zmazanie čiernych riadkov)',E'Logovať operácie FE? (zmazanie čiernych riadkov)',E'',E'',E'',E'',0),
(E'frontend_logging_max_entries',E'100',E'param',E'N',E'Max.počet neodoslaných log záznamov z FE',0,5 ,E'FOODIE SYS',E'',E'',E'',E'Max.počet neodoslaných log záznamov z FE - musí byť >= 100',E'Max.počet neodoslaných log záznamov z FE',E'Max.počet neodoslaných log záznamov z FE',E'',E'',E'',E'',0),
(E'frontend_logging_sync_interval',E'15',E'param',E'N',E'Perióda odosielania logov FE na API >10',0,6 ,E'FOODIE SYS',E'',E'',E'',E'Perióda odosielania logov FE na API - musí byť >= 10',E'Perióda odosielania logov FE na API',E'Perióda odosielania logov FE na API',E'',E'',E'',E'',0),
(E'frontend_logging_heartbeat',E'.f.',E'param',E'L',E'Zapnute frontend logovanie heartbeat chyb?',1,7 ,E'FOODIE SYS',E'',E'',E'',E'Ci sa maju na frontend-e logovat (a nasledne po dostupnosti\nodosielat na API server) chyby suvisiace s heartbeat\nrequestami.\ndefault = False (nelogovat)\n\n!!! Funguje iba ak je zapnute K32.frontend_logging !!!',E'Zapnute frontend logovanie heartbeat chyb?',E'Zapnute frontend logovanie heartbeat chyb?',E'',E'',E'',E'',0),
(E'frontend_logging_local_orderline_delete',E'.f.',E'param',E'L',E'Zapnute frontend logovanie mazania poloziek?',1,8 ,E'FOODIE SYS',E'',E'',E'',E'Ci sa ma na frontend-e logovat (a nasledne odosielat na API server) mazanie lokalnych\n(nepotvrdenych) poloziek z objednavky.\ndefault = True (logovat)\n\n!!! Funguje iba ak je zapnute K32.frontend_logging !!!',E'Zapnute frontend logovanie mazania poloziek?',E'Zapnute frontend logovanie mazania poloziek?',E'',E'',E'',E'',0),
(E'virtualized_tables_grid_buffer_px',E'300',E'param',E'N',E'TEST - mobil - buffer px mapa stolov',1,0 ,E'FOODIE SYS',E'',E'',E'',E'',E'TEST - mobil - buffer px mapa stolov',E'TEST - mobil - buffer px mapa stolov',E'',E'',E'',E'',0),
(E'virtualized_orderline_sheet_buffer_px',E'300',E'param',E'N',E'TEST - mobil - buffer px objednavka',1,0 ,E'FOODIE SYS',E'',E'',E'',E'',E'TEST - mobil - buffer px objednavka',E'TEST - mobil - buffer px objednavka',E'',E'',E'',E'',0),
---
--- -----
---
(E'def_akce',E'3',E'param',E'N',E'Def. akce po přihlásení (3 objedn.)',0,50,E'----- ',E'',E'',E'',E'',E'Def. akce po přihlásení (3 objedn.)',E'Def. akce po přihlásení (3 objedn.)',E'',E'',E'',E'',0);
COMMIT;
+6
View File
@@ -0,0 +1,6 @@
server pro pokladnu:
-přes openVPN
ip adresa: 192.168.180.133
user: altohlu\demo22
pass: Prihlaseni.0622
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
m:
cd \pokladna
call v3.12\Scripts\activate
rem uvicorn server_sqlite:app --reload --host 0.0.0.0 --port 8000
python kivy_app.py
deactivate
pause
+4
View File
@@ -0,0 +1,4 @@
m:
cd\pokladna
m:\pokladna\v3.12\Scripts\python.exe local_print_agent.py --config local_print_agent.example.json
pause
+6
View File
@@ -0,0 +1,6 @@
m:
cd\pokladna
call m:\pokladna\v3.12\scripts\activate
uvicorn server_sqlite:app --host 0.0.0.0 --port 8000 --timeout-keep-alive 120
rem deactivate
pause
Binary file not shown.
+44
View File
@@ -0,0 +1,44 @@
# nacti uzaverku
import api_call
from pydantic import SecretStr
# ---------- API context ----------
def get_ctx():
ctx = api_call.ApiContext(
user="Alto",
base_url="http://127.0.0.1:8000",
refresh_url="http://127.0.0.1:8000",
client_id="99",
id_kas="01",
username="Kobrle",
password=SecretStr("heslo"),
)
return ctx
ctx = get_ctx()
# ---------- login ----------
api_call.login_API(ctx)
# ---------- dotaz na seznam uzávěrek ----------
closures, err = api_call.load_closures_API( ctx)
if err:
print(err)
exit()
# ---------- poslední 2 uzávěrky ----------
print("\nNAČÍTÁM POSLEDNÍ 2 UZÁVĚRKY:\n")
for c in closures[:2]:
print(
"UZÁVĚRKA:",
c.clsrep_no,
c.ucislo_od,
c.ucislo_do,
c.closed_at_od,
c.closed_at_do,
)
# ---------- načti detail uzávěrky ----------
detail, err = api_call.closure_detail_API(ctx, c.clsrep_no)
if err:
print("CHYBA:", err)
continue
print("POČET ÚČTŮ:", len(detail["ucty"]))
print("CLSREP:", detail["data"])
for u in detail["ucty"]:
print(" UCET:", u.get("ucislo"))
print()
+53
View File
@@ -0,0 +1,53 @@
# nacti ucty, ktere nejsou v uzaverce
import api_call
from pydantic import SecretStr
# ---------- API context ----------
def get_ctx():
ctx = api_call.ApiContext(
user="Alto",
base_url="http://127.0.0.1:8000",
refresh_url="http://127.0.0.1:8000",
client_id="99",
id_kas="07",
username="Kobrle",
password=SecretStr("heslo"),
)
return ctx
ctx = get_ctx()
# ---------- login ----------
api_call.login_API(ctx)
# ---------- načtení účtů mimo uzávěrku ----------
ucty, err = api_call.load_ucty_notinclsrep_API(ctx)
if err:
print(err)
exit()
api_call.logout_API(ctx)
# ---------- výpis ----------
print("\nÚČTY MIMO UZÁVĚRKU:\n")
if not ucty:
print("Žádné účty k uzávěrce.")
else:
print(f"POČET ÚČTŮ: {len(ucty)}\n")
for u in ucty:
print(
"UCET:",
u.ucislo,
"| STŮL:", u.stul,
"| AUTOR:", u.autor,
"| ČAS:", u.closed_at,
)
# ---------- návrh intervalu ----------
print("\nNÁVRH UZÁVĚRKY:\n")
ucislo_od = ucty[0].ucislo
ucislo_do = ucty[-1].ucislo
print("OD:", ucislo_od)
print("DO:", ucislo_do)
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://192.168.0.145:8000",
"refresh_url": "http://192.168.0.145:8000/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+510
View File
@@ -0,0 +1,510 @@
# Verze upravená JQ:
# - reaguje na klávesnici
# - obsahuje ochranu proti zdvojování písmen
# - umožňuje vstup ze čtečky, případně diakritiku přemění na čísla
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.logger import Logger
from kivy.uix.widget import Widget
# =====================================================
# NUMBER PAD
# =====================================================
from kivy.metrics import dp
from kivy.uix.modalview import ModalView
from kivy.uix.boxlayout import BoxLayout
from time import time
# JQ
from kivy.core.window import Window
from kivy.clock import Clock
# JQ
class NumberPad(ModalView):
def _btn(self, txt):
btn = Button(text=txt)
if txt == "":
btn.font_size = 36
btn.bind(on_press=lambda *_: self._press(txt))
return btn
def __init__(
self,
*,
mode="number",
max_len=10,
mask=False,
allow_fraction=False,
show_dot=True,
decimal_places=2,
initial_value="",
overwrite_on_first=False,
on_accept=None,
on_cancel=None,
when=None, #obsahuje time promenou pro potlaceni eventu
allow_text=False, # 🔥 NOVÉ
auto_accept_scanner=True, # 🔥 NOVÉ
scanner_timeout=0.05, # 🔥 NOVÉ
**kwargs,
):
super().__init__(
size_hint=(None, None),
size=(dp(360), dp(480)),
auto_dismiss=False,
**kwargs,
)
self.when = when
self.overwrite_on_first = overwrite_on_first
self._editing_started = False
self.on_accept = on_accept
self.on_cancel = on_cancel
self.show_dot = show_dot
# JQ
self._cz_to_num = str.maketrans("+ěščřžýáíé", "1234567890")
self.allow_text = allow_text
self.auto_accept_scanner = auto_accept_scanner
self.scanner_timeout = scanner_timeout
self._last_text = None
self._last_text_time = 0
# JQ
self._last_key_time = 0
self._scanner_buffer = ""
# --- interní stav (ZDROJ PRAVDY) ---
self.num = ""
self.den = "1"
self.is_fraction = False
self.mode = mode
self.allow_fraction = allow_fraction
self.decimal_places = decimal_places
self.max_len = max_len
self.mask = mask
# ---------- ROOT (JEDNOU!) ----------
root = BoxLayout(
orientation="vertical",
padding=dp(10),
spacing=dp(10),
)
self.add_widget(root)
# --- display ---
self.lbl = Label(
font_size=32,
size_hint_y=None,
height=dp(60),
halign="right",
valign="middle",
)
self.lbl.bind(size=lambda *_: setattr(self.lbl, "text_size", self.lbl.size))
root.add_widget(self.lbl)
# --- keypad ---
grid = GridLayout(cols=3, spacing=dp(5), size_hint_y=1)
for txt in ["7", "8", "9", "4", "5", "6", "1", "2", "3"]:
grid.add_widget(self._btn(txt))
if self.show_dot:
grid.add_widget(self._btn("."))
else:
grid.add_widget(Label())
grid.add_widget(self._btn("0"))
grid.add_widget(self._btn(""))
root.add_widget(grid)
# --- fractions ---
if self.allow_fraction:
frac = BoxLayout(size_hint_y=None, height=dp(50), spacing=dp(10))
frac.add_widget(self._btn("1/1"))
frac.add_widget(self._btn("1/2"))
frac.add_widget(self._btn("1/3"))
root.add_widget(frac)
# --- actions ---
actions = BoxLayout(size_hint_y=None, height=dp(60), spacing=dp(10))
btn_cancel = Button(text="ZRUŠIT")
btn_ok = Button(text="OK")
btn_cancel.bind(on_release=self._cancel)
btn_ok.bind(on_release=self._accept)
actions.add_widget(btn_cancel)
actions.add_widget(btn_ok)
root.add_widget(actions)
# ---------- TADY JE KLÍČ ----------
self._set_initial_value(initial_value)
self._overwrite = bool(initial_value)
self._refresh()
def _set_initial_value(self, val: str):
"""
Nastaví počáteční hodnotu NumberPadu z řetězce:
"3" nebo "3/2"
"""
if not val:
return
val = val.strip()
if "/" in val and self.allow_fraction:
try:
a, b = val.split("/", 1)
self.num = a
self.den = b
self.is_fraction = (b != "1")
except Exception:
self.num = ""
self.den = "1"
self.is_fraction = False
else:
# čisté číslo
self.num = val
self.den = "1"
self.is_fraction = False
def disable(self):
for w in self.children[0].children:
if hasattr(w, "disabled"):
w.disabled = True
# -------------------------------------------------
def clear(self):
self.num = ""
self.den = "1"
self.value = ""
self._refresh()
def reset(self):
"""
Resetuje NumberPad do počátečního stavu
"""
self.num = ""
self.den = "1"
self.is_fraction = False
self.value = ""
self._refresh()
def _press(self, txt):
print(txt)
def _start_new_input(self):
self.num = ""
self.den = "1"
self._editing_started = True
self.is_fraction = False
self._overwrite = False
# code: žádná fraction, žádná tečka
if self.mode == "code":
if txt in ("1/1", "1/2", "1/3"):
return
# fraction: žádná desetinná tečka
if self.allow_fraction and txt == ".":
return
# BACKSPACE
if txt == "":
self._scanner_buffer = "" # 🔥 FIX
if self.num:
# mažu poslední cifru čitatele
self.num = self.num[:-1]
else:
# čitatel prázdný → ruším fraction (zpět na /1)
if self.allow_fraction and self.den != "1":
self.den = "1"
self._refresh()
return
# FRACTION
if txt in ("1/1", "1/2", "1/3"):
if not self.allow_fraction:
return
new_den = txt.split("/")[1] # "1", "2", "3"
# toggle chování
if self.den == new_den:
self.den = "1"
else:
self.den = new_den
self._refresh()
return
# DESETINNÁ TEČKA (jen decimal režim)
if txt == ".":
if not self.show_dot or self.allow_fraction:
return
if "." in self.num: return
if self._overwrite:
self._start_new_input()
self.num = self.num + "." if self.num else "0."
self._refresh()
return
# ČÍSLICE
# ---- CODE REŽIM ----
if self.mode == "code":
if len(self.num) < self.max_len:
self.num += txt
self._refresh()
return
# ---- DECIMAL REŽIM (bez fraction) ----
if not self.allow_fraction:
# simulace budoucí hodnoty
if self._overwrite:
self._start_new_input()
new_num = self.num + txt if self.num else txt # kontrola desetinných míst
if "." in new_num:
_, dec_part = new_num.split(".", 1)
if len(dec_part) > self.decimal_places:
return
# kontrola délky celé části
if "." not in self.num and len(self.num) >= self.max_len:
return
self.num = new_num
self._refresh()
return
# ---- FRACTION REŽIM ----
if self.allow_fraction:
if self._overwrite:
# první stisk → přepíšeme čitatele, jmenovatel zachováme
self.num = txt
self._overwrite = False
else:
if len(self.num) < self.max_len:
self.num += txt
self._refresh()
return
def _start_new_input(self):
"""
Připraví NumberPad na nový vstup přepíše initial value
"""
self.num = ""
self.den = "1"
self.is_fraction = False
self._overwrite = False
def _refresh(self):
# JQ
if self.allow_text:
self.value = self.num
self.lbl.text = self.num
return
# JQ
n = self.num if self.num else "0"
# --- výstupní hodnota (datový kontrakt) ---
if self.allow_fraction:
self.value = f"{n}/{self.den}"
else:
self.value = n
# --- ZOBRAZENÍ ---
if self.mode == "code":
# 🔑 KLÍČ: při prázdném vstupu nezobrazuj NIC
if not self.num:
self.lbl.text = ""
else:
self.lbl.text = "*" * len(self.num)
return
# --- ostatní režimy ---
if not self.allow_fraction:
self.lbl.text = n
return
if self.den == "1":
self.lbl.text = n
else:
self.lbl.text = f"{n}/{self.den}"
def _compose(self) -> str:
n = self.num if self.num else "0"
if not self.allow_fraction:
# čistý numerický výstup
return n
return f"{n}/{self.den}"
def _accept(self, *_):
if not self.num:
Logger.info("IGNORE empty accept")
return
Logger.info(f"NumberPad ACCEPT: {self.value}")
self._scanner_buffer = ""
# validace fraction
if self.when:
self.when = time()
if self.allow_fraction:
try:
num, den = self.value.split("/")
if int(num) == 0:
# 0 kusů → ignorovat
self.dismiss()
return
except Exception:
pass
if self.on_accept:
self.on_accept(self.value)
self.dismiss()
def _cancel(self, *_):
Logger.info("NumberPad CANCEL")
if self.on_cancel:
self.on_cancel()
self.dismiss()
def enable(self):
for w in self.children[0].children:
if hasattr(w, "disabled"):
w.disabled = False
# JQ
def on_open(self):
Logger.info("NumberPad opened → bind keyboard")
Window.bind(on_key_down=self._on_key_down)
Window.bind(on_textinput=self._on_textinput)
def on_dismiss(self):
Window.unbind(on_key_down=self._on_key_down)
Window.unbind(on_textinput=self._on_textinput)
def _keyboard_closed(self):
Logger.info("Keyboard closed")
self._keyboard = None
def _on_key_down(self, *args):
# ------------------------
# NORMALIZACE ARGUMENTŮ
# ------------------------
if len(args) == 4:
# Keyboard API
keyboard, keycode, text, modifiers = args
key = keycode[1] if isinstance(keycode, tuple) else keycode
elif len(args) == 5:
# Window API
window, key, scancode, text, modifiers = args
else:
return False
now = time()
# delta = now - self._last_key_time
self._last_key_time = now
# ------------------------
# CONTROL KEYS
# ------------------------
# 🔥 ENTER = scanner done
# Tohle fungovalo samostatně, ale ne z volani z loginscreen
# if key in ("enter", "numpadenter", "tab", 13, 271, 9):
# if self.auto_accept_scanner and self.allow_text:
# self._accept()
# else:
# self._accept()
# return True
if key in ("enter", "numpadenter", "tab", 13, 271, 9):
if not self.num:
return True
self._accept()
return True
if key in ("escape", 27):
self._cancel()
return True
if key in ("backspace", 8):
if self.num:
self.num = self.num[:-1]
self._refresh()
return True
return False
def _on_textinput(self, window, text):
Logger.info(f"TEXT INPUT: {text}")
# 🔥 převod CZ → čísla
if text:
text = text.translate(self._cz_to_num)
# anti-duplicate (ponecháme)
now = time()
if text == self._last_text and (now - self._last_text_time) < 0.02:
return True
self._last_text = text
self._last_text_time = now
if self.mode == "code":
for ch in text:
if ch in "0123456789*.":
self._press(ch)
return True
if self.allow_text:
allowed = set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%&?<>#*[]-_/.")
if text not in allowed:
return True
# 🔥 respektuj max_len
if self.max_len and len(self.num) >= self.max_len:
return True
self.num += text
self._refresh()
return True
for ch in text:
if ch in "0123456789":
self._press(ch)
elif ch in ",." and self.show_dot and not self.allow_fraction:
self._press(".")
return True
return False
# JQ
# =====================================================
# TEST APP
# =====================================================
class TestApp(App):
def build(self):
return Widget() # 👈 prázdný root
def on_start(self):
def done(val):
Logger.info(f"RESULT = {val}")
# pad = NumberPad(
# mode="code",
# max_len=15,
# mask=True,
# allow_fraction=False,
# on_accept=done,
# on_cancel=lambda *_: Logger.info("CANCEL"),
# allow_text=True,
# auto_accept_scanner=True,
# )
pad = NumberPad(
mode="code",
max_len=15,
# on_accept=self._accept,
# on_cancel=self._cancel,
allow_text=True,
auto_accept_scanner=True,
)
pad.open()
if __name__ == "__main__":
TestApp().run()
+2627
View File
File diff suppressed because it is too large Load Diff
+324
View File
@@ -0,0 +1,324 @@
#-------------------------------------------
# --- pokladna client, obchodni logika, main
#-------------------------------------------
from PySide6.QtWidgets import QApplication, QDialog, QMessageBox
import logging
import hashlib
import json
import data
import sys
import threading
import time
import re
import api_call
from datetime import datetime
from api_call import ApiContext
from tisky import show_receipt_preview, Printer, ConsolePrinter
from mujUI_IV import (POSMode, POSDialog, AccountSelectDialog, StornoSelectDialog,
PosOperation, PaymentDialog, ensure_user_logged_in, STUL_RE, POS_QSS)
from mujUI_IV import (messagebox, NumberPadDialog)
#from UI_Kivy.kivy_ui import NumberPadDialog, messagebox
#from config import (user, base_url, r_url, client_id, id_kas, username,
# password, token, refresh_token)
#----------------------------------------
logger = logging.getLogger("POS")
logger.setLevel(logging.DEBUG)
if not logger.handlers:
handler = logging.StreamHandler(sys.stderr) # ⬅️ DŮLEŽITÉ
formatter = logging.Formatter( "%(asctime)s [%(levelname)s] %(name)s: %(message)s" )
handler.setFormatter(formatter)
logger.addHandler(handler)
def setup_logging(debug: bool = False):
level = logging.DEBUG if debug else logging.INFO
root = logging.getLogger()
root.setLevel(level)
handler = logging.StreamHandler(sys.stderr) # ⬅️ DŮLEŽITÉ
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s: %(message)s" )
handler.setFormatter(formatter)
root.handlers.clear()
root.addHandler(handler)
def PosDialog_Normal(setup, cenik, ucet):
dlg = POSDialog(cenik, ucet, setup, mode=POSMode.NORMAL)
return dlg
if __name__ == "__main__":
debug = True #jiz jen pro tvorbu testovacich dat
setup_logging(debug = True)
logger = logging.getLogger(__name__)
if debug:
import tst_data
user = "Petr Kobrle"
# --- client setup
apictx = ApiContext(user = "Petr Kobrle, cisnik",
base_url = "http://127.0.0.1:8000",
refresh_url = "http://127.0.0.1:8000",
client_id = "01", #terminal
id_kas = "01", #cislo pokladny
#zde udaje pro zakazku
username = "Kobrle",
password = "heslo")
# --- startuj heartbeat
api_call.start_heartbeat(ctx=apictx)
app = QApplication(sys.argv)
app.setStyle("Fusion") # důležité
app.setStyleSheet(POS_QSS)
info = {"exists": False, "blocked": False, "blocked_by": ""}
messagebox(
f"Startuje pokladna, user:{apictx.user}\nuser zakazky:{apictx.username}",)
#buttons=("OK",),) parent=None, initial_index=0)
try:
api_call.login_API(apictx)
setup = api_call.load_setup_API(apictx)
except Exception as e:
messagebox(f"Chyba startu:\n{e}")
sys.exit(1)
#open_printer_from_setup(setup)
if debug:
# --- Vytvoreni a nacteni testovaciho ceniku, jen pri debug == True
api_call.debugrepl_cenik_API(apictx)
# --- Nacteni ceniku ze serveru pro danou pokladnu
cenik = api_call.load_cenik_API(apictx)
cisnik = None
max_pokusu = 3
while True:
if cisnik == None:
for pokus in range(max_pokusu):
cisnik = ensure_user_logged_in(None, apictx)
if cisnik == None:
messagebox("Zruseno prihlasovani\nPokladna se ukončí.")
sys.exit(0)
if cisnik:
apictx.user = cisnik.name
break
else:
zbyva = max_pokusu - pokus - 1
if zbyva > 0:
messagebox(f"Neplatné přihlášení.\nZbývá {zbyva} pokusů.")
else:
#po x neúspěšných pokusech
messagebox("Překročen maximální počet pokusů.\nPokladna se ukončí.")
sys.exit(0)
# --- nacte stoly/ucty ze serveru, jen pro moji pokladnu a closed == False
ucty_row = api_call.load_stoly_API(apictx, closed = False) #vrati svoje otevrene
dlg = AccountSelectDialog(ucty_row, apictx.client_id)
if dlg.exec() != QDialog.Accepted:
break
accountselectionaction = dlg.action
if accountselectionaction == "logof":
#po logof neni vybran zadny ucet, kod musi byt zde
cisnik = None
continue
elif accountselectionaction == "uzav": #uzaverka
...
continue
elif accountselectionaction == "view":
# výběr z uzavřených účtů
ucty_closed = api_call.load_stoly_API(apictx, closed=True)
print(ucty_closed)
if not ucty_closed:
messagebox("Žádné uzavřené účty k prohlížení.")
continue
dlg = StornoSelectDialog(ucty=ucty_closed, parent=None, mode="View")
if dlg.exec() != QDialog.Accepted:
continue
ucet_sel = dlg.selected_ucet
ucet = api_call.load_ucet_by_ucislo_API(apictx, ucislo=ucet_sel.ucislo)
# 🔍 pouze náhled žádné změny
show_receipt_preview(None, ucet)
continue
elif accountselectionaction == "stornopol":
#storno polozek z uzavrenych uctu (zatim neni Accountselection na tohle cesta)
...
continue
elif accountselectionaction == "stornuct" or accountselectionaction == "zmdrplt":
#storna z uzavrenych uctu
ucty = api_call.load_stoly_API(apictx, closed=True) #limitem a sortem na serveru
ucty_closed = [ u for u in ucty if not ((u.origin == "Storno") or (u.origin == "Stornovan") or (u.origin == "StorPaymChg"))]
storno_dlg = StornoSelectDialog( ucty=ucty_closed, parent=None,)
if storno_dlg.exec() != QDialog.Accepted: #vybrany ucet
continue
ucet = storno_dlg.selected_ucet
ucet = api_call.load_ucet_by_ucislo_API(apictx, ucislo=ucet.ucislo)
#storno, zmena druhu platby
ucet_puvodni = ucet.model_copy(deep=True) #ucet je pro storno
if not accountselectionaction == "zmdrplt":
logger.info(f"storno uctu {ucet.ucislo}" )
else:
logger.info(f"zmena druhu platby {ucet.ucislo}" )
#musime nejdriv vybrat nove platby, aby sla operace zrusit
ucet_novy = ucet.model_copy(deep=True) # novymi platbami
ucet_novy.platby = []
ucet_novy.storno = ""
ucet_novy.is_storno = f"Z{ucet_novy.ucislo}" #cislo zmeneneho
ucet_novy.ucislo = "" #bude to novy ucet
discount = ucet_novy.discount_abs
total_czk = ucet_novy.total_czk()
dlg = PaymentDialog(total=total_czk, discount_abs=discount,
parent=None, setup=setup )
if dlg.exec() != QDialog.Accepted:
#zatim jedina moznost jak odblokovat uzavreny ucet
api_call.save_ucet_API(apictx, ucet)
logger.info(f"zmena druhu platby {ucet.ucislol} zrusena" )
continue # obsluha zrušila platbu, tim i celou operaci
pay_result = dlg.result()
# vytvoř účet s novymi platbami
for p in pay_result.payments:
ucet_novy.platby.append(
data.Platba( code=p.code, nazev=p.nazev, suma=p.suma, unit=p.unit,
rate=p.rate, suma_czk=p.suma_czk, fiscal=p.fiscal, ))
ucet_novy.sumdph()
ucet_novy.stul = ""
ucet_novy.origin = "Zmena_Platby"
api_call.save_ucet_API(apictx, ucet_novy)
show_receipt_preview(None, ucet_novy)
#ted storno uctu (musi se stornovat i pro zmenu druhu platby)
ucet.autor = apictx.user
ucet.is_storno = ucet_puvodni.ucislo
ucet.origin = "Storno"
ucet.ucislo = ""
ucet.stul = ""
ucet.blocked_by = ""
ucet.closed_at = data.now_clk_str()
for dd in ucet.dane:
dd.zaklad = -dd.zaklad
ucet.datetime = data.stime_str()
ucet.discount_abs = -ucet.discount_abs
for pp in ucet.platby:
pp.suma = -pp.suma
pp.suma_czk = -pp.suma_czk
for po in ucet.poloz:
po.pocet = -po.pocet
po.kstornu = 0
resp = api_call.save_ucet_API(apictx, ucet)
show_receipt_preview(None, ucet)
ucet_puvodni.storno = resp.ucislo
ucet_puvodni.blocked_by = ""
if ucet_puvodni.origin == "Zmena_Platby":
ucet_puvodni.origin = "StorPaymChg"
#print(ucet_puvodni)
resp = api_call.save_ucet_API(apictx, ucet_puvodni)
if not resp or not resp.ucislo:
messagebox("Chyba storna operace nedokončena")
continue
#zbytek operaci pracuje s otevrenym uctem dle cisla stolu
ucet = dlg.selected_ucet
ucet = api_call.load_ucet_API(apictx, ucet.stul)
logger.debug(f"akce po vyberu stolu: {accountselectionaction},\nvybrany stul: {ucet.stul}" )
dlg = PosDialog_Normal(setup, cenik, ucet)
#print(f"u_main\n{u_main}\nu_sec\n{u_sec}\n dlg")
ret = dlg.exec()
# --- vyhodnoceni operace PosDialog
if ret != QDialog.Accepted or dlg.result is None:
api_call.unblock_ucet_API(apictx, ucet.stul)
logger.debug("POS zrušen (bez operace)")
continue
else:
res = dlg.result
logger.debug( f"""Operace {res.operation}\nUcet main\n{res.ucet_main}\n
Ucet secondary\n{res.ucet_secondary}""")
u_main = res.ucet_main # doplnime chybejici udaje
u_sec = res.ucet_secondary
logger.info(f"Operace PosDialog {res.operation}")
# --- zde provedeni operaci z PosDialog
#CANCEL = "none" # opusti POSDialog bez akce, ok
#PAY_FULL = "pay_full" # platba celého účtu , ok
#PAY_PARTIAL = "pay_partial" # částečná platba
#SPLIT = "split" # rozdělení účtu
#STORNO = "storno" # storno položek
#SAVE_UCET = "save_full" # ulozi zmeny uctu , ok
if res.operation == PosOperation.SAVE_UCET:
api_call.save_ucet_API(apictx, u_main) #implicitne unblock
#tisk bonu
continue
elif res.operation == PosOperation.PAY_FULL:
u_main.autor = apictx.user
u_main.closed_at = data.now_clk_str()
u_main.datetime = data.stime_str()
u_main.sumdph()
u_main.origin = "Normal"
api_call.save_ucet_API(apictx, u_main) #implicitne unblock
#tisk uctu
#printer = ConsolePrinter(None)
#tisk_uctu(u_main, printer)
show_receipt_preview(None, u_main)
continue
elif res.operation == PosOperation.PAY_PARTIAL:
#PosDialog nepropusti castecnou platbu bez vybranych polozek
u_sec.autor = apictx.user
u_sec.closed_at = data.now_clk_str()
u_sec.datetime = data.stime_str()
u_sec.sumdph()
u_sec.origin = "Normal"
resp=api_call.save_ucet_API(apictx, u_sec) #implicitne unblock
if u_main.poloz != []:
api_call.save_ucet_API(apictx, u_main)
#ulozi se na stejne misto, ci-li UPSERT nestaras se o stary
#print(f"u_main\n{u_main}\nu_sec\n{u_sec}\n")
#printer = ConsolePrinter(None)
#tisk_uctu(u_sec, printer)
show_receipt_preview(None, u_sec)
continue
elif res.operation == PosOperation.SPLIT:
stul = ""
if u_sec.poloz!=[]: # nic nevybrano k prevodu
while True:
dlg = NumberPadDialog(None, "Zadej číslo stolu")
if dlg.exec() != QDialog.Accepted:
break
stul = str(dlg.value()).strip()
if not stul:
messagebox(
"Neplatné číslo.\nČíslo stolu nesmí být prázdné.")
continue
if not STUL_RE.match(stul):
messagebox(
"Neplatný formát.\nČíslo stolu musí mít tvar 1999 nebo 1999.9", )
continue
info = api_call.is_ucet_blocked_API(apictx, stul)
if info["exists"] and info["blocked"]:
block_id_kas,_ = info["blocked_by"].split("|",1) #'01|13:11:49'
if block_id_kas != apictx.id_kas:
messagebox(
f"""Účet blokován.\nStůl {stul}
je právě otevřen na jiném POS\n({info['blocked_by']})""" )
continue #nove zadani stolu
break
#print(f"u_main\n{u_main}\nu_sec\n{u_sec}\nstul {stul}\nblocked{info}")
if u_main.stul != stul and stul != "":
if u_main.poloz==[]: #prevede se vse
api_call.merge_ucet_API(apictx, u_sec, stul)
api_call.unblock_ucet_API(apictx, stul)
# pokud tam nic nezbyde
api_call.delete_ucet_API(apictx, stul=u_main.stul)
else:
api_call.save_ucet_API(apictx, u_main)
api_call.merge_ucet_API(apictx, u_sec, stul)
api_call.unblock_ucet_API(apictx, u_main.stul)
api_call.unblock_ucet_API(apictx, stul)
else:
api_call.unblock_ucet_API(apictx, u_main.stul)
else: #nic se neprevadelo, jen odblokuj u_main
api_call.unblock_ucet_API(apictx, u_main.stul)
continue
elif res.operation == PosOperation.STORNO:
#print(f"u_main\n{u_main}\nu_sec\n{u_sec}\n res.operation")
#sys.exit(0)
for i in u_sec.poloz:
#print bon z i
...
api_call.save_ucet_API(apictx, u_main)
continue
+7313
View File
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
from __future__ import annotations
from contextlib import contextmanager
import data
class PostgresServiceError(Exception):
pass
def _load_driver():
try:
import psycopg
return "psycopg", psycopg
except Exception:
pass
try:
import psycopg2
return "psycopg2", psycopg2
except Exception as e:
raise PostgresServiceError("Nie je nainstalovany PostgreSQL driver psycopg ani psycopg2.") from e
def is_configured(conn: data.PostgresConnection) -> bool:
return bool(
conn.host.strip()
and conn.database.strip()
and conn.user.strip()
and int(conn.port or 0) > 0
)
def connection_kwargs(conn: data.PostgresConnection) -> dict:
kwargs = {
"host": conn.host,
"port": int(conn.port or 5432),
"dbname": conn.database,
"user": conn.user,
"password": conn.password,
"connect_timeout": int(conn.connect_timeout or 5),
}
if conn.sslmode:
kwargs["sslmode"] = conn.sslmode
return kwargs
def test_connection(conn: data.PostgresConnection) -> None:
if not conn.enabled:
raise PostgresServiceError("PostgreSQL pripojenie nie je povolene pre instalaciu.")
if not is_configured(conn):
raise PostgresServiceError("PostgreSQL pripojenie nie je vyplnene.")
driver_name, driver = _load_driver()
try:
pg = driver.connect(**connection_kwargs(conn))
try:
cur = pg.cursor()
try:
cur.execute("SELECT 1")
cur.fetchone()
finally:
cur.close()
finally:
pg.close()
except Exception as e:
raise PostgresServiceError(f"PostgreSQL spojenie zlyhalo ({driver_name}): {e}") from e
def open_connection(conn: data.PostgresConnection):
if not conn.enabled:
raise PostgresServiceError("PostgreSQL pripojenie nie je povolene pre instalaciu.")
if not is_configured(conn):
raise PostgresServiceError("PostgreSQL pripojenie nie je vyplnene.")
driver_name, driver = _load_driver()
try:
return driver.connect(**connection_kwargs(conn))
except Exception as e:
raise PostgresServiceError(f"PostgreSQL spojenie zlyhalo ({driver_name}): {e}") from e
@contextmanager
def connect(conn: data.PostgresConnection):
if not conn.enabled:
raise PostgresServiceError("PostgreSQL pripojenie nie je povolene pre instalaciu.")
if not is_configured(conn):
raise PostgresServiceError("PostgreSQL pripojenie nie je vyplnene.")
driver_name, driver = _load_driver()
try:
pg = driver.connect(**connection_kwargs(conn))
except Exception as e:
raise PostgresServiceError(f"PostgreSQL spojenie zlyhalo ({driver_name}): {e}") from e
try:
yield pg
finally:
pg.close()
+178
View File
@@ -0,0 +1,178 @@
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.metrics import dp
# 👉 import tvého numpadu (uprav cestu podle projektu)
from numberpad import NumberPad
# =====================================================
# IP + PORT INPUT (COMPACT)
# =====================================================
class IpPortInput(BoxLayout):
"""
mode:
"ip" jen IP
"port" jen port
"both" IP + port
"""
def __init__(self, *, mode="both", ip="192.168.0.1", port=8000, on_done=None, **kwargs):
super().__init__(
orientation="vertical",
spacing=dp(5),
size_hint_y=None,
height=dp(120),
padding=dp(5),
**kwargs
)
self.mode = mode
self.on_done = on_done
self.ip_parts = self._parse_ip(ip)
self.port = str(port)
# ================= DISPLAY =================
self.lbl = Label(
text=self._format(),
size_hint_y=None,
height=dp(25),
halign="center",
valign="middle",
)
self.lbl.bind(size=lambda *_: setattr(self.lbl, "text_size", self.lbl.size))
self.add_widget(self.lbl)
# ================= BUTTON ROW =================
row = BoxLayout(spacing=dp(3), height=dp(45), size_hint_y=None)
self.btn_ip = []
if mode in ("ip", "both"):
for i in range(4):
b = Button(text=self.ip_parts[i], font_size=14)
b.bind(on_press=lambda inst, idx=i: self._edit_ip(idx))
self.btn_ip.append(b)
row.add_widget(b)
if mode in ("port", "both"):
self.btn_port = Button(text=self.port, font_size=14)
self.btn_port.bind(on_press=self._edit_port)
row.add_widget(self.btn_port)
self.add_widget(row)
# ================= OK BUTTON =================
btn_ok = Button(
text="OK",
size_hint_y=None,
height=dp(40),
background_color=(0.2, 0.6, 0.2, 1),
)
btn_ok.bind(on_press=self._done)
self.add_widget(btn_ok)
# =================================================
# PARSE / FORMAT
# =================================================
def _parse_ip(self, ip):
try:
parts = ip.split(".")
return [str(int(p)) for p in parts[:4]]
except Exception:
return ["192", "168", "0", "1"]
def _format(self):
if self.mode == "ip":
return ".".join(self.ip_parts)
if self.mode == "port":
return self.port
return f"{'.'.join(self.ip_parts)}:{self.port}"
def get_value(self):
if self.mode == "ip":
return ".".join(self.ip_parts)
if self.mode == "port":
return int(self.port)
return ".".join(self.ip_parts), int(self.port)
# =================================================
# EDIT IP PART
# =================================================
def _edit_ip(self, idx):
def done(val):
try:
v = int(val)
if 0 <= v <= 255:
self.ip_parts[idx] = str(v)
self.btn_ip[idx].text = str(v)
self._refresh()
except Exception:
pass
NumberPad(
mode="number",
max_len=3,
allow_fraction=False,
initial_value=self.ip_parts[idx],
on_accept=done,
).open()
# =================================================
# EDIT PORT
# =================================================
def _edit_port(self, *_):
def done(val):
try:
v = int(val)
if 0 <= v <= 65535:
self.port = str(v)
self.btn_port.text = str(v)
self._refresh()
except Exception:
pass
NumberPad(
mode="number",
max_len=5,
allow_fraction=False,
initial_value=self.port,
on_accept=done,
).open()
# =================================================
# REFRESH
# =================================================
def _refresh(self):
self.lbl.text = self._format()
# =================================================
# DONE
# =================================================
def _done(self, *_):
if self.on_done:
self.on_done(self.get_value())
# =====================================================
# TEST APP
# =====================================================
class TestApp(App):
def build(self):
root = BoxLayout(padding=dp(20))
widget = IpPortInput(
mode="both",
ip="10.0.0.5",
port=8080,
on_done=lambda v: print("RESULT:", v),
)
root.add_widget(widget)
return root
if __name__ == "__main__":
TestApp().run()
+8
View File
@@ -0,0 +1,8 @@
kivy
fastapi
uvicorn[standard]
pydantic
requests
qrcode[pil]
psycopg[binary]
jinja2
+12
View File
@@ -0,0 +1,12 @@
{
"user": "SYSTEM",
"base_url": "http://192.168.180.133:8000",
"refresh_url": "http://192.168.180.133:8000/refresh/",
"client_id": "01",
"id_kas": "01",
"username": "Kobrle",
"password": "heslo",
"bill_printer": "192.168.0.148:9100",
"bon_printer1": "192.168.0.148:9100",
"bon_printer2": ""
}
+1015
View File
File diff suppressed because it is too large Load Diff
+62832
View File
File diff suppressed because it is too large Load Diff
+12492
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More