252 lines
7.3 KiB
Python
252 lines
7.3 KiB
Python
from PySide6.QtWidgets import (
|
||
QDialog, QVBoxLayout, QTextEdit,
|
||
QPushButton, QHBoxLayout, QPlainTextEdit
|
||
)
|
||
from PySide6.QtGui import QFont
|
||
from PySide6.QtCore import Qt
|
||
import os
|
||
import builtins
|
||
import socket
|
||
|
||
def open_printer_from_setup(setup):
|
||
if setup.printer_type == "tcp":
|
||
...
|
||
#return open_printer_tcp(
|
||
# setup.printer_host,
|
||
# setup.printer_port or 9100)
|
||
|
||
|
||
|
||
def show_receipt_preview(parent, ucet, width: int = 40):
|
||
dlg = QDialog(parent)
|
||
dlg.setWindowTitle("Náhled účtenky")
|
||
dlg.setModal(True)
|
||
dlg.resize(600, 780)
|
||
|
||
main = QVBoxLayout(dlg)
|
||
main.setSpacing(10)
|
||
main.setContentsMargins(10, 10, 10, 10)
|
||
|
||
# =====================================================
|
||
# TEXT ÚČTENKY – MONOSPACE (VYNUCENÝ)
|
||
# =====================================================
|
||
receipt = QPlainTextEdit()
|
||
receipt.setReadOnly(True)
|
||
receipt.setLineWrapMode(QPlainTextEdit.NoWrap)
|
||
|
||
# 🔒 VYNUCENÝ MONOSPACE – žádné QSS to nepřebije
|
||
font = QFont()
|
||
font.setFamily("Consolas")
|
||
font.setStyleHint(QFont.Monospace)
|
||
font.setFixedPitch(True)
|
||
font.setPointSize(11)
|
||
|
||
receipt.setFont(font)
|
||
|
||
# pojistka proti QSS
|
||
receipt.setStyleSheet("""
|
||
QPlainTextEdit {
|
||
font-family: Consolas, "Courier New", monospace;
|
||
font-size: 11pt;
|
||
}
|
||
""")
|
||
|
||
receipt.setPlainText("\n".join(format_uctu_text(ucet, width)))
|
||
main.addWidget(receipt, stretch=1)
|
||
|
||
# =====================================================
|
||
# SPODNÍ TOUCH PANEL
|
||
# =====================================================
|
||
bottom = QHBoxLayout()
|
||
bottom.setSpacing(12)
|
||
|
||
BTN_W = 180
|
||
BTN_H = 70
|
||
|
||
btn_print = QPushButton("TISK")
|
||
btn_print.setEnabled(False)
|
||
btn_print.setFixedSize(BTN_W, BTN_H)
|
||
btn_print.setStyleSheet("""
|
||
QPushButton {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
background: #CCCCCC;
|
||
color: #666666;
|
||
}
|
||
""")
|
||
|
||
btn_close = QPushButton("ZAVŘÍT")
|
||
btn_close.setFixedSize(BTN_W, BTN_H)
|
||
btn_close.setStyleSheet("""
|
||
QPushButton {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
background: #E6B3B3;
|
||
color: black;
|
||
}
|
||
QPushButton:pressed {
|
||
background: #D99A9A;
|
||
}
|
||
""")
|
||
btn_close.clicked.connect(dlg.accept)
|
||
|
||
bottom.addStretch(1)
|
||
bottom.addWidget(btn_print)
|
||
bottom.addWidget(btn_close)
|
||
|
||
main.addLayout(bottom)
|
||
|
||
dlg.exec()
|
||
|
||
|
||
|
||
def format_uctu_str( ucet, width: int = 40) -> str:
|
||
return "\n".join(format_uctu_text(ucet, width))
|
||
|
||
def format_uctu_text(ucet, width: int = 40) -> list[str]:
|
||
lines: list[str] = []
|
||
|
||
def line(left: str = "", right: str = ""):
|
||
#Zarovná left vlevo a right doprava na pevnou šířku
|
||
if right:
|
||
space = width - len(left) - len(right)
|
||
if space < 1:
|
||
space = 1
|
||
lines.append((left + " " * space + right)[:width])
|
||
else:
|
||
lines.append(left[:width])
|
||
|
||
def sep(ch: str = "-"):
|
||
lines.append(ch * width)
|
||
|
||
discount = round(getattr(ucet, "discount_abs", 0.0) or 0.0, 2)
|
||
# ================= HLAVIČKA =================
|
||
sep("=")
|
||
dt = ucet.closed_at or ucet.datetime or ""
|
||
lines.append(dt.center(width))
|
||
cislo = ucet.ucislo or "-"
|
||
lines.append(f"UCET {cislo}".center(width))
|
||
# STORNO OZNAČENÍ
|
||
if getattr(ucet, "is_storno", None):
|
||
lines.append(" STORNO ".center(width))
|
||
if ucet.autor:
|
||
lines.append(f"Obsluha: {ucet.autor}".center(width))
|
||
sep("=")
|
||
# ================= POLOŽKY =================
|
||
total = 0.0
|
||
total_before_discount = 0.0
|
||
for p in ucet.poloz:
|
||
|
||
# je to dělená porce?
|
||
delene = bool(p.delitel and p.delitel != 1)
|
||
|
||
if delene:
|
||
kusu = p.pocet / p.delitel
|
||
qty_txt = f"{p.pocet}×1/{p.delitel}" # např. 2×1/2
|
||
else:
|
||
kusu = p.pocet
|
||
qty_txt = str(p.pocet)
|
||
|
||
cena = round(p.cena * kusu, 2)
|
||
cena_before = round((p.cena_puv if getattr(p, "cena_puv", None) is not None else p.cena) * kusu, 2)
|
||
total += cena
|
||
total_before_discount += cena_before
|
||
|
||
# ====== JEDEN ŘÁDEK – jen NEDĚLENÝ 1 KS ======
|
||
if not delene and kusu == 1:
|
||
left = p.nazev
|
||
right = f"{cena:.2f}"
|
||
line(left.ljust(width - len(right)) + right)
|
||
|
||
# ====== DĚLENÉ NEBO VÍCE KS → VŽDY DVA ŘÁDKY ======
|
||
else:
|
||
line(p.nazev[:width])
|
||
left = f"{qty_txt} x {p.cena:.2f}"
|
||
right = f"{cena:.2f}"
|
||
line(left.ljust(width - len(right)) + right)
|
||
# ================= SOUČET =================
|
||
sep("-")
|
||
prorated = getattr(ucet, "discounts_prorated", False)
|
||
payable = total if prorated else total - discount
|
||
subtotal_for_print = total_before_discount if prorated else total
|
||
line("MEZISOUČET", f"{subtotal_for_print:.2f} Kč")
|
||
if discount != 0:
|
||
if discount > 0:
|
||
line("SLEVA", f"-{discount:.2f} Kč")
|
||
else:
|
||
line("PRIRAZKA", f"{-discount:.2f} Kč")
|
||
sep("-")
|
||
line("K ÚHRADĚ", f"{payable:.2f} Kč")
|
||
else:
|
||
line("K ÚHRADĚ", f"{payable:.2f} Kč")
|
||
# ================= DPH =================
|
||
sep("-")
|
||
for d in ucet.dane:
|
||
# d.rate např. "0.21"
|
||
_, pct = d.rate.split(".", 1)
|
||
pct = pct.ljust(2, "0")
|
||
|
||
zaklad = round(d.zaklad, 2)
|
||
dph_castka = round(d.zaklad * float(d.rate), 2)
|
||
|
||
left = f"DPH {pct} % Zaklad {zaklad:.2f}"
|
||
right = f"{dph_castka:.2f}"
|
||
line(left, right)
|
||
|
||
# ================= PLATBY =================
|
||
if getattr(ucet, "platby", None):
|
||
sep("-")
|
||
for p in ucet.platby:
|
||
if p.unit != "CZK" and p.rate != 1:
|
||
left = f"{p.nazev} {p.suma:.2f} {p.unit}"
|
||
right = f"{p.suma * p.rate:.2f}"
|
||
else:
|
||
left = p.nazev
|
||
right = f"{p.suma:.2f}"
|
||
line(left, right)
|
||
|
||
sep("=")
|
||
return lines
|
||
|
||
def normalize_cp852(text: str) -> str:
|
||
replacements = {
|
||
"—": "-", "–": "-", "−": "-",
|
||
"“": '"', "”": '"', "„": '"',
|
||
"’": "'", "‘": "'",
|
||
"…": "...",
|
||
"\u00a0": " ", # NBSP
|
||
"č": "c", "ě": "e", "š": "s", "ř": "r",
|
||
"ž": "z", "ý": "y", "á": "a", "í": "i",
|
||
"é": "e", "ú": "u", "ů": "u",
|
||
}
|
||
for k, v in replacements.items():
|
||
text = text.replace(k, v)
|
||
return text
|
||
|
||
def tisk_uctu( ucet, printer, *, width: int = 40,
|
||
init_cmd: bytes = b"\x1b@",
|
||
cut_cmd: bytes = b"\x1dV\x00",):
|
||
# inicializace tiskárny
|
||
if init_cmd:
|
||
printer.write(init_cmd)
|
||
for l in format_uctu_text(ucet, width):
|
||
safe = normalize_cp852(l)
|
||
printer.write((safe + "\n").encode("cp852", errors="replace"))
|
||
# odřez
|
||
if cut_cmd:
|
||
printer.write(cut_cmd)
|
||
|
||
class Printer:
|
||
def __init__(self, stream):
|
||
self.stream = stream
|
||
def write(self, data: bytes):
|
||
self.stream.write(data)
|
||
def close(self):
|
||
if hasattr(self.stream, "close"):
|
||
self.stream.close()
|
||
|
||
class ConsolePrinter(Printer):
|
||
def write(self, data: bytes):
|
||
data = data.replace(b"\x1b", b"<ESC>")
|
||
print(data.decode("ascii", errors="replace"), end="")
|