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>
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
|
||||||
|
# Virtual environment
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# PyInstaller build artifacts
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Claude
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# Output folders
|
||||||
|
QRPlatby/
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
#!/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"
|
||||||
|
# sys.executable = cesta k EXE (i po přesunutí), __file__ by v onefile EXE ukazoval do temp
|
||||||
|
OUTPUT_DIR = Path(sys.executable).parent / "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í dict: název -> cena (float), seřazený podle KOD
|
||||||
|
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 – sestavení SPAYD řetězce
|
||||||
|
# ================================
|
||||||
|
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))
|
||||||
|
|
||||||
|
# 👤 Informace o pacientovi
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 💰 Výběr položky
|
||||||
|
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))
|
||||||
|
|
||||||
|
# 🏦 Výběr účtu
|
||||||
|
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 se vygeneruje automaticky po načtení okna
|
||||||
|
self.after(100, self.refresh_qr)
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# 🪟 Vycentrování okna
|
||||||
|
# ================================
|
||||||
|
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}")
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# 💸 QR logika
|
||||||
|
# ================================
|
||||||
|
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 – obnoví částku i QR kód."""
|
||||||
|
self.refresh_qr()
|
||||||
|
|
||||||
|
def refresh_qr(self):
|
||||||
|
"""Zobrazí QR kód pro aktuální výběr (bez uložení na disk)."""
|
||||||
|
item, _, spayd = self._get_current()
|
||||||
|
self.amount_label.configure(text=f"Částka: {ITEMS[item]:.2f} Kč")
|
||||||
|
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 PNG a zobrazí potvrzení."""
|
||||||
|
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()
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# QRPlatbaApp – Generátor QR plateb pro ordinaci MUDr. Buzalková
|
||||||
|
|
||||||
|
Aplikace pro generování QR kódů plateb dle standardu **SPAYD / ČBA** přímo z prostředí Medicus 3.
|
||||||
|
Ceník se načítá živě z databáze Medicusu, takže jakákoliv změna cen v Medicusu se okamžitě projeví.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Funkce
|
||||||
|
|
||||||
|
- Načítá ceník přímo z databáze Medicus (Firebird) – vždy aktuální ceny
|
||||||
|
- Pacientské údaje (jméno, příjmení, rodné číslo) přijímá jako argumenty z tlačítka Medicusu
|
||||||
|
- QR kód se zobrazí automaticky po otevření okna
|
||||||
|
- QR kód se obnoví při každé změně položky nebo čísla účtu
|
||||||
|
- Výběr ze dvou bankovních účtů (dropdown)
|
||||||
|
- Tlačítko **Uložit QR kód** uloží PNG soubor do složky `QRPlatby/`
|
||||||
|
- Variabilní symbol = rodné číslo pacienta
|
||||||
|
- Zpráva příjemci = `Příjmení Jméno – Název položky`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spuštění z Medicusu
|
||||||
|
|
||||||
|
Příkaz pro tlačítko v Medicusu:
|
||||||
|
|
||||||
|
```
|
||||||
|
"U:\PycharmProjects\FIO\.venv\Scripts\pythonw.exe" "U:\PycharmProjects\FIO\QRPlatba\QRPlatbaApp.py" "%JMENO%" "%PRIJMENI%" "%RODCISN%"
|
||||||
|
```
|
||||||
|
|
||||||
|
> `pythonw.exe` se používá místo `python.exe`, aby se nezobrazovalo černé konzolové okno.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Struktura projektu
|
||||||
|
|
||||||
|
```
|
||||||
|
QRPlatba/
|
||||||
|
├── QRPlatbaApp.py # Hlavní aplikace
|
||||||
|
├── README.md # Tato dokumentace
|
||||||
|
└── QRPlatby/ # Výstupní složka pro uložené QR kódy (PNG)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Závislosti
|
||||||
|
|
||||||
|
Nainstalovat přes pip do projektového venv:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install customtkinter pillow qrcode fdb
|
||||||
|
```
|
||||||
|
|
||||||
|
| Balíček | Účel |
|
||||||
|
|-----------------|-------------------------------------------|
|
||||||
|
| `customtkinter` | Moderní GUI (tmavý/světlý režim) |
|
||||||
|
| `pillow` | Práce s obrázky (zobrazení QR v okně) |
|
||||||
|
| `qrcode` | Generování QR kódů |
|
||||||
|
| `fdb` | Připojení k Firebird databázi (Medicus) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Databázové připojení
|
||||||
|
|
||||||
|
Aplikace se připojuje k lokální Firebird databázi Medicusu:
|
||||||
|
|
||||||
|
| Parametr | Hodnota |
|
||||||
|
|-----------|------------------------------------------|
|
||||||
|
| DSN | `localhost:c:\medicus 3\data\medicus.fdb`|
|
||||||
|
| Uživatel | `SYSDBA` |
|
||||||
|
| Charset | `win1250` |
|
||||||
|
| View | `VLV_SEL` – ceník výkonů |
|
||||||
|
|
||||||
|
Položky jsou seřazeny podle pole `KOD` (pořadové číslo v ceníku Medicusu).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Formát QR kódu (SPAYD)
|
||||||
|
|
||||||
|
QR kód odpovídá standardu **Short Payment Descriptor (SPAYD)** dle specifikace České bankovní asociace.
|
||||||
|
|
||||||
|
Příklad vygenerovaného řetězce:
|
||||||
|
```
|
||||||
|
SPD*1.0*ACC:CZ1720100000002100046291*AM:600.00*CC:CZK*X-VS:486122443*MSG:Abohamda%20Horia%20%E2%80%93%20O%C4%8Dkov%C3%A1n%C3%AD%20ch%C5%99ipka%20(VAXIGRIP)
|
||||||
|
```
|
||||||
|
|
||||||
|
| Pole | Obsah |
|
||||||
|
|---------|------------------------------------|
|
||||||
|
| `ACC` | IBAN cílového účtu |
|
||||||
|
| `AM` | Částka v Kč |
|
||||||
|
| `CC` | Měna (vždy CZK) |
|
||||||
|
| `X-VS` | Variabilní symbol = rodné číslo |
|
||||||
|
| `MSG` | Příjmení Jméno – Název položky |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bankovní účty
|
||||||
|
|
||||||
|
| Číslo účtu | IBAN |
|
||||||
|
|------------------|----------------------------|
|
||||||
|
| 2100046291/2010 | CZ1720100000002100046291 |
|
||||||
|
| 2800046620/2010 | CZ7520100000002800046620 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Uložené QR kódy
|
||||||
|
|
||||||
|
Soubory se ukládají do složky `QRPlatby/` ve formátu:
|
||||||
|
|
||||||
|
```
|
||||||
|
Příjmení_Jméno_YYYYMMDD_HHMMSS.png
|
||||||
|
```
|
||||||
|
|
||||||
|
Příklad: `Abohamda_Horia_20260325_143022.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*© Ordinace MUDr. Buzalková | QR Platba dle ČBA v1.2*
|
||||||
+75
-32
@@ -4,6 +4,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import qrcode
|
import qrcode
|
||||||
|
import fdb
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
@@ -14,11 +15,20 @@ from tkinter import messagebox
|
|||||||
# ================================
|
# ================================
|
||||||
# ⚙️ Default Configuration
|
# ⚙️ Default Configuration
|
||||||
# ================================
|
# ================================
|
||||||
IBAN = "CZ7520100000002800046620"
|
ACCOUNTS = {
|
||||||
|
"2100046291/2010": "CZ1720100000002100046291",
|
||||||
|
"2800046620/2010": "CZ7520100000002800046620",
|
||||||
|
}
|
||||||
CURRENCY = "CZK"
|
CURRENCY = "CZK"
|
||||||
OUTPUT_DIR = Path("QRPlatby")
|
OUTPUT_DIR = Path("QRPlatby")
|
||||||
OUTPUT_DIR.mkdir(exist_ok=True)
|
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)
|
# Default values (can be overridden by arguments)
|
||||||
PRIJMENI = "Buzalka"
|
PRIJMENI = "Buzalka"
|
||||||
JMENO = "Vladimír"
|
JMENO = "Vladimír"
|
||||||
@@ -36,12 +46,23 @@ elif len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# ================================
|
# ================================
|
||||||
# 💉 Items to Pay
|
# 💉 Items to Pay – načteno z Medicusu
|
||||||
# ================================
|
# ================================
|
||||||
ITEMS = {
|
def nacti_polozky():
|
||||||
"Očkování chřipka Vaxigrip": 600.00,
|
"""Načte ceník z Medicusu seřazený podle KOD (pořadového čísla)."""
|
||||||
"Očkování chřipka Efluelda": 1300.00,
|
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
|
# 🧩 Helper
|
||||||
@@ -58,7 +79,7 @@ class QRPlatbaApp(ctk.CTk):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.title("QR Platba – Ordinace MUDr. Buzalková")
|
self.title("QR Platba – Ordinace MUDr. Buzalková")
|
||||||
self.geometry("520x520")
|
self.geometry("520x680")
|
||||||
self.minsize(480, 480)
|
self.minsize(480, 480)
|
||||||
self.resizable(True, True)
|
self.resizable(True, True)
|
||||||
|
|
||||||
@@ -69,21 +90,21 @@ class QRPlatbaApp(ctk.CTk):
|
|||||||
frame.pack(expand=True, fill="both", padx=20, pady=20)
|
frame.pack(expand=True, fill="both", padx=20, pady=20)
|
||||||
|
|
||||||
ctk.CTkLabel(frame, text="Generátor QR Platby",
|
ctk.CTkLabel(frame, text="Generátor QR Platby",
|
||||||
font=("Arial", 20, "bold")).pack(pady=(10, 20))
|
font=("Arial", 20, "bold")).pack(pady=(8, 10))
|
||||||
|
|
||||||
# 👤 Patient Info
|
# 👤 Patient Info
|
||||||
patient = ctk.CTkFrame(frame, corner_radius=8)
|
patient = ctk.CTkFrame(frame, corner_radius=8)
|
||||||
patient.pack(fill="x", pady=(0, 20), padx=10)
|
patient.pack(fill="x", pady=(0, 8), padx=10)
|
||||||
for text in [f"Příjmení: {PRIJMENI}",
|
for text in [f"Příjmení: {PRIJMENI}",
|
||||||
f"Jméno: {JMENO}",
|
f"Jméno: {JMENO}",
|
||||||
f"Rodné číslo: {RODCIS}"]:
|
f"Rodné číslo: {RODCIS}"]:
|
||||||
ctk.CTkLabel(patient, text=text, font=("Arial", 12)).pack(anchor="w", padx=10, pady=2)
|
ctk.CTkLabel(patient, text=text, font=("Arial", 12)).pack(anchor="w", padx=10, pady=1)
|
||||||
|
|
||||||
# 💰 Payment Section
|
# 💰 Payment Section
|
||||||
pay = ctk.CTkFrame(frame, corner_radius=8)
|
pay = ctk.CTkFrame(frame, corner_radius=8)
|
||||||
pay.pack(fill="x", pady=(0, 20), padx=10)
|
pay.pack(fill="x", pady=(0, 8), padx=10)
|
||||||
ctk.CTkLabel(pay, text="Vyberte položku k úhradě:",
|
ctk.CTkLabel(pay, text="Vyberte položku k úhradě:",
|
||||||
font=("Arial", 12, "bold")).pack(anchor="w", padx=10, pady=(10, 5))
|
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.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.item_map = {f"{name} ({price:.0f} Kč)": name for name, price in ITEMS.items()}
|
||||||
@@ -94,21 +115,33 @@ class QRPlatbaApp(ctk.CTk):
|
|||||||
variable=self.selected_item,
|
variable=self.selected_item,
|
||||||
values=self.display_items,
|
values=self.display_items,
|
||||||
font=("Arial", 12),
|
font=("Arial", 12),
|
||||||
command=self.update_amount
|
command=self.on_change
|
||||||
)
|
)
|
||||||
self.combo.pack(fill="x", padx=10)
|
self.combo.pack(fill="x", padx=10)
|
||||||
|
|
||||||
self.amount_label = ctk.CTkLabel(pay, text="", font=("Arial", 12, "italic"))
|
self.amount_label = ctk.CTkLabel(pay, text="", font=("Arial", 12, "italic"))
|
||||||
self.amount_label.pack(anchor="e", padx=10, pady=(5, 10))
|
self.amount_label.pack(anchor="e", padx=10, pady=(3, 6))
|
||||||
self.update_amount()
|
|
||||||
|
|
||||||
ctk.CTkButton(frame, text="Vytvořit QR Platbu",
|
# 🏦 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"),
|
font=("Arial", 13, "bold"),
|
||||||
height=40,
|
height=40,
|
||||||
command=self.generate_qr).pack(pady=10)
|
command=self.ulozit_qr).pack(pady=6)
|
||||||
|
|
||||||
self.qr_label = ctk.CTkLabel(frame, text="")
|
self.qr_label = ctk.CTkLabel(frame, text="")
|
||||||
self.qr_label.pack(pady=15)
|
self.qr_label.pack(pady=6)
|
||||||
|
|
||||||
ctk.CTkLabel(frame,
|
ctk.CTkLabel(frame,
|
||||||
text="© Ordinace MUDr. Buzalková | QR Platba dle ČBA v1.2",
|
text="© Ordinace MUDr. Buzalková | QR Platba dle ČBA v1.2",
|
||||||
@@ -116,6 +149,8 @@ class QRPlatbaApp(ctk.CTk):
|
|||||||
text_color="#666").pack(side="bottom", pady=(10, 0))
|
text_color="#666").pack(side="bottom", pady=(10, 0))
|
||||||
|
|
||||||
self.center_window()
|
self.center_window()
|
||||||
|
# QR automaticky při startu
|
||||||
|
self.after(100, self.refresh_qr)
|
||||||
|
|
||||||
# ================================
|
# ================================
|
||||||
# 🪟 Center Window
|
# 🪟 Center Window
|
||||||
@@ -133,28 +168,36 @@ class QRPlatbaApp(ctk.CTk):
|
|||||||
# ================================
|
# ================================
|
||||||
# 💸 Update and Generate
|
# 💸 Update and Generate
|
||||||
# ================================
|
# ================================
|
||||||
def update_amount(self, _=None):
|
def _get_current(self):
|
||||||
|
"""Vrátí (item, iban, spayd) pro aktuální výběr."""
|
||||||
display_item = self.selected_item.get()
|
display_item = self.selected_item.get()
|
||||||
item = self.item_map[display_item]
|
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} Kč")
|
self.amount_label.configure(text=f"Částka: {ITEMS[item]:.2f} Kč")
|
||||||
|
|
||||||
def generate_qr(self):
|
|
||||||
display_item = self.selected_item.get()
|
|
||||||
item = self.item_map[display_item]
|
|
||||||
spayd = create_spayd(IBAN, ITEMS[item], RODCIS, f"{PRIJMENI} {JMENO} – {item}", CURRENCY)
|
|
||||||
img = qrcode.make(spayd)
|
img = qrcode.make(spayd)
|
||||||
filename = f"{PRIJMENI}_{JMENO}_{datetime.now():%Y%m%d_%H%M%S}.png"
|
img_resized = img.resize((200, 200), Image.LANCZOS)
|
||||||
out_path = OUTPUT_DIR / filename
|
|
||||||
img.save(out_path)
|
|
||||||
|
|
||||||
img_resized = img.resize((300, 300))
|
|
||||||
qr_tk = ImageTk.PhotoImage(img_resized)
|
qr_tk = ImageTk.PhotoImage(img_resized)
|
||||||
self.qr_label.configure(image=qr_tk)
|
self.qr_label.configure(image=qr_tk)
|
||||||
self.qr_label.image = qr_tk
|
self.qr_label.image = qr_tk
|
||||||
|
|
||||||
self.update_idletasks()
|
def ulozit_qr(self):
|
||||||
self.geometry(f"{self.winfo_reqwidth()}x{self.winfo_reqheight()}")
|
"""Uloží QR kód do souboru a informuje uživatele."""
|
||||||
self.center_window()
|
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}")
|
||||||
|
|
||||||
|
|
||||||
# ================================
|
# ================================
|
||||||
|
|||||||
Reference in New Issue
Block a user