#------------------------------------------- # --- 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