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

324 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#-------------------------------------------
# --- 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