notebookvb

This commit is contained in:
Vladimir Buzalka
2026-05-10 07:29:09 +02:00
parent 0f0e533a9e
commit 38568dc2ca
20 changed files with 41700 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
# QRPlatbaApp QR kód pro platby
## Přehled
Aplikace **QRPlatbaApp** generuje QR kódy pro platby pacientů v ordinaci. Údaje o pacientovi jsou předávány z Medicusu, aplikace zobrazí okno s výběrem položky k úhradě a generuje QR kód ve formátu **SPAYD** (Czech Payment Standard).
## Soubory
- `QRPlatbaApp.py` — hlavní Python skript (customtkinter GUI)
- `dist/QRPlatbaApp.exe` — kompilovaný spustitelný soubor (bez CMD okna)
## Technologie
- **Python 3.x** (nutný pouze pro vývoj, EXE běží bez Pythonu)
- **customtkinter** — moderní GUI
- **qrcode** — generování QR kódů
- **pillow (PIL)** — manipulace s obrázky
- **fdb** — připojení na Firebird DB (Medicus)
## Jak to funguje
### 1. Spouštění z Medicusu
Medicus předá údaje pacienta přes příkazový řádek do EXE:
```
QRPlatbaApp.exe %JMENO% %PRIJMENI% %RODCIS%
```
### 2. Načtení ceníku
Skript se připojí na Medicus DB a načte ceník ze stored procedure `VLV_SEL`:
```python
cur.execute("SELECT V.KOD, V.NAZEV, V.CENA FROM VLV_SEL(NULL, NULL, NULL) V ORDER BY V.KOD")
```
### 3. GUI okno
Zobrazí se okno s:
- Údaji pacienta (jméno, příjmení, rodné číslo)
- Výběrem položky k úhradě (dropdown)
- Výběrem bankovního účtu (2 možnosti: Fio 2100046291/2010 nebo 2800046620/2010)
- Tlačítko "Uložit QR kód"
- QR kód (200x200px, SPAYD formát)
### 4. SPAYD formát
QR kód obsahuje:
```
SPD*1.0*ACC:CZ7520100000002800046620*AM:500.00*CC:CZK*X-VS:7309281045*MSG:Buzalka%20Vladimír%20%20Poplatek
```
- `ACC` — IBAN bankovního účtu
- `AM` — částka (Kč)
- `CC` — měna (CZK)
- `X-VS` — variabilní symbol (rodné číslo pacienta)
- `MSG` — zpráva (jméno + položka)
### 5. Uložení QR
Po kliknutí na "Uložit QR kód" se PNG vygeneruje do adresáře:
```
U:\OrdinaceProjekt\Medicus\QRCode\QRPlatby\Buzalka_Vladimír_20260510_143025.png
```
## Konfigurace v Medicusu
V menu **Konfigurace > Externí programy** přidej nový záznam:
| Položka | Hodnota |
|---------|---------|
| **Kategorie** | Ostatní |
| **Typ programu** | Externí program |
| **Program** | `U:\OrdinaceProjekt\Medicus\QRCode\dist\QRPlatbaApp.exe` |
| **Příkazový řádek** | `"U:\OrdinaceProjekt\Medicus\QRCode\dist\QRPlatbaApp.exe" %JMENO% %PRIJMENI% %RODCIS%` |
| **Pacient** | ✓ (vyžaduje vybraného pacienta) |
| **Menu** | QR Platba |
## Firebird DB připojení
```python
DB_DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
DB_USER = 'SYSDBA'
DB_PASSWORD = 'masterkey'
DB_CHARSET = 'win1250'
```
## Bankovní účty
```python
ACCOUNTS = {
"2100046291/2010": "CZ1720100000002100046291",
"2800046620/2010": "CZ7520100000002800046620",
}
```
Oba účty jsou u Fio banky.
## Spouštění bez CMD okna
EXE soubor se zkompiloval pomocí PyInstalleru s parametrem `--windowed`, což zajistí spuštění bez zobrazení CMD okna. GUI se zobrazí přímo bez blikání.
## Příjmání argumentů
Python skript přijímá argumenty přesně z příkazového řádku:
```python
if len(sys.argv) >= 4:
JMENO = sys.argv[1] # arg 1 z Medicusu (%JMENO%)
PRIJMENI = sys.argv[2] # arg 2 z Medicusu (%PRIJMENI%)
RODCIS = sys.argv[3] # arg 3 z Medicusu (%RODCIS%)
```
## Ladění a opravy
Při chybách se informace vypíší v GUI (messagebox).
Pokud potřebuješ upravit zdrojový kód, edituj `QRPlatbaApp.py` a znovu zkompiluj EXE.
## Výstup
Generované QR kódy PNG se ukládají do:
```
U:\OrdinaceProjekt\Medicus\QRCode\QRPlatby\
├── Buzalka_Vladimír_20260510_143025.png
├── Buzalka_Vladimír_20260510_144030.png
└── ...
```
## PyInstaller kompilace
EXE soubor byl zkompilován příkazem:
```bash
pyinstaller --onefile --windowed --icon=qrcode.ico QRPlatbaApp.py
```
Výsledný EXE se nachází v `dist/QRPlatbaApp.exe` a je schopen běžet na počítačích bez Pythonu.
Při potřebě znovu kompilovat po změně kódu:
```bash
cd U:\OrdinaceProjekt\Medicus\QRCode
C:\Python312\Scripts\pyinstaller.exe --onefile --windowed --icon=qrcode.ico --clean QRPlatbaApp.py
```
+208
View File
@@ -0,0 +1,208 @@
#!/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(r"U:\OrdinaceProjekt\Medicus\Externi\QRCode\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()
+39
View File
@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['QRPlatbaApp.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='QRPlatbaApp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['qrcode.ico'],
)
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB