324 lines
15 KiB
Python
324 lines
15 KiB
Python
#-------------------------------------------
|
||
# --- 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 1–999 nebo 1–999.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
|
||
|