Files
fio/QRPlatbaApp.py
T
administrator 89511c500f Add QR payment generator with live Medicus price list
- New QRPlatba/ module: standalone app for generating SPAYD QR payment codes
- Live price list loaded directly from Medicus Firebird DB (VLV_SEL), sorted by KOD
- Patient data (name, surname, RC) passed as arguments from Medicus button
- Auto-generates QR on load and refreshes on every dropdown change
- Account selector dropdown (two FIO accounts with IBAN)
- Save button exports QR as PNG next to the EXE (sys.executable-relative path)
- SPAYD format: ACC, AM, CC, X-VS (rodné číslo), MSG (patient + item)
- README.md with full documentation, Medicus button command, DB schema
- Add .gitignore (excludes build/, dist/, .venv/, .idea/, .claude/)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 20:07:10 +01:00

209 lines
7.3 KiB
Python
Raw 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import urllib.parse
import qrcode
import fdb
from pathlib import Path
from datetime import datetime
from PIL import Image, ImageTk
import customtkinter as ctk
from tkinter import messagebox
# ================================
# ⚙️ Default Configuration
# ================================
ACCOUNTS = {
"2100046291/2010": "CZ1720100000002100046291",
"2800046620/2010": "CZ7520100000002800046620",
}
CURRENCY = "CZK"
OUTPUT_DIR = Path("QRPlatby")
OUTPUT_DIR.mkdir(exist_ok=True)
# Firebird připojení
DB_DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
DB_USER = 'SYSDBA'
DB_PASSWORD = 'masterkey'
DB_CHARSET = 'win1250'
# Default values (can be overridden by arguments)
PRIJMENI = "Buzalka"
JMENO = "Vladimír"
RODCIS = "730928104"
# ================================
# 💬 Argument Handling
# ================================
if len(sys.argv) >= 4:
JMENO = sys.argv[1]
PRIJMENI = sys.argv[2]
RODCIS = sys.argv[3]
elif len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
print("Usage: QRPlatbaApp.py JMENO PRIJMENI RODCIS")
sys.exit(0)
# ================================
# 💉 Items to Pay načteno z Medicusu
# ================================
def nacti_polozky():
"""Načte ceník z Medicusu seřazený podle KOD (pořadového čísla)."""
try:
conn = fdb.connect(dsn=DB_DSN, user=DB_USER, password=DB_PASSWORD, charset=DB_CHARSET)
cur = conn.cursor()
cur.execute("SELECT V.KOD, V.NAZEV, V.CENA FROM VLV_SEL(NULL, NULL, NULL) V ORDER BY V.KOD")
rows = cur.fetchall()
conn.close()
# Vrátí OrderedDict: název -> cena (float)
return {row[1].strip(): float(row[2]) for row in rows if row[2] is not None}
except Exception as e:
messagebox.showerror("Chyba databáze", f"Nepodařilo se načíst ceník z Medicusu:\n{e}")
return {}
ITEMS = nacti_polozky()
# ================================
# 🧩 Helper
# ================================
def create_spayd(iban, amount, vs, msg, currency="CZK"):
msg_encoded = urllib.parse.quote(msg, safe="$%*+-.:/")
return f"SPD*1.0*ACC:{iban}*AM:{amount:.2f}*CC:{currency}*X-VS:{vs}*MSG:{msg_encoded}"
# ================================
# 🪟 GUI Class
# ================================
class QRPlatbaApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("QR Platba Ordinace MUDr. Buzalková")
self.geometry("520x680")
self.minsize(480, 480)
self.resizable(True, True)
ctk.set_appearance_mode("light")
ctk.set_default_color_theme("blue")
frame = ctk.CTkFrame(self, corner_radius=10)
frame.pack(expand=True, fill="both", padx=20, pady=20)
ctk.CTkLabel(frame, text="Generátor QR Platby",
font=("Arial", 20, "bold")).pack(pady=(8, 10))
# 👤 Patient Info
patient = ctk.CTkFrame(frame, corner_radius=8)
patient.pack(fill="x", pady=(0, 8), padx=10)
for text in [f"Příjmení: {PRIJMENI}",
f"Jméno: {JMENO}",
f"Rodné číslo: {RODCIS}"]:
ctk.CTkLabel(patient, text=text, font=("Arial", 12)).pack(anchor="w", padx=10, pady=1)
# 💰 Payment Section
pay = ctk.CTkFrame(frame, corner_radius=8)
pay.pack(fill="x", pady=(0, 8), padx=10)
ctk.CTkLabel(pay, text="Vyberte položku k úhradě:",
font=("Arial", 12, "bold")).pack(anchor="w", padx=10, pady=(6, 3))
self.display_items = [f"{name} ({price:.0f} Kč)" for name, price in ITEMS.items()]
self.item_map = {f"{name} ({price:.0f} Kč)": name for name, price in ITEMS.items()}
self.selected_item = ctk.StringVar(value=self.display_items[0])
self.combo = ctk.CTkOptionMenu(
pay,
variable=self.selected_item,
values=self.display_items,
font=("Arial", 12),
command=self.on_change
)
self.combo.pack(fill="x", padx=10)
self.amount_label = ctk.CTkLabel(pay, text="", font=("Arial", 12, "italic"))
self.amount_label.pack(anchor="e", padx=10, pady=(3, 6))
# 🏦 Account Selection
acc = ctk.CTkFrame(frame, corner_radius=8)
acc.pack(fill="x", pady=(0, 8), padx=10)
ctk.CTkLabel(acc, text="Číslo účtu:", font=("Arial", 12, "bold")).pack(anchor="w", padx=10, pady=(6, 3))
self.selected_account = ctk.StringVar(value=list(ACCOUNTS.keys())[0])
ctk.CTkOptionMenu(
acc,
variable=self.selected_account,
values=list(ACCOUNTS.keys()),
font=("Arial", 12),
command=self.on_change
).pack(fill="x", padx=10, pady=(0, 6))
ctk.CTkButton(frame, text="Uložit QR kód",
font=("Arial", 13, "bold"),
height=40,
command=self.ulozit_qr).pack(pady=6)
self.qr_label = ctk.CTkLabel(frame, text="")
self.qr_label.pack(pady=6)
ctk.CTkLabel(frame,
text="© Ordinace MUDr. Buzalková | QR Platba dle ČBA v1.2",
font=("Arial", 10),
text_color="#666").pack(side="bottom", pady=(10, 0))
self.center_window()
# QR automaticky při startu
self.after(100, self.refresh_qr)
# ================================
# 🪟 Center Window
# ================================
def center_window(self):
self.update_idletasks()
width = self.winfo_width()
height = self.winfo_height()
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
x = int((screen_width / 2) - (width / 2))
y = int((screen_height / 2) - (height / 2))
self.geometry(f"{width}x{height}+{x}+{y}")
# ================================
# 💸 Update and Generate
# ================================
def _get_current(self):
"""Vrátí (item, iban, spayd) pro aktuální výběr."""
display_item = self.selected_item.get()
item = self.item_map[display_item]
iban = ACCOUNTS[self.selected_account.get()]
spayd = create_spayd(iban, ITEMS[item], RODCIS, f"{PRIJMENI} {JMENO} {item}", CURRENCY)
return item, iban, spayd
def on_change(self, _=None):
"""Při změně dropdownu aktualizuj částku i QR."""
self.refresh_qr()
def refresh_qr(self):
"""Zobrazí QR kód pro aktuální výběr (bez uložení)."""
item, _, spayd = self._get_current()
self.amount_label.configure(text=f"Částka: {ITEMS[item]:.2f}")
img = qrcode.make(spayd)
img_resized = img.resize((200, 200), Image.LANCZOS)
qr_tk = ImageTk.PhotoImage(img_resized)
self.qr_label.configure(image=qr_tk)
self.qr_label.image = qr_tk
def ulozit_qr(self):
"""Uloží QR kód do souboru a informuje uživatele."""
item, _, spayd = self._get_current()
img = qrcode.make(spayd)
filename = f"{PRIJMENI}_{JMENO}_{datetime.now():%Y%m%d_%H%M%S}.png"
out_path = OUTPUT_DIR / filename
img.save(out_path)
messagebox.showinfo("Uloženo", f"QR kód uložen:\n{out_path}")
# ================================
# 🚀 Main
# ================================
if __name__ == "__main__":
app = QRPlatbaApp()
app.mainloop()