notebookvb
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
# MedicusWithClaude – poznámky pro Clauda
|
||||
|
||||
## Stav projektu
|
||||
- Zahájeno: 2026-03-13
|
||||
- Cíl: průzkum Firebird DB Medicus → analýzy a reporty
|
||||
- Zatím: úspěšně čteme i zapisujeme do DB, rozumíme RTF formátu
|
||||
- **2026-03-17**: Obnovena session – Claude si přečetl poznámky, připraven pokračovat
|
||||
- **2026-03-18**: Obnovena session – Claude si přečetl poznámky, připraven pokračovat. Průběžně zapisuje do tohoto souboru.
|
||||
- **2026-03-20**: Obnovena session – merge logika z `test_import_single.py` integrována do `s03soubory.py`. Import pipeline kompletní.
|
||||
- **2026-04-01**: Opravena chyba `BlobReader invalid BLOB handle` v `dekurz_report.py` – viz níže.
|
||||
|
||||
## Bezpečnost
|
||||
- Pracujeme na **místní kopii** – poškození DB nevadí, obnova = 5 minut
|
||||
- Lze bez obav experimentovat, zapisovat testovací data atd.
|
||||
- Testovací záznamy nemusíme mazat
|
||||
|
||||
## Připojení k DB
|
||||
```python
|
||||
import fdb
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
```
|
||||
- fdb verze 2.0.4, Python na PATH (prostý `python` příkaz)
|
||||
- Firebird instalace: `C:\Program Files\Firebird\Firebird_2_5_CGM\bin\`
|
||||
- Jedná se o **HQbird** (IBSurgeon enhanced Firebird) – port **3070** (ne standardní 3050)
|
||||
- fbtracemgr trace nefungoval spolehlivě – doporučeno použít **FBScanner** (stejný výrobce)
|
||||
|
||||
## O Medicusu
|
||||
- Lékařský software pro praktického lékaře
|
||||
- Vyvíjen od ~roku 2000, organicky rostl přidáváním funkcí
|
||||
- Firebird 2.5 – nikdy nepřejdou na nový (desetitisíce instalací, 993 tabulek)
|
||||
- Žádné zabezpečení DB – vše čitelné přes SYSDBA/masterkey
|
||||
- Charset: win1250
|
||||
|
||||
## DB fakta
|
||||
- 993 tabulek celkem
|
||||
- Charset: win1250
|
||||
|
||||
## Klíčové tabulky
|
||||
|
||||
### KAR – Kartotéka (základní kámen Medicusu)
|
||||
- 6319 pacientů – všichni kdo kdy prošli ordinací
|
||||
- Primární klíč: **IDPAC** (interní ID, používá se jako FK v ostatních tabulkách)
|
||||
- Identifikace pacienta v ČR/pojišťovna: **RODCIS** (rodné číslo, bez lomítka, např. 7309208104)
|
||||
- **PRIJMENI**, **JMENO**, TITUL, TITULZA, DATNAR, POHLAVI
|
||||
- **POJ** – pojišťovna (3 znaky, např. 111 = VZP)
|
||||
- Adresy: TRV* (trvalé), PRE* (přechodné)
|
||||
- **VYRAZEN** – příznak vyřazeného pacienta
|
||||
- HLAVDGN – hlavní diagnóza
|
||||
|
||||
### DEKURS – záznamy z návštěv
|
||||
- 172 068 záznamů
|
||||
- **IDPAC** → vazba na KAR
|
||||
- **DATUM** (date), **CAS** (time) – kdy
|
||||
- **DEKURS** – text záznamu (BLOB, RTF nebo holý text)
|
||||
- **DGN1** – hlavní diagnóza (5 znaků, např. 'Z000 '), VDGN1-4 – vedlejší
|
||||
- **IDPRAC** – který pracovník
|
||||
- IDODD, IDUZI – oddělení/uživatel (u Buzalky = 2, 2, 6)
|
||||
- IDSKUPINA, IDTYP – volitelné (NULL OK)
|
||||
|
||||
## Základní schéma
|
||||
```
|
||||
KAR (kartotéka) ←IDPAC→ DEKURS (záznamy z návštěv)
|
||||
```
|
||||
|
||||
## Testovací pacient (uživatel)
|
||||
- **IDPAC = 9742** – Buzalka Vladimír, RC 7309208104, nar. 20.9.1973
|
||||
- IDODD=2, IDPRAC=2, IDUZI=6 (použít při INSERT do DEKURS)
|
||||
|
||||
## RTF formát v DEKURS
|
||||
|
||||
### Základní struktura
|
||||
```
|
||||
{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029
|
||||
{\info{\bookmarks "popis","Typ:ID",číslo}} ← metadata Medicusu
|
||||
{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f2...Courier New;}{\f5...Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red255\green255\blue0;\red0\green128\blue0;\red192\green192\blue192;}
|
||||
{\stylesheet{styly...}}
|
||||
\uc1\pard\s10\plain... ← tělo záznamu
|
||||
}
|
||||
```
|
||||
|
||||
### Barvy (\cfX)
|
||||
- `\cf1` = modrá (odkazy)
|
||||
- `\cf2` = žlutá (highlight)
|
||||
- `\cf3` = zelená
|
||||
- `\cf4` = šedá (pozadí buněk tabulky `\clcbpat4`)
|
||||
|
||||
### Velikost písma (\fsXX = XX/2 bodů)
|
||||
- `\fs18` = 9pt (tabulky)
|
||||
- `\fs20` = 10pt (normální)
|
||||
- `\fs24` = 12pt atd.
|
||||
|
||||
### Řezy a formátování
|
||||
- `\b` = tučné, `\i` = kurzíva, `\ul` = podtržené
|
||||
- `\ql` = vlevo, `\qr` = vpravo, `\qc` = střed
|
||||
|
||||
### Styly (stylesheet)
|
||||
- `cs15/cs16` = Normální
|
||||
- `cs20` = Vkládaný text
|
||||
- `cs21` = Záhlaví (italic)
|
||||
- `cs22/cs23` = Odkaz (underline, modrá)
|
||||
- `s8/s10` = Vlevo (paragraph styl)
|
||||
|
||||
### Záložky (bookmarks v {\info})
|
||||
- Klikatelné odkazy v Medicusu
|
||||
- Formát: `"popis","Typ:ID",číslo`
|
||||
- Typy: `Files:ID`, `MEDLAB:ID`, `Lab:ID`, `Ock:ID`, `VykA:ID`
|
||||
|
||||
### České znaky
|
||||
- RTF hex escape v win1250: `\'e1`=á, `\'9e`=ž, `\'fd`=ý atd.
|
||||
|
||||
### Tabulky
|
||||
- Plně funkční RTF tabulky (`\trowd`, `\cell`, `\cellx...`)
|
||||
- Používají se např. pro laboratorní výsledky (viz ID=243082, 50KB)
|
||||
|
||||
### Holý text (starší záznamy)
|
||||
- Některé starší záznamy jsou prostý text bez RTF obálky
|
||||
- Nutno detekovat: začíná na `{\\rtf` = RTF, jinak plain text
|
||||
|
||||
### Příklad minimálního RTF záznamu
|
||||
```
|
||||
{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029
|
||||
{\fonttbl{\f0\fnil\fcharset238 Arial;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
\pard\plain\f0\fs20 Text záznamu zde\par
|
||||
}
|
||||
```
|
||||
|
||||
## INSERT do DEKURS – ověřený postup
|
||||
```python
|
||||
import fdb, datetime
|
||||
|
||||
conn = fdb.connect(...)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute('SELECT MAX(ID) FROM DEKURS')
|
||||
next_id = cur.fetchone()[0] + 1
|
||||
|
||||
text = r'{\rtf1\ansi\ansicpg1250...\par' + '\n}' # raw string pro RTF!
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO DEKURS (ID, IDPAC, IDODD, IDPRAC, IDUZI, DATUM, CAS, DEKURS, DGN1)
|
||||
VALUES (?, ?, 2, 2, 6, ?, ?, ?, ?)
|
||||
""", (next_id, idpac, datum, cas, text, dgn1))
|
||||
|
||||
conn.commit()
|
||||
```
|
||||
- RTF string musí být **raw string** (r'...') kvůli zpětným lomítkům
|
||||
- DGN1 = 5 znaků s mezerou: `'Z000 '`
|
||||
- DATUM = datetime.date(...), CAS = datetime.time(...)
|
||||
|
||||
## Soubory v projektu
|
||||
- `explore_db.py` – výpis všech tabulek a jejich sloupců
|
||||
- `inspect_table.py` – detailní info o konkrétní tabulce (arg: název tabulky)
|
||||
- `sample_dekurs.py` – ukázky nejstarších a nejnovějších záznamů
|
||||
- `sample_dekurs2.py` – celý text jednoho záznamu
|
||||
- `sample_rtf.py` – nejdelší záznamy pacienta (dle délky RTF)
|
||||
- `analyze_rtf.py` – analýza RTF tagů v záznamu
|
||||
- `insert_test.py` – testovací INSERT do DEKURS
|
||||
- `CLAUDE_NOTES.md` – tento soubor
|
||||
|
||||
## Export FILES do externích DB (zjištěno 2026-03-17 přes FBScanner)
|
||||
|
||||
### Mechanismus přesunu
|
||||
Medicus vytváří **měsíční** externí Firebird databáze ve formátu `MEDICUS_FILES_YYYYMM.fdb`.
|
||||
|
||||
**Postup exportu:**
|
||||
1. SELECT dat z FILES po 10 záznamech: `select first 10 ID, BODY, DATUM from FILES where ID > X ORDER BY ID ASC`
|
||||
2. Binární data (skeny) se zapíší do externího `.fdb` souboru
|
||||
3. V hlavní DB se BODY přepíše referenčním číslem (ID v externím souboru): `update FILES set BODY = 379 where ID = 10009`
|
||||
4. Do EXTERNI_DB se zapíše lokace: `insert into EXTERNI_DB (DBNAME, SERVER, PATH, HESLO) values ('DB202501', 'localhost/3053', 'u:\MEDICUS_FILES_202501.fdb', '<encrypted>')`
|
||||
|
||||
### Výsledek
|
||||
- Hlavní DB: FILES.BODY obsahuje jen malé číslo (referenci) místo MB blobu
|
||||
- Skenované soubory jsou fyzicky v `u:\MEDICUS_FILES_YYYYMM.fdb`
|
||||
- Medicus načítá soubory přes EXTERNI_DB tabulku (SERVER + PATH + HESLO)
|
||||
- Hesla v EXTERNI_DB jsou šifrovaná (base64 AES)
|
||||
|
||||
### FBScanner
|
||||
- Nainstalován, funguje jako proxy: Medicus → [3050] FBScanner → [3053] Firebird
|
||||
- Community Edition 2.5 – stačí pro monitoring
|
||||
- Export proveden na záloze (ne produkční DB)
|
||||
|
||||
## Přímý zápis do externí DB – dokončeno (2026-03-17)
|
||||
|
||||
### Nový soubor: `funkce_ext.py`
|
||||
Náhrada za `funkce.zapis_file()`. Ukládá PDF přímo do měsíční externí FDB
|
||||
místo do hlavní medicus.fdb.
|
||||
|
||||
**Funkce:** `zapis_file_ext(vstupconnection, idpac, cesta, souborname, prvnizavorka, soubordate, souborfiledate, poznamka, ext_base_path=r'u:\\')`
|
||||
- Parametry shodné s `funkce.zapis_file()` → jen prohodit import
|
||||
- Vrací `fileid` stejně jako původní funkce
|
||||
|
||||
**Co dělá:**
|
||||
1. Z `soubordate` odvodí `YYYYMM` → `DBNAME = 'DB202603'`
|
||||
2. Načte PDF jako bytes
|
||||
3. Vyhledá EXTERNI_DB pro daný měsíc:
|
||||
- Nalezeno → připojí se k existující FDB (masterkey)
|
||||
- Nenalezeno → vytvoří novou `MEDICUS_FILES_YYYYMM.fdb`, zaregistruje do EXTERNI_DB
|
||||
4. Vygeneruje `UID = uuid.uuid4().hex` (32 znaků)
|
||||
5. Zapíše do DATA tabulky: `UID, DATA (blob), DATASIZE, CHUNK=0`
|
||||
6. Sestaví 48bajtovou BODY referenci
|
||||
7. Získá `fileid` z Gen_Files
|
||||
8. Vloží do FILES s BODY = reference (ne blob)
|
||||
|
||||
### Úprava `s03soubory.py`
|
||||
- Přidán import `funkce_ext`
|
||||
- Volání `funkce.zapis_file(...)` nahrazeno `funkce_ext.zapis_file_ext(...)`
|
||||
|
||||
### BODY reference – binární formát (48 B)
|
||||
```
|
||||
magic 4 B = b'\xee\xbb\xaa\x0b'
|
||||
uid 32 B = UUID4 hex (ASCII, 32 znaků)
|
||||
dblen 4 B = len(DBNAME) jako little-endian uint32
|
||||
dbname N B = DBNAME ASCII (typicky 'DB202603', 8 B)
|
||||
```
|
||||
Celkem typicky: 4 + 32 + 4 + 8 = **48 bajtů**
|
||||
|
||||
### Struktura DATA tabulky (externe DB)
|
||||
```
|
||||
UID VARCHAR(32) NOT NULL
|
||||
DATA BLOB SUB_TYPE 0
|
||||
DATASIZE INTEGER
|
||||
CHUNK INTEGER
|
||||
```
|
||||
Generator: `GEN_UID` (Medicus ho asi nepoužívá přímo, UID je UUID string)
|
||||
|
||||
### EXTERNI_DB tabulka (hlavní DB)
|
||||
```
|
||||
DBNAME např. 'DB202603'
|
||||
SERVER např. 'localhost/3053' (FBScanner proxy)
|
||||
PATH např. 'u:\MEDICUS_FILES_202603.fdb'
|
||||
HESLO šifrované heslo (Medicus AES) – zkopírujeme z existující položky
|
||||
```
|
||||
Při vytváření nové DB: HESLO zkopírujeme z libovolné existující EXTERNI_DB položky
|
||||
(všechny používají masterkey, jen jinak zašifrované – ale stejný klíč).
|
||||
|
||||
### Poznámka k HESLO šifrování
|
||||
Hesla v EXTERNI_DB jsou šifrovaná Medicusem. Při vytváření nové DB proto
|
||||
zkopírujeme HESLO z existující položky (stejné heslo = stejný šifrovaný text).
|
||||
Medicus pak dokáže novou DB otevřít standardně. Pokud by to nefungovalo,
|
||||
lze heslo zadat manuálně přes správce Medicusu.
|
||||
|
||||
## Stav 2026-03-18 – nastudováno po obnově session
|
||||
|
||||
### Co bylo dokončeno (včera večer)
|
||||
- `funkce_ext.zapis_file_ext()` je **otestována a funkční**
|
||||
- `s03soubory.py` volá `zapis_file_ext` místo původní `funkce.zapis_file`
|
||||
- Celý import pipeline funguje end-to-end
|
||||
|
||||
### Celý import pipeline (s03soubory.py)
|
||||
1. Scanuje složku `u:\NextcloudOrdinace\Dokumentace_ke_zpracování`
|
||||
2. Ověří formát názvu souboru: `RC YYYY-MM-DD Jmeno, Jmeno. [prvnizavorka] [druhazavorka].pdf`
|
||||
3. Ověří rodné číslo v KAR tabulce
|
||||
4. Seskupí soubory podle RC (jeden pacient = jeden dekurs)
|
||||
5. Pro každý PDF zavolá `funkce_ext.zapis_file_ext()` → zapíše do externí DB
|
||||
6. Přesune soubor do `u:\NextcloudOrdinace\Dokumentace_zpracovaná`
|
||||
7. Sestaví RTF se záložkami (klikatelné odkazy na soubory v Medicusu)
|
||||
8. Zapíše dekurs do DEKURS tabulky
|
||||
|
||||
### Klíčové soubory
|
||||
- `funkce_ext.py` – zápis PDF do měsíční externí FDB (náhrada za funkce.zapis_file)
|
||||
- `funkce.py` – původní funkce (zapis_file, zapis_dekurs, get_files_id, get_idpac, convert_to1250)
|
||||
- `s03soubory.py` – hlavní import script (spouštět na Windows)
|
||||
- `s04_presun_externi_db.py` – utility: přesun ext DB souborů do u:\externi\ a update PATH v EXTERNI_DB
|
||||
- `test_ext_db4.py` – ověření BODY bajty z FILES tabulky (hex dump)
|
||||
|
||||
### Formát názvu souboru pro import
|
||||
```
|
||||
RC YYYY-MM-DD Prijmeni, Jmeno. [prvnizavorka] [druhazavorka].pdf
|
||||
např: 7309208104 2026-03-17 Buzalka, Vladimír. [EKG] [normální nález].pdf
|
||||
```
|
||||
|
||||
## Oprava RTF klikacích odkazů (2026-03-18)
|
||||
|
||||
### Problém
|
||||
`s03soubory.py` generoval RTF s `\cs22` pro styl odkazu, ale Medicus vyžaduje `\cs32`.
|
||||
Druhý+ bookmark neměl link styling vůbec. `poradi` se nikdy neinkrementoval.
|
||||
|
||||
### Zjištěno analýzou Medicusem vytvořeného DEKURS (ID=263480, AhojClaude.pdf)
|
||||
Správný RTF formát klikacího odkazu:
|
||||
```
|
||||
{\*\cs32\f0\ul\fs20\cf1 Odkaz;} ← stylesheet: cs32, NE cs22
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 Přílohy: {\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend 0}\par
|
||||
```
|
||||
- "Přílohy:" je inline s prvním odkazem na stejném `\pard` řádku
|
||||
- Každý další odkaz: `\pard\s10{\*\bkmkstart N}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend N}\par`
|
||||
|
||||
### Opraveno v s03soubory.py + test_import_3files.py
|
||||
1. `\cs22` → `\cs32` v stylesheet i těle RTF
|
||||
2. Header `Vložené přílohy:` je na **samostatném** `\pard` řádku (RTF: `Vlo\'9een\'e9 p\'f8\'edlohy:`)
|
||||
3. Všechny bookmarky mají stejný formát: `\pard\s10{\*\bkmkstart N}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend N}\par`
|
||||
4. Přidáno `poradi += 1` (bug: poradi se nikdy neinkrementoval)
|
||||
5. Odstraněna proměnná `prvnibookmark` – již nepotřebná
|
||||
|
||||
### RTF kódování českých znaků (win1250)
|
||||
- ž = `\'9e`, é = `\'e9`, ř = `\'f8`, í = `\'ed`, á = `\'e1`
|
||||
- "Vložené přílohy:" → `Vlo\'9een\'e9 p\'f8\'edlohy:`
|
||||
|
||||
## Merge do existujícího dekurzu (2026-03-18)
|
||||
|
||||
### Rozhodovací logika (3 případy)
|
||||
1. Poslední dekurs pacienta je z dnešního dne A má `Vložené přílohy:`
|
||||
→ **přidat soubor DO existující sekce** (`pridat_do_sekce_prilohy`)
|
||||
2. Poslední dekurs je z dnešního dne, ale sekci nemá
|
||||
→ **prepend nové sekce na začátek** (`merge_rtf_prepend`)
|
||||
3. Poslední dekurs je z jiného dne / neexistuje
|
||||
→ **nový dekurs pro dnešek**
|
||||
- Funkce: `najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni)`
|
||||
|
||||
### pridat_do_sekce_prilohy (přidání do existující sekce)
|
||||
1. Spočítat `"Files:"` v `{\info{\bookmarks}}` = N → nový bkmkstart = N
|
||||
2. Posunout všechny bkmkstart/bkmkend ≥ N o +1 (uvolnit index N)
|
||||
3. Vložit nový `\pard` před `\pard\s10\plain\cs15\f0\fs20 \par` (konec sekce)
|
||||
4. Vložit bookmark na pozici N do `{\info{\bookmarks}}`
|
||||
|
||||
### merge_rtf_prepend (prepend nové sekce)
|
||||
1. Posunout existující `\bkmkstart N` / `\bkmkend N` o počet nových souborů
|
||||
2. Přidat naše bookmarky NA ZAČÁTEK `{\info{\bookmarks ...}}`
|
||||
3. Vložit naše `\pard` tělo PŘED první `\uc1\pard` existujícího těla
|
||||
|
||||
### Detekce sekce
|
||||
- Přítomnost: `PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"` – hledáme v RTF
|
||||
- Konec sekce: `PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'`
|
||||
|
||||
### Skripty
|
||||
- `test_import_merge.py` – 3 soubory → merge do dnešního dekurzu (bez sekce přílohy)
|
||||
- `test_import_single.py` – 1 soubor → merge DO existující sekce přílohy
|
||||
- `test_import_3files.py` – 3 soubory → vždy nový dekurs (bez merge logiky, starší)
|
||||
|
||||
### Stav testování (2026-03-18)
|
||||
- `test_import_3files.py` ✅ ověřeno – klikací odkazy fungují, "Vložené přílohy:" správně
|
||||
- `test_import_merge.py` ✅ připraveno – merge 3 souborů do dnešního dekurzu (bez sekce)
|
||||
- `test_import_single.py` ✅ připraveno – přidání 1 souboru DO existující sekce přílohy
|
||||
|
||||
### Stav s03soubory.py (2026-03-20) – KOMPLETNÍ ✅
|
||||
- Merge logika plně integrována z `test_import_single.py`
|
||||
- Přidány konstanty `PRILOHY_HEADER`, `PRILOHY_CLOSING`
|
||||
- Přidány funkce: `najdi_posledni_dekurs_dnes()`, `ma_sekci_prilohy()`, `pridat_do_sekce_prilohy()`, `merge_rtf_prepend()`
|
||||
- RTF šablona přesunuta do konstanty `RTF_TEMPLATE`
|
||||
- Odstraněn dead code: `prvnibookmark`, staré šablony, nepoužívané `convert_to1250`
|
||||
- `pridat_do_sekce_prilohy()` podporuje **více souborů najednou** (oproti test_import_single.py, kde byl jen 1)
|
||||
- `idpac` se bere z prvního záznamu skupiny (čistěji než z `row` po skončení loopu)
|
||||
|
||||
### Rozhodovací logika s03soubory.py – 3 případy
|
||||
1. Dnešní dekurs **má** sekci `Vložené přílohy` → soubory přidány **dovnitř** sekce (`pridat_do_sekce_prilohy`)
|
||||
2. Dnešní dekurs **nemá** sekci příloh → nová sekce vložena **na začátek** (`merge_rtf_prepend`)
|
||||
3. Žádný dnešní dekurs → **nový** záznam (INSERT)
|
||||
|
||||
### Stav testovacích skriptů
|
||||
- `test_import_3files.py` ✅ ověřeno – klikací odkazy fungují
|
||||
- `test_import_merge.py` ✅ otestováno – merge 3 souborů (jen 2 případy, bez detekce sekce)
|
||||
- `test_import_single.py` ✅ otestováno – všechny 3 případy, základ pro s03soubory.py
|
||||
|
||||
## Oprava BlobReader v dekurz_report.py (2026-04-01)
|
||||
|
||||
### Chyba
|
||||
```
|
||||
Exception ignored while calling deallocator <function BlobReader.__del__>:
|
||||
fdb.fbcore.DatabaseError: BlobReader.close/isc_close_blob: invalid BLOB handle (-901)
|
||||
```
|
||||
Skript fungoval správně (exit code 0, Excel uložen), ale garbage collector házel warningy.
|
||||
|
||||
### Příčina
|
||||
`fdb` vrací BLOB sloupce jako `BlobReader` objekty. Po `conn.close()` se při GC pokoušely
|
||||
zavřít handle, který již neexistoval.
|
||||
|
||||
### Oprava
|
||||
Explicitně zavřít `BlobReader` hned po `.read()`, ještě za živa spojení:
|
||||
|
||||
```python
|
||||
# PŘED (špatně):
|
||||
rtf = dekurs_blob.read() if hasattr(dekurs_blob, 'read') else (dekurs_blob or '')
|
||||
|
||||
# PO (správně):
|
||||
if hasattr(dekurs_blob, 'read'):
|
||||
rtf = dekurs_blob.read()
|
||||
dekurs_blob.close()
|
||||
else:
|
||||
rtf = dekurs_blob or ''
|
||||
```
|
||||
|
||||
### Obecné pravidlo
|
||||
Kdykoli čteme BLOB z fdb, vždy `.close()` po `.read()` – jinak GC po `conn.close()` hlásí chybu.
|
||||
|
||||
---
|
||||
|
||||
## Další postup (nápady)
|
||||
- Otestovat `s03soubory.py` na Windows se skutečnými soubory (všechny 3 případy)
|
||||
- Napsat `rtf_to_text()` pro extrakci čistého textu z dekurzů
|
||||
- Prozkoumat tabulky: LECH/LECD (léky?), POU (poukazy?), AMBULEKY (výkony?)
|
||||
- První report – domluvit s uživatelem co chce vidět
|
||||
|
||||
---
|
||||
|
||||
## KAR.POZNAMKA – automatické tagy (2026-04-09)
|
||||
|
||||
Pole `POZNAMKA` v tabulce KAR je **BLOB SUB_TYPE 1** (text blob, win1250) –
|
||||
Medicus ho renderuje jako RTF stejně jako DEKURS. Lze psát i prostý text.
|
||||
|
||||
### Konvence automatických tagů
|
||||
Automaticky zapisované informace se ukládají na **začátek** POZNAMKA ve formátu:
|
||||
```
|
||||
[[klic:hodnota1, hodnota2]] #zapsáno DD-MM-YYYY HH:MM#
|
||||
```
|
||||
- Oddělovače `[[` a `]]` se v lékařských poznámkách přirozeně nevyskytují
|
||||
- Tag lze programově najít a nahradit regexem
|
||||
- Ruční text lékaře/sestry pod tagem zůstává nedotčen
|
||||
|
||||
### Implementovaný tag: prev_prohlidka
|
||||
```
|
||||
[[prev_prohlidka:15-05-2024 01022, 15-04-2026]] #zapsáno 09-04-2026 14:30#
|
||||
```
|
||||
- `15-05-2024` = datum poslední PP
|
||||
- `01022` = kód výkonu (01022 = dospělí, 01021 = starší varianta)
|
||||
- `15-04-2026` = nejdřívější možný termín příští PP (23 měsíců od poslední)
|
||||
- Skript: `MedicusWithClaudeTest/prev_prohlidka.py`
|
||||
|
||||
---
|
||||
|
||||
## Tabulka VZPARC – výkonové dávky pojišťovnám (zjištěno 2026-04-09)
|
||||
|
||||
Zachyceno přes Firebird trace log při exportu dávek z Medicusu.
|
||||
|
||||
### Struktura
|
||||
- **1954 záznamů**, data od 2007 (s DAVKA blobem od 2013)
|
||||
- Klíčové sloupce: `ID`, `ROK`, `DRUH`, `DATUMOD`, `DATUMDO`, `DAVKA` (blob), `ICP`, `ODB`, `IDICZ`
|
||||
- Vazba na ICZ přes `IDICZ`
|
||||
|
||||
### Typy dávek (DRUH)
|
||||
| DRUH | Popis |
|
||||
|---|---|
|
||||
| `98` | Výkonová dávka – obsahuje výkony vykázané pojišťovně (DP98 formát) |
|
||||
| `05` | Doplňková dávka (DP05) |
|
||||
| `80` | Registrační dávka – přihlášení/odhlášení pacientů (DP80) |
|
||||
| `36` | Jiný typ |
|
||||
|
||||
### DAVKA blob – formát (CP852)
|
||||
Stejný formát jako KDAVKA v PORTAL, dekódování identické:
|
||||
```python
|
||||
text = raw.encode('cp1250', errors='replace').decode('cp852', errors='replace')
|
||||
```
|
||||
|
||||
Struktura DP98 dávky:
|
||||
```
|
||||
DP98... – hlavička (IČP, rok, měsíc, disk, počet případů, částka)
|
||||
A ... – ambulantní případ: RC pacienta na pozici 34, délka 10
|
||||
V ... – výkon: V + DDMM + YYYY + KOD(5) + ...
|
||||
Z ... – ZULP
|
||||
L ... – lék
|
||||
```
|
||||
|
||||
### Parsování RC z A řádku
|
||||
```python
|
||||
aktualni_rc = line[34:44].strip() # pevná pozice, délka 10
|
||||
```
|
||||
|
||||
### Parsování výkonu z V řádku
|
||||
```python
|
||||
dd = int(line[1:3])
|
||||
mm = int(line[3:5])
|
||||
yyyy = int(line[5:9])
|
||||
kod = line[9:14].strip() # 5znakový kód výkonu
|
||||
```
|
||||
|
||||
### Poznámka: PORTAL vs VZPARC
|
||||
- **PORTAL** – starší tabulka, KDAVKA blob, data jen do 2015
|
||||
- **VZPARC** – správná tabulka pro výkonové dávky, data od 2013 do dnes
|
||||
- Pro hledání výkonů per pacient vždy používat **VZPARC**
|
||||
|
||||
---
|
||||
|
||||
## Skript prev_prohlidka.py (MedicusWithClaudeTest/)
|
||||
|
||||
Účel: najde datum poslední preventivní prohlídky (PP) pro každého registrovaného
|
||||
pacienta a zapíše/aktualizuje tag v KAR.POZNAMKA.
|
||||
|
||||
### Co dělá
|
||||
1. Načte registrované pacienty (přesný dotaz přes REGISTR + ICP, IČP=09305001)
|
||||
2. Projde všechny VZPARC DRUH=98 dávky, hledá výkony `01022` nebo `01021`
|
||||
3. Pro každého pacienta zachová jen **nejnovější** datum PP
|
||||
4. Zapíše/přepíše tag `[[prev_prohlidka:...]]` na začátek KAR.POZNAMKA
|
||||
|
||||
### Výsledek posledního spuštění (2026-04-09)
|
||||
- Registrovaných pacientů: 1621
|
||||
- Dávek VZPARC DRUH=98: 778
|
||||
- Pacientů s nalezenou PP: 1289
|
||||
- Zapsáno do KAR: 1120
|
||||
- Nenalezeno (odregistrovaní nebo bez PP): 169
|
||||
|
||||
### Závislosti
|
||||
```
|
||||
pip install python-dateutil
|
||||
```
|
||||
(fdb již musí být nainstalováno)
|
||||
@@ -0,0 +1,37 @@
|
||||
@echo off
|
||||
:: Firebird SQL trace pro Medicus
|
||||
:: Zachytí všechny SQL příkazy při exportu souborů do externích DB
|
||||
::
|
||||
:: INSTRUKCE:
|
||||
:: 1. Spusť tento soubor jako Administrator (pravé tlačítko → Spustit jako správce)
|
||||
:: 2. Nechej okno otevřené
|
||||
:: 3. V Medicusu proveď export souborů do externích DB
|
||||
:: 4. Vrať se sem a stiskni Ctrl+C pro zastavení
|
||||
:: 5. Log najdeš v medicus_trace_output.txt
|
||||
|
||||
echo.
|
||||
echo === MEDICUS SQL TRACE ===
|
||||
echo Hledam Firebird fbtracemgr...
|
||||
echo.
|
||||
|
||||
set FB_BIN=C:\Program Files\Firebird\Firebird_2_5_CGM\bin
|
||||
|
||||
if not exist "%FB_BIN%\fbtracemgr.exe" (
|
||||
echo CHYBA: fbtracemgr.exe nenalezen v %FB_BIN%
|
||||
echo Zkontroluj cestu a uprav tento soubor.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Nalezen: %FB_BIN%\fbtracemgr.exe
|
||||
echo.
|
||||
echo POZOR: Nyni PROVEĎ export v Medicusu, pak se vrat a stiskni Ctrl+C
|
||||
echo Výstup se zapisuje do: medicus_trace_output.txt
|
||||
echo.
|
||||
|
||||
powershell -Command "& '%FB_BIN%\fbtracemgr.exe' -se localhost:service_mgr -user SYSDBA -password masterkey -start -config '%~dp0medicus_trace.conf' | Tee-Object -FilePath '%~dp0medicus_trace_output.txt'"
|
||||
|
||||
echo.
|
||||
echo === TRACE ZASTAVEN ===
|
||||
echo Výsledky jsou v medicus_trace_output.txt
|
||||
pause
|
||||
@@ -0,0 +1,34 @@
|
||||
import fdb, re
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
cur.execute('SELECT DEKURS FROM DEKURS WHERE ID=243082')
|
||||
text = cur.fetchone()[0]
|
||||
text = text.read() if hasattr(text, 'read') else text
|
||||
text = text.decode('windows-1250', errors='replace') if isinstance(text, bytes) else text
|
||||
|
||||
# colortbl
|
||||
ct = re.search(r'\\colortbl[^}]+\}', text)
|
||||
if ct: print('COLORTBL:', ct.group(0))
|
||||
|
||||
# stylesheet
|
||||
ss = re.search(r'\\stylesheet\{.+?\}(?=\n)', text, re.DOTALL)
|
||||
if ss: print('\nSTYLESHEET:', ss.group(0)[:600])
|
||||
|
||||
# Najdi různé styly použité v textu
|
||||
print('\n--- Použité RTF tagy (unikátní) ---')
|
||||
tags = re.findall(r'\\[a-z]+\d*', text)
|
||||
from collections import Counter
|
||||
for tag, count in Counter(tags).most_common(40):
|
||||
print(f" {tag:<20} {count}x")
|
||||
|
||||
# Ukázka tabulky
|
||||
tbl_start = text.find(r'\trowd')
|
||||
if tbl_start > 0:
|
||||
print('\n--- Začátek tabulky ---')
|
||||
print(text[tbl_start:tbl_start+300])
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,42 @@
|
||||
"""check_last_dekurs.py – zobrazí posledních 5 záznamů v DEKURS + FILES pro pacienta 9742"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
print("=== Posledních 5 záznamů DEKURS pro Buzalka (idpac=9742) ===")
|
||||
cur.execute("""
|
||||
SELECT id, datum, cas, OCTET_LENGTH(dekurs) as delka
|
||||
FROM dekurs WHERE idpac = 9742
|
||||
ORDER BY id DESC ROWS 5
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
print(f" ID={row[0]} datum={row[1]} cas={row[2]} délka={row[3]} B")
|
||||
|
||||
print()
|
||||
print("=== Obsah posledního DEKURS záznamu ===")
|
||||
cur.execute("""
|
||||
SELECT dekurs FROM dekurs WHERE idpac = 9742
|
||||
ORDER BY id DESC ROWS 1
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
text = row[0]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('cp1250', errors='replace')
|
||||
print(text)
|
||||
|
||||
print()
|
||||
print("=== Posledních 5 záznamů FILES pro Buzalka (idpac=9742) ===")
|
||||
cur.execute("""
|
||||
SELECT id, filename, datum, OCTET_LENGTH(body) as bodylen
|
||||
FROM files WHERE idpac = 9742
|
||||
ORDER BY id DESC ROWS 5
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
print(f" ID={row[0]} filename={row[1]} datum={row[2]} body={row[3]} B")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Pomocný skript pro Clauda – spusť na Windows: python db_query.py
|
||||
Výsledky zapíše do db_query_result.txt
|
||||
"""
|
||||
import fdb
|
||||
import json
|
||||
import sys
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
results = {}
|
||||
|
||||
# 1. Tabulky s klíčovými slovy (soubory, dokumenty)
|
||||
cur.execute("""
|
||||
SELECT RDB$RELATION_NAME
|
||||
FROM RDB$RELATIONS
|
||||
WHERE RDB$SYSTEM_FLAG = 0 AND RDB$VIEW_BLR IS NULL
|
||||
ORDER BY RDB$RELATION_NAME
|
||||
""")
|
||||
all_tables = [row[0].strip() for row in cur.fetchall()]
|
||||
|
||||
keywords = ['FILE', 'SOUB', 'DOC', 'SCAN', 'OBRAZ', 'IMG', 'ATTACH',
|
||||
'EXTERN', 'PRILOHA', 'ZPRAV', 'ARCH', 'PRIL', 'FOTO']
|
||||
matches = [t for t in all_tables if any(k in t.upper() for k in keywords)]
|
||||
results['tables_matching'] = matches
|
||||
results['total_tables'] = len(all_tables)
|
||||
|
||||
# 2. Podrobnosti o nalezených tabulkách (sloupce + počet řádků)
|
||||
table_details = {}
|
||||
for t in matches:
|
||||
try:
|
||||
cur.execute(f"SELECT COUNT(*) FROM {t}")
|
||||
count = cur.fetchone()[0]
|
||||
except:
|
||||
count = "error"
|
||||
|
||||
cur.execute("""
|
||||
SELECT r.RDB$FIELD_NAME, f.RDB$FIELD_TYPE, f.RDB$FIELD_LENGTH
|
||||
FROM RDB$RELATION_FIELDS r
|
||||
JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = r.RDB$FIELD_SOURCE
|
||||
WHERE r.RDB$RELATION_NAME = ?
|
||||
ORDER BY r.RDB$FIELD_POSITION
|
||||
""", (t,))
|
||||
cols = [(row[0].strip(), row[1], row[2]) for row in cur.fetchall()]
|
||||
table_details[t] = {"count": count, "columns": cols}
|
||||
|
||||
results['table_details'] = table_details
|
||||
|
||||
# 3. Uložit výsledky
|
||||
output = []
|
||||
output.append(f"=== VÝSLEDKY DB PRŮZKUMU ===\n")
|
||||
output.append(f"Celkem tabulek v DB: {results['total_tables']}\n")
|
||||
output.append(f"\nTabulky obsahující klíčová slova (soubory/dokumenty):\n")
|
||||
for t in results['tables_matching']:
|
||||
d = results['table_details'].get(t, {})
|
||||
output.append(f"\n ** {t} ** (řádků: {d.get('count', '?')})")
|
||||
for col in d.get('columns', []):
|
||||
output.append(f" - {col[0]} (type={col[1]}, len={col[2]})")
|
||||
|
||||
with open('db_query_result.txt', 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(output))
|
||||
|
||||
print("Hotovo! Výsledky jsou v db_query_result.txt")
|
||||
conn.close()
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Podrobný průzkum FILES a EXTERNI_DB – spusť na Windows: python db_query2.py
|
||||
"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
out = []
|
||||
|
||||
# 1. Obsah EXTERNI_DB
|
||||
out.append("=== EXTERNI_DB (konfigurace externích databází) ===")
|
||||
cur.execute("SELECT DBNAME, PATH, SERVER, HESLO FROM EXTERNI_DB")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" DBNAME={row[0]} PATH={row[1]} SERVER={row[2]} HESLO={row[3]}")
|
||||
|
||||
# 2. FILES – přehled po rocích
|
||||
out.append("\n=== FILES – počty záznamů po rocích ===")
|
||||
cur.execute("""
|
||||
SELECT EXTRACT(YEAR FROM DATUM) as ROK, COUNT(*) as POCET,
|
||||
SUM(OCTET_LENGTH(BODY)) as CELKEM_BYTU
|
||||
FROM FILES
|
||||
GROUP BY EXTRACT(YEAR FROM DATUM)
|
||||
ORDER BY ROK
|
||||
""")
|
||||
total_bytes = 0
|
||||
for row in cur.fetchall():
|
||||
rok, pocet, bytu = row
|
||||
mb = (bytu or 0) / 1024 / 1024
|
||||
total_bytes += (bytu or 0)
|
||||
out.append(f" Rok {int(rok) if rok else '?'}: {pocet} souborů, {mb:.1f} MB")
|
||||
out.append(f" CELKEM: {total_bytes/1024/1024:.1f} MB v BLOB datech FILES")
|
||||
|
||||
# 3. FILES – ukázka typů souborů
|
||||
out.append("\n=== FILES – typy souborů (dle přípony FILENAME) ===")
|
||||
cur.execute("""
|
||||
SELECT LOWER(SUBSTRING(FILENAME FROM CHAR_LENGTH(FILENAME) - 3)) as EXT,
|
||||
COUNT(*) as POCET
|
||||
FROM FILES
|
||||
WHERE FILENAME IS NOT NULL
|
||||
GROUP BY 1
|
||||
ORDER BY 2 DESC
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" {row[0]} : {row[1]} souborů")
|
||||
|
||||
# 4. FILES – ukázka prvních 5 záznamů (bez BODY)
|
||||
out.append("\n=== FILES – ukázka 5 záznamů (bez binárních dat) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, IDPAC, FILENAME, DATUM, OCTET_LENGTH(BODY) as VELIKOST,
|
||||
POZNAMKA, IDDOCTYP
|
||||
FROM FILES
|
||||
ORDER BY DATUM DESC
|
||||
ROWS 5
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" ID={row[0]} IDPAC={row[1]} soubor={row[2]} datum={row[3]}"
|
||||
f" velikost={row[4]}B pozn={row[5]} typ={row[6]}")
|
||||
|
||||
# 5. DOCTYP – typy dokumentů
|
||||
out.append("\n=== DOCTYP – číselník typů dokumentů ===")
|
||||
cur.execute("SELECT ID, NAZEV, SYSID FROM DOCTYP ORDER BY ID")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" ID={row[0]} nazev={row[1]} sysid={row[2]}")
|
||||
|
||||
result = '\n'.join(out)
|
||||
print(result)
|
||||
with open('db_query2_result.txt', 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
print("\nUloženo do db_query2_result.txt")
|
||||
conn.close()
|
||||
@@ -0,0 +1,532 @@
|
||||
=== VÝSLEDKY DB PRŮZKUMU ===
|
||||
|
||||
Celkem tabulek v DB: 993
|
||||
|
||||
|
||||
Tabulky obsahující klíčová slova (soubory/dokumenty):
|
||||
|
||||
|
||||
** CHAT_ZPRAVY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDADRESAR (type=8, len=4)
|
||||
- ODCHOZI (type=14, len=1)
|
||||
- ODESLANO (type=35, len=8)
|
||||
- TEXT (type=261, len=8)
|
||||
- OTHER_ID (type=8, len=4)
|
||||
- PRECTENO (type=35, len=8)
|
||||
- SENT (type=35, len=8)
|
||||
|
||||
** CLICKDOC ** (řádků: 2)
|
||||
- KEY (type=37, len=100)
|
||||
- VALUE (type=37, len=100)
|
||||
|
||||
** CLICKDOC_CINNOSTI ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDCINNOSTI (type=8, len=4)
|
||||
|
||||
** CLICKDOC_KAR ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
|
||||
** CLICKDOC_OBJ ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDOBJ (type=8, len=4)
|
||||
- REMOTECREATE (type=14, len=1)
|
||||
- REMOTECHANGE (type=14, len=1)
|
||||
- APPROVAL (type=7, len=2)
|
||||
- SEND_REMINDER_SMS (type=14, len=1)
|
||||
|
||||
** CLICKDOC_UZIVATEL ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
|
||||
** DEKIMG ** (řádků: 8)
|
||||
- ID (type=8, len=4)
|
||||
- HASH (type=37, len=32)
|
||||
- REFCOUNT (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
|
||||
** DEKIMGREF ** (řádků: 7)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- IDDEKURS (type=8, len=4)
|
||||
- IDDEKIMG (type=8, len=4)
|
||||
|
||||
** DOCLIST ** (řádků: 461429)
|
||||
- ID (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- TABULKA (type=37, len=20)
|
||||
- IDREC (type=8, len=4)
|
||||
- TYP (type=7, len=2)
|
||||
- POPIS (type=37, len=40)
|
||||
- IDPAC (type=8, len=4)
|
||||
- IDODDEL (type=8, len=4)
|
||||
|
||||
** DOCTYP ** (řádků: 27)
|
||||
- ID (type=8, len=4)
|
||||
- NAZEV (type=37, len=32)
|
||||
- SYSID (type=8, len=4)
|
||||
|
||||
** DOMPECEARCHIV ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- CREATED (type=35, len=8)
|
||||
- DATA (type=261, len=8)
|
||||
|
||||
** ED_BOOKOFSUBMISSIONATTACH ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- BOOK_OF_SUBMISSION_ID (type=8, len=4)
|
||||
- ORDER (type=8, len=4)
|
||||
- HAS_ALREADY_BEEN_SENT (type=14, len=1)
|
||||
- NAME (type=37, len=255)
|
||||
- CONTENT (type=261, len=8)
|
||||
|
||||
** EET_ZPRAVARECORD ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- UUID_ZPRAVY (type=14, len=16)
|
||||
- ID_TRZBA (type=8, len=4)
|
||||
- GUID_TRZBA (type=14, len=16)
|
||||
- DAT_ODESL (type=35, len=8)
|
||||
- CERTTHUMBPRINT (type=37, len=64)
|
||||
- REQUEST (type=261, len=8)
|
||||
- RESPONSE (type=261, len=8)
|
||||
- RESP_TYPE (type=8, len=4)
|
||||
- RESP_UUID_ZPRAVY (type=37, len=50)
|
||||
- RESP_DAT_PRIJ (type=35, len=8)
|
||||
- RESP_DAT_ODMIT (type=35, len=8)
|
||||
- RESP_BKP (type=37, len=44)
|
||||
- RESP_FIK (type=37, len=39)
|
||||
- RESP_KOD (type=8, len=4)
|
||||
- RESP_CHYBA (type=37, len=100)
|
||||
- PRVNI_ZASLANI (type=7, len=2)
|
||||
- RESP_TEST (type=7, len=2)
|
||||
- RESP_EXCEPTIONMESSAGE (type=261, len=8)
|
||||
|
||||
** ES_KONZULTACE_FILE ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- ID_KONZULTACE (type=8, len=4)
|
||||
- CAS (type=35, len=8)
|
||||
- NAME (type=37, len=256)
|
||||
- CONTENT (type=261, len=8)
|
||||
- MIME (type=37, len=100)
|
||||
- PATIENT_DOC (type=14, len=1)
|
||||
- PORADI (type=8, len=4)
|
||||
|
||||
** EXTERNI_DB ** (řádků: 1)
|
||||
- DBNAME (type=37, len=12)
|
||||
- PATH (type=37, len=120)
|
||||
- SERVER (type=37, len=32)
|
||||
- HESLO (type=37, len=64)
|
||||
|
||||
** FILES ** (řádků: 10305)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DOCID (type=8, len=4)
|
||||
- TYP (type=8, len=4)
|
||||
- FILENAME (type=37, len=254)
|
||||
- BODY (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- IDDOCTYP (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
- POZNAMKA (type=37, len=100)
|
||||
- DATSOUBORU (type=35, len=8)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
- EXT_ID (type=37, len=36)
|
||||
|
||||
** FNUSA_ATTACHMENT ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- ATTACH_INT_TYP (type=37, len=1)
|
||||
- ATTACH_INT_ID (type=8, len=4)
|
||||
- LAST_UPDATED (type=35, len=8)
|
||||
|
||||
** GRARCH ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- NAME (type=37, len=16)
|
||||
- PATH (type=37, len=150)
|
||||
|
||||
** GRDOC ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDNEM (type=37, len=60)
|
||||
- IDPAC (type=8, len=4)
|
||||
- SKUPINA (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- PREVIEW (type=261, len=8)
|
||||
- PICT (type=261, len=8)
|
||||
- LOCATION (type=37, len=16)
|
||||
|
||||
** HISTDOC ** (řádků: 32250)
|
||||
- ID (type=8, len=4)
|
||||
- TYP (type=37, len=7)
|
||||
- DATUM (type=12, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- CENA (type=16, len=8)
|
||||
- REPPOZN (type=37, len=60)
|
||||
- REPDATUM (type=12, len=4)
|
||||
- IDPACI (type=8, len=4)
|
||||
- IDZAR (type=8, len=4)
|
||||
- IDODD (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- IDUZIV (type=8, len=4)
|
||||
- IDLEK (type=8, len=4)
|
||||
- IDZARPR (type=8, len=4)
|
||||
- IDODDPR (type=8, len=4)
|
||||
- IDPRACPR (type=8, len=4)
|
||||
- IDLEKPR (type=8, len=4)
|
||||
- STAV (type=14, len=1)
|
||||
- REPSTAV (type=14, len=1)
|
||||
- IDSABLONPRAC (type=37, len=55)
|
||||
- REPTEXT (type=261, len=8)
|
||||
- TYPCENY (type=14, len=1)
|
||||
- CENAPACIENT (type=16, len=8)
|
||||
- IDDOKLAD (type=8, len=4)
|
||||
- POZNAMKA (type=37, len=100)
|
||||
- PRINTED (type=14, len=1)
|
||||
- PRIJALJINE (type=37, len=150)
|
||||
- BMKDATA (type=261, len=8)
|
||||
- PORCISLO (type=8, len=4)
|
||||
- DRUHPOJ (type=8, len=4)
|
||||
- IDLEKZPR (type=8, len=4)
|
||||
- IDHLAV (type=8, len=4)
|
||||
- CREATED (type=35, len=8)
|
||||
- IDSOUHLASPACSABL (type=8, len=4)
|
||||
- REPIDUZIVINS (type=8, len=4)
|
||||
- REPIDUZIVUPD (type=8, len=4)
|
||||
- CGMNUMBER_ODESILATEL (type=37, len=12)
|
||||
- CGMNUMBER_PRIJEMCE (type=37, len=12)
|
||||
- CLICKBOX_ATT_ID (type=8, len=4)
|
||||
- CLICKBOX_MAILBOX (type=37, len=255)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
|
||||
** HISTDOCLAB ** (řádků: 27185)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDMETOD (type=8, len=4)
|
||||
- POZN (type=261, len=8)
|
||||
- IDPLADET (type=8, len=4)
|
||||
|
||||
** HISTDOCLABPARAMS ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDHISTDOCLAB (type=8, len=4)
|
||||
- IDPARAM (type=8, len=4)
|
||||
- TYP (type=14, len=1)
|
||||
- TEXT (type=14, len=8)
|
||||
|
||||
** HISTDOCSABL ** (řádků: 7)
|
||||
- ID (type=8, len=4)
|
||||
- TYP (type=37, len=7)
|
||||
- DATA (type=261, len=8)
|
||||
- IDZAR (type=8, len=4)
|
||||
- IDODD (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- IDUZIV (type=8, len=4)
|
||||
- IDLEK (type=8, len=4)
|
||||
- IDZARPR (type=8, len=4)
|
||||
- IDODDPR (type=8, len=4)
|
||||
- IDPRACPR (type=8, len=4)
|
||||
- IDLEKPR (type=8, len=4)
|
||||
- STAV (type=14, len=1)
|
||||
- IDSABLONPRAC (type=37, len=55)
|
||||
- NAZEV (type=37, len=48)
|
||||
- REPSTAV (type=14, len=1)
|
||||
- CENA (type=37, len=20)
|
||||
- DOPLATEK (type=37, len=20)
|
||||
- USEPRAC (type=8, len=4)
|
||||
- USEUZIV (type=8, len=4)
|
||||
|
||||
** HISTDOCSABLLAB ** (řádků: 19)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDMETOD (type=8, len=4)
|
||||
|
||||
** HISTDOCVYK ** (řádků: 1868)
|
||||
- IDHDVYK (type=8, len=4)
|
||||
- ID (type=8, len=4)
|
||||
- KOD (type=14, len=7)
|
||||
- POCET (type=16, len=8)
|
||||
- CENA (type=16, len=8)
|
||||
- CENADOPL (type=16, len=8)
|
||||
- POJ (type=14, len=3)
|
||||
- ICZ (type=14, len=8)
|
||||
- ICP (type=14, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- REV (type=14, len=1)
|
||||
- TYP (type=7, len=2)
|
||||
- NOT_CENA_PRUMER (type=14, len=1)
|
||||
- VARSYM (type=37, len=20)
|
||||
- SKUPINA (type=14, len=2)
|
||||
|
||||
** HISTDOC_DEF ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- KOD (type=37, len=5)
|
||||
- NAZEV (type=37, len=40)
|
||||
- IDUZI (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- SKUPINA (type=8, len=4)
|
||||
|
||||
** HISTDOC_EPOUKAZ ** (řádků: 849)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- ID_DOKLADU (type=14, len=9)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- ODESLANO (type=35, len=8)
|
||||
- CHYBA (type=14, len=1)
|
||||
- SCHVALENI (type=14, len=1)
|
||||
- MODIFIED (type=14, len=1)
|
||||
- ODESLANO_LAST (type=35, len=8)
|
||||
- ID_ZP (type=37, len=36)
|
||||
- ID_DOKLADU_VYDEJ (type=37, len=9)
|
||||
- ID_ZP_VYDEJ (type=37, len=36)
|
||||
- VYDANO (type=35, len=8)
|
||||
|
||||
** HISTDOC_EPOUKAZ_ZMENY ** (řádků: 3)
|
||||
- ID_LEKAR (type=37, len=36)
|
||||
- LAST_DATE (type=12, len=4)
|
||||
|
||||
** HISTDOC_EPRILOHY ** (řádků: 17)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- NAZEV (type=37, len=255)
|
||||
- POPIS (type=37, len=255)
|
||||
- TYP (type=37, len=7)
|
||||
- SOUBOR (type=261, len=8)
|
||||
- ID_PRILOHY (type=37, len=36)
|
||||
|
||||
** HISTDOC_EZADANKA ** (řádků: 2195)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- TYP (type=37, len=10)
|
||||
- DATUMVYS (type=12, len=4)
|
||||
- CASVYS (type=13, len=4)
|
||||
- CISLO (type=37, len=30)
|
||||
- POZNAMKA (type=261, len=8)
|
||||
- HOTOVO (type=14, len=1)
|
||||
- IDZAD (type=8, len=4)
|
||||
- VYSTEXT (type=261, len=8)
|
||||
- DATUMEXP (type=12, len=4)
|
||||
- CASEXP (type=13, len=4)
|
||||
- SABLONY (type=261, len=8)
|
||||
- PRINTED (type=35, len=8)
|
||||
- SENDED (type=35, len=8)
|
||||
- TEMPDAT (type=35, len=8)
|
||||
- SEZNAMMETOD (type=261, len=8)
|
||||
- SEZNAMSABLON (type=261, len=8)
|
||||
- DIAG1 (type=37, len=10)
|
||||
- DIAG2 (type=37, len=10)
|
||||
- DIAG3 (type=37, len=10)
|
||||
- STATIM (type=14, len=1)
|
||||
- ODEBRANO (type=35, len=8)
|
||||
- BEZODBERU (type=14, len=1)
|
||||
- EXPORTOVAT (type=35, len=8)
|
||||
- NAH1 (type=37, len=10)
|
||||
- NAH2 (type=37, len=10)
|
||||
- ODEBRAL (type=37, len=30)
|
||||
- POCETZKUM (type=8, len=4)
|
||||
- OS_NAZEV (type=37, len=100)
|
||||
- OS_ID (type=37, len=20)
|
||||
- PARAMETRY (type=261, len=8)
|
||||
- POZNAMKY (type=261, len=8)
|
||||
- STRINGPARAMETRY (type=261, len=8)
|
||||
- VZOREK1 (type=37, len=25)
|
||||
- VZOREK2 (type=37, len=25)
|
||||
- VZOREK3 (type=37, len=25)
|
||||
- VZOREK4 (type=37, len=25)
|
||||
- PLATCE (type=37, len=30)
|
||||
- OSVOBOZENDPH (type=14, len=1)
|
||||
- PLATCEPOZNAMKA (type=37, len=100)
|
||||
- KATALOGALIAS (type=37, len=20)
|
||||
- KATALOGNAZEV (type=37, len=100)
|
||||
- PRIMARNIVZORKY (type=37, len=256)
|
||||
|
||||
** HISTDOC_SCHVALENI ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDVZPARC (type=8, len=4)
|
||||
- PRILOHA (type=261, len=8)
|
||||
- ODESLANO (type=14, len=1)
|
||||
|
||||
** HOSP_ZPRAVY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- CAS (type=13, len=4)
|
||||
- ZPRAVA (type=261, len=8)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
- IDHOSP (type=8, len=4)
|
||||
|
||||
** IZIPDOC ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- STATUS (type=8, len=4)
|
||||
- IDENT (type=8, len=4)
|
||||
- ZAPIS (type=261, len=8)
|
||||
- DEKID (type=8, len=4)
|
||||
- DGN (type=14, len=5)
|
||||
- CAS (type=13, len=4)
|
||||
- IDZAR (type=8, len=4)
|
||||
- IDODD (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- AUTOR (type=8, len=4)
|
||||
- LOCKED (type=14, len=1)
|
||||
- TYP (type=14, len=1)
|
||||
- VDGN1 (type=14, len=5)
|
||||
- VDGN2 (type=14, len=5)
|
||||
- VDGN3 (type=14, len=5)
|
||||
- VDGN4 (type=14, len=5)
|
||||
|
||||
** IZIPFILES ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- DOCID (type=8, len=4)
|
||||
- TYP (type=8, len=4)
|
||||
- FILENAME (type=37, len=50)
|
||||
- BODY (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
|
||||
** LEKZPRAVY ** (řádků: 13748)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DEKURSID (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- TEXT (type=261, len=8)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDNOSVYK (type=8, len=4)
|
||||
- TYP (type=14, len=1)
|
||||
- ODESILATEL (type=37, len=254)
|
||||
- DEKURS_COPYED (type=35, len=8)
|
||||
- EISPUBLIC (type=14, len=1)
|
||||
- PRIJEMCE (type=37, len=254)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- SIGNATURE (type=261, len=8)
|
||||
- SIGNATURE_INFO (type=261, len=8)
|
||||
- IDCERTIFICATE (type=8, len=4)
|
||||
- TST (type=261, len=8)
|
||||
- NAZEV (type=37, len=80)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
- PRIJATO (type=14, len=1)
|
||||
- JMENOLEK (type=37, len=50)
|
||||
- ICP (type=14, len=8)
|
||||
- ES_STAV (type=37, len=1)
|
||||
- PR_TYP (type=14, len=1)
|
||||
- PR_ID (type=8, len=4)
|
||||
- STORNO (type=14, len=1)
|
||||
- UID (type=37, len=16)
|
||||
- PUVODNI (type=8, len=4)
|
||||
- CAS (type=13, len=4)
|
||||
- DATAKT (type=35, len=8)
|
||||
- ODALIAS (type=37, len=8)
|
||||
- PDF (type=261, len=8)
|
||||
- PREPIS (type=8, len=4)
|
||||
- OZNACENI_O (type=37, len=50)
|
||||
- STAV (type=14, len=1)
|
||||
- CLICKBOX_ATT_ID (type=8, len=4)
|
||||
- CGMNUMBER_ODESILATEL (type=37, len=12)
|
||||
- CLICKBOX_MAILBOX (type=37, len=255)
|
||||
- CGMNUMBER_PRIJEMCE (type=37, len=12)
|
||||
- CGMNUMBER (type=37, len=12)
|
||||
|
||||
** LEKZPRAVY_PR ** (řádků: 134)
|
||||
- ID (type=8, len=4)
|
||||
- LEKZPRID (type=8, len=4)
|
||||
- PRID (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
- TYP (type=14, len=1)
|
||||
- EXT (type=14, len=4)
|
||||
|
||||
** LEKZPRAVY_SEND ** (řádků: 0)
|
||||
- LEKZPRID (type=8, len=4)
|
||||
- KDY (type=35, len=8)
|
||||
- KOMU (type=37, len=80)
|
||||
|
||||
** MEDINETIN_ATTACHMENT ** (řádků: 120)
|
||||
- ID (type=8, len=4)
|
||||
- ID_MEDINETIN (type=8, len=4)
|
||||
- FILE_NAME (type=37, len=254)
|
||||
- FILE_EXT (type=37, len=5)
|
||||
- FILE_TYPE (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- ID_FILES (type=8, len=4)
|
||||
- ZPRACOVANO (type=14, len=1)
|
||||
|
||||
** MEDINETOUT_ATTACHMENT ** (řádků: 2236)
|
||||
- ID (type=8, len=4)
|
||||
- ID_MEDINETOUT (type=8, len=4)
|
||||
- FILE_ID (type=37, len=36)
|
||||
- FILE_NAME (type=37, len=254)
|
||||
- FILE_EXT (type=37, len=5)
|
||||
- FILE_TYPE (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- ATTACH_INT_TYP (type=37, len=1)
|
||||
- ATTACH_INT_ID (type=8, len=4)
|
||||
|
||||
** OSE_ZPRAVY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DATUM (type=35, len=8)
|
||||
- UZI (type=37, len=3)
|
||||
- DATA (type=261, len=8)
|
||||
- SIGNATURE (type=261, len=8)
|
||||
- SIGNATURE_INFO (type=261, len=8)
|
||||
- IDCERTIFICATE (type=8, len=4)
|
||||
- STORNO (type=35, len=8)
|
||||
- STORNOBY (type=14, len=3)
|
||||
|
||||
** SOC_FINPRIJEM_HISTDOC ** (řádků: 0)
|
||||
- ID_SFH (type=8, len=4)
|
||||
- ID_SFP (type=8, len=4)
|
||||
- ID_HDC (type=8, len=4)
|
||||
|
||||
** ZPRAVY ** (řádků: 26)
|
||||
- ID (type=8, len=4)
|
||||
- IDZPRAVY (type=8, len=4)
|
||||
- IDOD (type=8, len=4)
|
||||
- IDKOMUZAR (type=8, len=4)
|
||||
- IDKOMUODD (type=8, len=4)
|
||||
- IDKOMUPRAC (type=8, len=4)
|
||||
- IDKOMUUZIV (type=8, len=4)
|
||||
- PREDMET (type=37, len=100)
|
||||
- TEXT (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- CAS (type=13, len=4)
|
||||
- URGENTNI (type=8, len=4)
|
||||
- PRECTENA (type=8, len=4)
|
||||
- DATUMPRECT (type=12, len=4)
|
||||
- CASPRECT (type=13, len=4)
|
||||
- SMAZANA (type=8, len=4)
|
||||
- OZNAMOPRECT (type=8, len=4)
|
||||
- TYP (type=8, len=4)
|
||||
- KOMU (type=261, len=8)
|
||||
- PLATNOST (type=35, len=8)
|
||||
- LZE_ODPOVED (type=14, len=1)
|
||||
- ID_EXT (type=8, len=4)
|
||||
|
||||
** ZPRAVY_ANKETY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDPRIJEMCE (type=8, len=4)
|
||||
- ID_EXT (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- ODESLANO (type=14, len=1)
|
||||
|
||||
** ZPRAVY_EXT ** (řádků: 18)
|
||||
- ID (type=8, len=4)
|
||||
- ID_EXT (type=8, len=4)
|
||||
- ID_ODESILATEL (type=8, len=4)
|
||||
- TYP (type=37, len=1)
|
||||
- PRIORITA (type=37, len=1)
|
||||
- DATUM (type=35, len=8)
|
||||
- PREDMET (type=37, len=254)
|
||||
- DATA (type=261, len=8)
|
||||
- STAV (type=37, len=1)
|
||||
- AKCE (type=37, len=20)
|
||||
- PRIJEMCE (type=37, len=3)
|
||||
- ODESLAL (type=37, len=20)
|
||||
- ODB (type=37, len=3)
|
||||
|
||||
** ZPRAVY_LOCK ** (řádků: 1)
|
||||
- IDUZI (type=8, len=4)
|
||||
- BEGINTIME (type=35, len=8)
|
||||
- ENDTIME (type=35, len=8)
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Průzkum Medicus databáze – výpis tabulek a jejich sloupců.
|
||||
"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
# Všechny uživatelské tabulky
|
||||
cur.execute("""
|
||||
SELECT rdb$relation_name
|
||||
FROM rdb$relations
|
||||
WHERE rdb$view_blr IS NULL
|
||||
AND (rdb$system_flag IS NULL OR rdb$system_flag = 0)
|
||||
ORDER BY rdb$relation_name
|
||||
""")
|
||||
tables = [row[0].strip() for row in cur.fetchall()]
|
||||
|
||||
print(f"Počet tabulek: {len(tables)}\n")
|
||||
print("=== SEZNAM TABULEK ===")
|
||||
for t in tables:
|
||||
print(f" {t}")
|
||||
|
||||
print("\n=== SLOUPCE KAŽDÉ TABULKY ===")
|
||||
for table in tables:
|
||||
cur.execute("""
|
||||
SELECT rf.rdb$field_name, f.rdb$field_type, f.rdb$field_length
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE rf.rdb$relation_name = ?
|
||||
ORDER BY rf.rdb$field_position
|
||||
""", (table,))
|
||||
cols = cur.fetchall()
|
||||
col_names = [c[0].strip() for c in cols]
|
||||
print(f"\n{table}")
|
||||
print(" " + ", ".join(col_names))
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
@@ -0,0 +1,114 @@
|
||||
import os,fdb,datetime
|
||||
|
||||
# conn = fdb.connect(
|
||||
# dsn=r'localhost:u:\medicus 3\data\medicus.fdb', # Database path
|
||||
# user='SYSDBA', # Username
|
||||
# password="masterkey", # Password,
|
||||
# charset="win1250")
|
||||
# cur = conn.cursor()
|
||||
|
||||
def zapis_file(vstupconnection,idpac,cesta,souborname,prvnizavorka,soubordate,souborfiledate,poznamka):
|
||||
import funkce
|
||||
cur=vstupconnection.cursor()
|
||||
fileid = funkce.get_files_id(vstupconnection)
|
||||
with open(os.path.join(cesta,souborname), 'rb') as f:
|
||||
daticka = f.read()
|
||||
query = "insert into files (id,iduzi,iddoctyp,typ,idpac,filename,body,datum,datsouboru,poznamka) values(?,?,?,?,?,?,?,?,?,?)"
|
||||
cur.execute(query,(fileid,6,1,1,idpac,prvnizavorka+".pdf",daticka,soubordate,souborfiledate,poznamka[:99]))
|
||||
vstupconnection.commit()
|
||||
return fileid
|
||||
def zapis_dekurs(vstupconnection, idpac, idodd, iduzi, idprac, idfile, filename, text, datumzpravy,
|
||||
datumsouboru):
|
||||
import funkce
|
||||
dekursid = funkce.get_dekurs_id(vstupconnection)
|
||||
cur = vstupconnection.cursor()
|
||||
print("Funkce zapis_dekurs hlasí OK")
|
||||
print("idpac", idpac)
|
||||
print("idodd", idodd)
|
||||
print("iduzi", iduzi)
|
||||
print("idfile", idfile)
|
||||
print("filename", filename)
|
||||
print("text", text)
|
||||
print("datumzpravy", datumzpravy)
|
||||
print("datumsouboru", datumsouboru)
|
||||
print("dekursid", dekursid)
|
||||
|
||||
# rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks "BOOKMARKNAME","Files:FILEID",9}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
# {\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
# {\stylesheet{\s0\fi0\li0\ql\ri0\sb0\sa0 Norm\'e1ln\'ed;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
# \uc1\pard\s0\plain\cs20\f0\i\fs20 P\'f8\'edlohy: {\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 BOOKMARKNAME{\*\bkmkend 0}\par
|
||||
# \pard\s0\plain\cs15\f0\fs20 \par
|
||||
# }
|
||||
# """
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks "BOOKMARKNAME","Files:FILEID",9}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs22\f0\ul\fs20\cf1 Odkaz;}}
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 P\'f8\'edlohy:\par
|
||||
\pard\s10{\*\bkmkstart 0}\plain\cs22\f0\ul\fs20\cf1 BOOKMARKNAME{\*\bkmkend 0}\par
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}
|
||||
|
||||
"""
|
||||
# id idpac filename body docid typ datum iddoctyp poznamka idpac=2 iduzi=2 datsouboru id_edokument ext_id
|
||||
encodedbookmark = funkce.convert_to1250(filename)
|
||||
print("Encodedbookmark", encodedbookmark)
|
||||
rtf = rtf.replace("BOOKMARKNAME", encodedbookmark)
|
||||
rtf = rtf.replace("FILEID", str(idfile))
|
||||
rtf = rtf.replace("TEXTENTER", text)
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
print("Datumzapisu", datumzapisu)
|
||||
print("Caszapisu", caszapisu)
|
||||
print("RTF", rtf)
|
||||
cur.execute("insert into dekurs (id,idpac,idodd,iduzi,idprac,datum,cas,dekurs) values(?,?,?,?,?,?,?,?)",
|
||||
(dekursid, idpac, idodd, iduzi, idprac, datumzapisu, caszapisu, rtf))
|
||||
vstupconnection.commit()
|
||||
|
||||
|
||||
def convert_to1250(retezec):
|
||||
retezec=retezec.encode("cp1250")
|
||||
retezec=str(retezec)[2:]
|
||||
retezec = retezec[:-1]
|
||||
retezec=retezec.replace(r"\x",r"\'")
|
||||
return retezec
|
||||
|
||||
|
||||
# x=convert_to1250("Příloha")
|
||||
# print(x,len(x))
|
||||
|
||||
def get_dekurs_id(connection):
|
||||
try:
|
||||
query = "SELECT GEN_ID(Gen_Dekurs, 1) FROM RDB$DATABASE"
|
||||
cur = connection.cursor()
|
||||
cur.execute(query)
|
||||
newid=cur.fetchone()[0]
|
||||
print("Funkce GET_DEKURS_ID přiřadila nové ID:",newid)
|
||||
return(newid)
|
||||
except:
|
||||
print("Funkce GET_DEKURS_ID nepřiřadila nové ID")
|
||||
return(None)
|
||||
|
||||
def get_files_id(connection):
|
||||
try:
|
||||
query = "SELECT GEN_ID(Gen_Files, 1) FROM RDB$DATABASE"
|
||||
cur=connection.cursor()
|
||||
cur.execute(query)
|
||||
newid=cur.fetchone()[0]
|
||||
print(newid)
|
||||
return(newid)
|
||||
except:
|
||||
return(None)
|
||||
|
||||
def get_idpac(rodnecislo,connection):
|
||||
try:
|
||||
query = "SELECT idpac,prijmeni FROM kar where rodcis=?"
|
||||
cur = connection.cursor()
|
||||
cur.execute(query,(rodnecislo,))
|
||||
tmp_nacteno=cur.fetchone()
|
||||
tmp_id = tmp_nacteno[0]
|
||||
tmp_jmeno=tmp_nacteno[1]
|
||||
print(f"Pacient s rodným číslem {rodnecislo} má ID {tmp_id} a jméno {tmp_jmeno}")
|
||||
return (tmp_id)
|
||||
except:
|
||||
return(None)
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
"""funkce_ext.py – zápis PDF souborů přímo do měsíční externí Firebird DB
|
||||
místo do hlavní medicus.fdb – spouštět na Windows
|
||||
|
||||
Náhrada za funkce.zapis_file() v s03soubory.py.
|
||||
Binární data souboru se uloží do MEDICUS_FILES_YYYYMM.fdb (DATA tabulka),
|
||||
do hlavní FILES.BODY se vloží 48bajtová reference.
|
||||
|
||||
Formát FILES.BODY reference (48 B):
|
||||
magic 4 B = b'\\xee\\xbb\\xaa\\x0b'
|
||||
uid 32 B = UUID4 hex (ASCII, 32 znaků)
|
||||
dblen 4 B = délka DBNAME jako little-endian uint32
|
||||
dbname N B = DBNAME ASCII (např. 'DB202603', 8 B)
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import uuid
|
||||
import fdb
|
||||
|
||||
|
||||
# Magic bajty identifikující referenci na externí DB
|
||||
MAGIC = b'\xee\xbb\xaa\x0b'
|
||||
|
||||
|
||||
# ─── Pomocné funkce ──────────────────────────────────────────────────────────
|
||||
|
||||
def make_body_ref(uid: str, dbname: str) -> bytes:
|
||||
"""Sestaví 48bajtovou binární referenci pro FILES.BODY."""
|
||||
uid_bytes = uid.encode('ascii') # 32 B
|
||||
dbname_bytes = dbname.encode('ascii') # typicky 8 B ('DB202603')
|
||||
return MAGIC + uid_bytes + struct.pack('<I', len(dbname_bytes)) + dbname_bytes
|
||||
|
||||
|
||||
def _get_ext_server(vstupconnection):
|
||||
"""Zjistí SERVER z existující EXTERNI_DB (např. 'localhost/3053')."""
|
||||
cur = vstupconnection.cursor()
|
||||
cur.execute(
|
||||
"SELECT SERVER FROM EXTERNI_DB WHERE DBNAME LIKE 'DB%' ORDER BY DBNAME DESC ROWS 1"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return row[0] if row else 'localhost/3053'
|
||||
|
||||
|
||||
def _get_ext_heslo(vstupconnection):
|
||||
"""Přečte šifrované heslo z existující EXTERNI_DB.
|
||||
Všechny externí DB mají stejné heslo (masterkey, jen zašifrované Medicusem),
|
||||
takže stačí vzít heslo z libovolné existující položky."""
|
||||
cur = vstupconnection.cursor()
|
||||
cur.execute(
|
||||
"SELECT HESLO FROM EXTERNI_DB WHERE DBNAME LIKE 'DB%' ORDER BY DBNAME DESC ROWS 1"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return row[0] if row else 'masterkey'
|
||||
|
||||
|
||||
def _vytvor_externi_db(ext_path):
|
||||
"""Vytvoří novou prázdnou externí Firebird DB s tabulkou DATA a generátorem GEN_UID.
|
||||
Schéma odpovídá tomu, co vytváří Medicus sám při prvním exportu daného měsíce."""
|
||||
print(f" Vytvářím novou ext DB: {ext_path}")
|
||||
conn = fdb.create_database(
|
||||
dsn=f'localhost:{ext_path}',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250',
|
||||
page_size=16384
|
||||
)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
CREATE TABLE DATA (
|
||||
UID VARCHAR(32) NOT NULL,
|
||||
DATA BLOB SUB_TYPE 0,
|
||||
DATASIZE INTEGER,
|
||||
CHUNK INTEGER
|
||||
)
|
||||
""")
|
||||
cur.execute("CREATE GENERATOR GEN_UID")
|
||||
conn.commit()
|
||||
print(f" Nová ext DB vytvořena: {ext_path}")
|
||||
return conn
|
||||
|
||||
|
||||
# ─── Hlavní funkce ───────────────────────────────────────────────────────────
|
||||
|
||||
def zapis_file_ext(vstupconnection, idpac, cesta, souborname, prvnizavorka,
|
||||
soubordate, souborfiledate, poznamka,
|
||||
ext_base_path=r'u:\externi'):
|
||||
"""Zapíše PDF soubor do měsíční externí DB a vrátí fileid.
|
||||
|
||||
Parametry jsou shodné s funkce.zapis_file() – stačí prohodit import.
|
||||
|
||||
Postup:
|
||||
1. Určí měsíc z soubordate → DBNAME (např. 'DB202603')
|
||||
2. Načte binární data PDF
|
||||
3. Najde nebo vytvoří měsíční MEDICUS_FILES_YYYYMM.fdb
|
||||
4. Zapíše data do DATA tabulky pod novým UID (UUID4 hex)
|
||||
5. Sestaví 48bajtovou BODY referenci
|
||||
6. Získá nové FILES ID z Gen_Files
|
||||
7. Vloží záznam do hlavní FILES tabulky (BODY = reference, ne BLOB)
|
||||
8. Vrátí fileid (shodné s funkce.zapis_file())
|
||||
"""
|
||||
import funkce # get_files_id ze stávajícího funkce.py
|
||||
|
||||
# 1. Měsíc a DBNAME
|
||||
yyyymm = soubordate.strftime('%Y%m')
|
||||
dbname = f'DB{yyyymm}'
|
||||
print(f" DBNAME: {dbname}")
|
||||
|
||||
# 2. Binární data PDF
|
||||
soubor_cesta = os.path.join(cesta, souborname)
|
||||
with open(soubor_cesta, 'rb') as f:
|
||||
data = f.read()
|
||||
print(f" Načten soubor: {souborname} ({len(data):,} B)")
|
||||
|
||||
# 3. Připojení k externí DB
|
||||
cur = vstupconnection.cursor()
|
||||
cur.execute(
|
||||
"SELECT SERVER, PATH FROM EXTERNI_DB WHERE DBNAME = ?", (dbname,)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
|
||||
if row:
|
||||
# Existující měsíční DB – připojíme se
|
||||
ext_server_raw, ext_path = row[0], row[1]
|
||||
# SERVER je "localhost/3053" (FBScanner proxy) – bereme jen hostname
|
||||
ext_server = ext_server_raw.split('/')[0]
|
||||
print(f" Existující ext DB: {dbname} → {ext_path}")
|
||||
conn_ext = fdb.connect(
|
||||
dsn=f'{ext_server}:{ext_path}',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250'
|
||||
)
|
||||
else:
|
||||
# Nová měsíční DB – vytvoříme a zaregistrujeme
|
||||
ext_filename = f'MEDICUS_FILES_{yyyymm}.fdb'
|
||||
ext_path = os.path.join(ext_base_path, ext_filename)
|
||||
conn_ext = _vytvor_externi_db(ext_path)
|
||||
|
||||
# Zaregistrovat do EXTERNI_DB (heslo zkopírujeme z existující položky)
|
||||
ext_server = _get_ext_server(vstupconnection)
|
||||
heslo = _get_ext_heslo(vstupconnection)
|
||||
cur.execute(
|
||||
"INSERT INTO EXTERNI_DB (DBNAME, SERVER, PATH, HESLO) VALUES (?, ?, ?, ?)",
|
||||
(dbname, ext_server, ext_path, heslo)
|
||||
)
|
||||
vstupconnection.commit()
|
||||
print(f" Registrována v EXTERNI_DB: {dbname}, server={ext_server}")
|
||||
|
||||
# 4. Zápis do DATA tabulky
|
||||
uid = uuid.uuid4().hex # 32 hex znaků, vždy unikátní
|
||||
cur_ext = conn_ext.cursor()
|
||||
cur_ext.execute(
|
||||
"INSERT INTO DATA (UID, DATA, DATASIZE, CHUNK) VALUES (?, ?, ?, ?)",
|
||||
(uid, data, len(data), 0)
|
||||
)
|
||||
conn_ext.commit()
|
||||
conn_ext.close()
|
||||
print(f" Zapsáno do ext DB: UID={uid}")
|
||||
|
||||
# 5. BODY reference (48 B)
|
||||
body_ref = make_body_ref(uid, dbname)
|
||||
print(f" BODY ref: {body_ref.hex()}")
|
||||
|
||||
# 6. Nové FILES ID
|
||||
fileid = funkce.get_files_id(vstupconnection)
|
||||
if fileid is None:
|
||||
raise RuntimeError("Nepodařilo se získat nové FILES ID (Gen_Files selhal)!")
|
||||
|
||||
# 7. INSERT do hlavní FILES tabulky
|
||||
cur.execute(
|
||||
"INSERT INTO FILES "
|
||||
"(id, iduzi, iddoctyp, typ, idpac, filename, body, datum, datsouboru, poznamka) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(fileid, 6, 1, 1, idpac,
|
||||
prvnizavorka + '.pdf',
|
||||
body_ref,
|
||||
soubordate, souborfiledate,
|
||||
poznamka[:99])
|
||||
)
|
||||
vstupconnection.commit()
|
||||
print(f" FILES záznam vytvořen: ID={fileid}, idpac={idpac}, "
|
||||
f"soubor={prvnizavorka}.pdf")
|
||||
|
||||
return fileid
|
||||
@@ -0,0 +1,23 @@
|
||||
import fdb
|
||||
import datetime
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute('SELECT MAX(ID) FROM DEKURS')
|
||||
next_id = cur.fetchone()[0] + 1
|
||||
print('Next ID:', next_id)
|
||||
|
||||
text = r'{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\fonttbl{\f0\fnil\fcharset238 Arial;}}{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}' + '\n' + r'\pard\plain\f0\fs20 Test Claude AI - automaticky vlo\u382en\'fd z\'e1znam 13.03.2026\par' + '\n}'
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO DEKURS (ID, IDPAC, IDODD, IDPRAC, IDUZI, DATUM, CAS, DEKURS, DGN1)
|
||||
VALUES (?, 9742, 2, 2, 6, ?, ?, ?, 'Z000 ')
|
||||
""", (next_id, datetime.date(2026, 3, 13), datetime.time(12, 0, 0), text))
|
||||
|
||||
conn.commit()
|
||||
print('Vloženo OK, ID:', next_id)
|
||||
conn.close()
|
||||
@@ -0,0 +1,28 @@
|
||||
import fdb
|
||||
import sys
|
||||
|
||||
table = sys.argv[1] if len(sys.argv) > 1 else 'DEKURS'
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT rf.rdb$field_name, f.rdb$field_type, f.rdb$field_length
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE rf.rdb$relation_name = ?
|
||||
ORDER BY rf.rdb$field_position
|
||||
""", (table,))
|
||||
cols = cur.fetchall()
|
||||
print(f"=== {table} – sloupce ===")
|
||||
for c in cols:
|
||||
print(f" {c[0].strip():<30} typ: {c[1]} délka: {c[2]}")
|
||||
|
||||
cur.execute(f'SELECT COUNT(*) FROM {table}')
|
||||
print(f"\nPočet záznamů: {cur.fetchone()[0]}")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
@@ -0,0 +1,16 @@
|
||||
# Firebird trace konfigurace – zachytí SQL příkazy Medicusu
|
||||
# Soubor: medicus_trace.conf
|
||||
|
||||
<database c:\\medicus 3\\data\\medicus.fdb>
|
||||
enabled true
|
||||
log_connections false
|
||||
log_transactions false
|
||||
log_statement_prepare false
|
||||
log_statement_start false
|
||||
log_statement_finish true
|
||||
log_procedure_finish false
|
||||
print_plan false
|
||||
print_perf false
|
||||
time_threshold 0
|
||||
max_sql_length 32768
|
||||
</database>
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
"""read_last_dekurs.py – jednorázový skript: vypíše obsah posledního DEKURS záznamu pro Buzalka"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, CAS, CHAR_LENGTH(DEKURS), DEKURS
|
||||
FROM DEKURS
|
||||
WHERE IDPAC = 9742
|
||||
ORDER BY ID DESC
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
idek, datum, cas, delka, obsah = row
|
||||
|
||||
print(f"ID={idek} datum={datum} cas={cas} délka={delka} B")
|
||||
print("=" * 60)
|
||||
print(obsah)
|
||||
conn.close()
|
||||
@@ -0,0 +1,323 @@
|
||||
import os, shutil, fdb, time
|
||||
import re, datetime, funkce, funkce_ext
|
||||
|
||||
# Connect to the Firebird database
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password="masterkey",
|
||||
charset="win1250")
|
||||
|
||||
cesta = r"u:\testimport"
|
||||
cestazpracovana = r"u:\testimportzpracovana"
|
||||
|
||||
# Konstanty pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─── Helper funkce ────────────────────────────────────────────────────────────
|
||||
|
||||
def restore_files_for_import(retezec):
|
||||
drop = r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
|
||||
next = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
|
||||
if not os.path.exists(drop):
|
||||
print(f"The directory '{drop}' does not exist.")
|
||||
return
|
||||
for item in os.listdir(drop):
|
||||
item_path = os.path.join(drop, item)
|
||||
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
print(f"Deleted file: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
print(f"Deleted directory: {item_path}")
|
||||
for item in os.listdir(next):
|
||||
item_path = os.path.join(next, item)
|
||||
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
|
||||
shutil.copy(item_path, os.path.join(drop, item))
|
||||
print(f"Copied file: {item_path}")
|
||||
|
||||
|
||||
def kontrola_rc(rc, connection):
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac", (rc,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def kontrola_struktury(souborname, connection):
|
||||
if souborname.endswith('.pdf'):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
vpohode = True
|
||||
if match and len(match.groups()) == 5:
|
||||
datum = match.group(2)
|
||||
try:
|
||||
datetime.datetime.strptime(datum, "%Y-%m-%d").date()
|
||||
except:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
|
||||
row = cur.fetchone()[0]
|
||||
if row != 1:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
return vpohode
|
||||
|
||||
|
||||
def vrat_info_o_souboru(souborname, connection):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
rc = match.group(1)
|
||||
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
|
||||
jmeno = match.group(3)
|
||||
prvnizavorka = match.group(4)
|
||||
druhazavorka = match.group(5)
|
||||
cur = connection.cursor()
|
||||
cur.execute("select idpac from kar where rodcis=?", (rc,))
|
||||
idpac = cur.fetchone()[0]
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta, souborname)))
|
||||
return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
|
||||
|
||||
|
||||
def prejmenuj_chybny_soubor(souborname, cesta):
|
||||
if souborname[0] != "♥":
|
||||
soubornovy = "♥" + souborname
|
||||
os.rename(os.path.join(cesta, souborname), os.path.join(cesta, soubornovy))
|
||||
|
||||
|
||||
def najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni):
|
||||
"""Vrátí (id, rtf) posledního dekurzu pacienta pokud je z dnešního dne, jinak None."""
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
if dekurs_datum == datum_vlozeni:
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
else:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový")
|
||||
return None
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list):
|
||||
"""Přidá více souborů do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nové indexy začínají od N
|
||||
2. Vloží nové \\pard řádky před uzavírací prázdný řádek sekce
|
||||
3. Přidá nové bookmarky na konec {\\info{\\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
n_files = 0
|
||||
print(f" Počet existujících Files odkazů: {n_files}, přidávám {len(bookmark_list)} nových")
|
||||
|
||||
# 2. Vložit nové \pard řádky před PRILOHY_CLOSING
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pards = ''
|
||||
for i, fname in enumerate(filenameforbookmark_list):
|
||||
idx = n_files + i
|
||||
new_pards += (r'\pard\s10{\*\bkmkstart ' + str(idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + fname
|
||||
+ r'{\*\bkmkend ' + str(idx) + r'}\par' + '\n')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pards + rtf[closing_pos:]
|
||||
|
||||
# 3. Přidat nové bookmarky na konec {\info{\bookmarks ...}}
|
||||
def append_bookmarks(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.extend(bookmark_list)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', append_bookmarks, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží novou sekci příloh na ZAČÁTEK stávajícího dekurzu (sekce tam ještě není)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# Šablona RTF pro nový dekurs
|
||||
RTF_TEMPLATE = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
# ─── Hlavní tělo skriptu ──────────────────────────────────────────────────────
|
||||
|
||||
info = []
|
||||
for soubor in os.listdir(cesta):
|
||||
if os.path.isfile(os.path.join(cesta, soubor)):
|
||||
print(soubor)
|
||||
if kontrola_struktury(soubor, conn):
|
||||
info.append(vrat_info_o_souboru(soubor, conn))
|
||||
else:
|
||||
prejmenuj_chybny_soubor(soubor, cesta)
|
||||
|
||||
info = sorted(info, key=lambda x: (x[0], x[1]))
|
||||
print(info)
|
||||
|
||||
skupiny = {}
|
||||
for row in info:
|
||||
skupiny[row[0]] = []
|
||||
for row in info:
|
||||
skupiny[row[0]].append(row)
|
||||
|
||||
for key in skupiny.keys():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RC: {key}, souborů: {len(skupiny[key])}")
|
||||
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
bookmark_list = []
|
||||
filenameforbookmark_list = []
|
||||
bookmarks_body = ''
|
||||
idpac = skupiny[key][0][1]
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB + přesunout do zpracovaných ────
|
||||
for row in skupiny[key]:
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=row[1],
|
||||
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
|
||||
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
|
||||
print(f" → FILES.ID = {fileid} ({row[6]})")
|
||||
|
||||
# Přesun souboru do zpracovaných
|
||||
for attempt in range(3):
|
||||
try:
|
||||
dest = os.path.join(cestazpracovana, row[6])
|
||||
if not os.path.exists(dest):
|
||||
shutil.move(os.path.join(cesta, row[6]), dest)
|
||||
else:
|
||||
ts = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
shutil.move(os.path.join(cesta, row[6]),
|
||||
os.path.join(cestazpracovana, row[6][:-4] + " " + ts + ".pdf"))
|
||||
print(" Přesun OK!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < 2:
|
||||
print(" Retrying in 5 seconds...")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(" Max retries reached. Command failed.")
|
||||
|
||||
filenameforbookmark = row[2].strftime('%Y-%m-%d') + ' ' + row[4] + ': ' + row[5]
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
filenameforbookmark_list.append(filenameforbookmark)
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# ── Krok 2: sestavit tělo nové sekce příloh ───────────────────────────────
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 3: rozhodovací logika (3 případy) ────────────────────────────────
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
cur = conn.cursor()
|
||||
|
||||
print(f"\n>>> Hledám poslední dekurs pro IDPAC={idpac}...")
|
||||
existujici = najdi_posledni_dekurs_dnes(conn, idpac, datumzapisu)
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# Případ 1: dnešní dekurs má sekci příloh → přidáme soubory dovnitř
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubory DO existující sekce...")
|
||||
merged_rtf = pridat_do_sekce_prilohy(existing_rtf, bookmark_list, filenameforbookmark_list)
|
||||
else:
|
||||
# Případ 2: dnešní dekurs existuje, ale sekci příloh nemá → prepend
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → vkládám sekci na začátek...")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(skupiny[key]))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# Případ 3: žádný dnešní dekurs → vytvoříme nový
|
||||
print(f"\n>>> Žádný dekurs pro dnešek → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = RTF_TEMPLATE.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(dekursid, 6, 2, 2, idpac, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
print("\n=== HOTOVO ===")
|
||||
conn.close()
|
||||
@@ -0,0 +1,397 @@
|
||||
import os, shutil, fdb, time, threading
|
||||
import re, datetime, funkce, funkce_ext
|
||||
|
||||
# Connect to the Firebird database
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password="masterkey",
|
||||
charset="win1250")
|
||||
|
||||
cesta = r"u:\testimport"
|
||||
cestazpracovana = r"u:\testimportzpracovana"
|
||||
|
||||
# Konstanty pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─── Helper funkce ────────────────────────────────────────────────────────────
|
||||
|
||||
def restore_files_for_import(retezec):
|
||||
drop = r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
|
||||
next = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
|
||||
if not os.path.exists(drop):
|
||||
print(f"The directory '{drop}' does not exist.")
|
||||
return
|
||||
for item in os.listdir(drop):
|
||||
item_path = os.path.join(drop, item)
|
||||
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
print(f"Deleted file: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
print(f"Deleted directory: {item_path}")
|
||||
for item in os.listdir(next):
|
||||
item_path = os.path.join(next, item)
|
||||
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
|
||||
shutil.copy(item_path, os.path.join(drop, item))
|
||||
print(f"Copied file: {item_path}")
|
||||
|
||||
|
||||
def kontrola_rc(rc, connection):
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac", (rc,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def kontrola_struktury(souborname, connection):
|
||||
if souborname.endswith('.pdf'):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
vpohode = True
|
||||
if match and len(match.groups()) == 5:
|
||||
datum = match.group(2)
|
||||
try:
|
||||
datetime.datetime.strptime(datum, "%Y-%m-%d").date()
|
||||
except:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
|
||||
row = cur.fetchone()[0]
|
||||
if row != 1:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
return vpohode
|
||||
|
||||
|
||||
def vrat_info_o_souboru(souborname, connection):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
rc = match.group(1)
|
||||
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
|
||||
jmeno = match.group(3)
|
||||
prvnizavorka = match.group(4)
|
||||
druhazavorka = match.group(5)
|
||||
cur = connection.cursor()
|
||||
cur.execute("select idpac from kar where rodcis=?", (rc,))
|
||||
idpac = cur.fetchone()[0]
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta, souborname)))
|
||||
return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
|
||||
|
||||
|
||||
def prejmenuj_chybny_soubor(souborname, cesta):
|
||||
if souborname[0] != "♥":
|
||||
soubornovy = "♥" + souborname
|
||||
os.rename(os.path.join(cesta, souborname), os.path.join(cesta, soubornovy))
|
||||
|
||||
|
||||
def _pokus_o_zamek(dekurs_id, vysledek):
|
||||
"""Běží ve vlákně: pokusí se zamknout dekurz přes separátní spojení.
|
||||
Výsledek zapíše do slovníku vysledek: {'ok': True} nebo {'chyba': str}.
|
||||
Pokud vlákno stále běží po uplynutí timeoutu → záznam je zamčený.
|
||||
"""
|
||||
conn_t = None
|
||||
try:
|
||||
conn_t = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur_t = conn_t.cursor()
|
||||
cur_t.execute(
|
||||
"SELECT ID FROM DEKURS WHERE ID = ? FOR UPDATE WITH LOCK",
|
||||
(dekurs_id,)
|
||||
)
|
||||
cur_t.fetchone()
|
||||
conn_t.rollback() # Uvolni zámek – sloužil jen k ověření
|
||||
vysledek['ok'] = True
|
||||
except Exception as e:
|
||||
vysledek['chyba'] = str(e)
|
||||
finally:
|
||||
if conn_t:
|
||||
try:
|
||||
conn_t.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def zkus_zamknout_dnesni_dekurs(conn, idpac, datum_vlozeni, timeout_sec=2):
|
||||
"""Zjistí zda existuje dnešní dekurz a ověří že není zamčený.
|
||||
|
||||
Vrátí:
|
||||
(id, rtf) – dnešní dekurz existuje a není zamčený
|
||||
None – žádný dnešní dekurz (bude se dělat INSERT, zámek není potřeba)
|
||||
|
||||
Vyhodí RuntimeError pokud je záznam zamčený jiným uživatelem (Medicus ho má otevřený).
|
||||
|
||||
Poznámka: NOWAIT transakci fdb neumí spolehlivě nastavit, proto spustíme
|
||||
pokus o zámek ve vlákně s timeoutem. Pokud vlákno do timeout_sec sekund
|
||||
neskončí, záznam je zamčený a přeskočíme celou skupinu.
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
|
||||
# Krok 1: přečti ID, datum a obsah posledního dekurzu (běžný SELECT)
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
print(f" Žádný dekurz pro pacienta IDPAC={idpac}")
|
||||
return None
|
||||
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
|
||||
if dekurs_datum != datum_vlozeni:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový (INSERT)")
|
||||
return None
|
||||
|
||||
# Krok 2: ověř přes vlákno s timeoutem zda záznam není zamčený
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓ – ověřuji zámek (timeout {timeout_sec}s)...")
|
||||
vysledek = {}
|
||||
t = threading.Thread(target=_pokus_o_zamek, args=(dekurs_id, vysledek), daemon=True)
|
||||
t.start()
|
||||
t.join(timeout=timeout_sec)
|
||||
|
||||
if t.is_alive():
|
||||
# Vlákno stále čeká na zámek = záznam drží Medicus
|
||||
raise RuntimeError(f"DEKURZ ID={dekurs_id} je zamčený (Medicus má záznam otevřený)")
|
||||
|
||||
if 'chyba' in vysledek:
|
||||
raise fdb.DatabaseError(vysledek['chyba'])
|
||||
|
||||
print(f" → záznam volný, pokračuji se zápisem")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list):
|
||||
"""Přidá více souborů do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nové indexy začínají od N
|
||||
2. Vloží nové \\pard řádky před uzavírací prázdný řádek sekce
|
||||
3. Přidá nové bookmarky na konec {\\info{\\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
n_files = 0
|
||||
print(f" Počet existujících Files odkazů: {n_files}, přidávám {len(bookmark_list)} nových")
|
||||
|
||||
# 2. Vložit nové \pard řádky před PRILOHY_CLOSING
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pards = ''
|
||||
for i, fname in enumerate(filenameforbookmark_list):
|
||||
idx = n_files + i
|
||||
new_pards += (r'\pard\s10{\*\bkmkstart ' + str(idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + fname
|
||||
+ r'{\*\bkmkend ' + str(idx) + r'}\par' + '\n')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pards + rtf[closing_pos:]
|
||||
|
||||
# 3. Přidat nové bookmarky na konec {\info{\bookmarks ...}}
|
||||
def append_bookmarks(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.extend(bookmark_list)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', append_bookmarks, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží novou sekci příloh na ZAČÁTEK stávajícího dekurzu (sekce tam ještě není)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# Šablona RTF pro nový dekurs
|
||||
RTF_TEMPLATE = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
# ─── Hlavní tělo skriptu ──────────────────────────────────────────────────────
|
||||
|
||||
info = []
|
||||
for soubor in os.listdir(cesta):
|
||||
if os.path.isfile(os.path.join(cesta, soubor)):
|
||||
print(soubor)
|
||||
if kontrola_struktury(soubor, conn):
|
||||
info.append(vrat_info_o_souboru(soubor, conn))
|
||||
else:
|
||||
prejmenuj_chybny_soubor(soubor, cesta)
|
||||
|
||||
info = sorted(info, key=lambda x: (x[0], x[1]))
|
||||
print(info)
|
||||
|
||||
skupiny = {}
|
||||
for row in info:
|
||||
skupiny[row[0]] = []
|
||||
for row in info:
|
||||
skupiny[row[0]].append(row)
|
||||
|
||||
for key in skupiny.keys():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RC: {key}, souborů: {len(skupiny[key])}")
|
||||
|
||||
idpac = skupiny[key][0][1]
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
|
||||
# ── PRE-CHECK: zkus zamknout dnešní dekurz PŘED zpracováním souborů ──────
|
||||
print(f"\n>>> Kontrola zámku dekurzu pro IDPAC={idpac}...")
|
||||
try:
|
||||
existujici = zkus_zamknout_dnesni_dekurs(conn, idpac, datumzapisu)
|
||||
except RuntimeError as e:
|
||||
# Vlákno nepřišlo do timeoutu = záznam drží Medicus
|
||||
print(f"\n!!! DEKURZ ZAMČEN – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
except fdb.DatabaseError as e:
|
||||
chyba = str(e).lower()
|
||||
if 'deadlock' in chyba or 'lock conflict' in chyba or 'update conflict' in chyba:
|
||||
print(f"\n!!! DEKURZ ZAMČEN (DB konflikt) – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
raise # jiná DB chyba – propaguj dál
|
||||
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
bookmark_list = []
|
||||
filenameforbookmark_list = []
|
||||
bookmarks_body = ''
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB + přesunout do zpracovaných ────
|
||||
for row in skupiny[key]:
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=row[1],
|
||||
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
|
||||
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
|
||||
print(f" → FILES.ID = {fileid} ({row[6]})")
|
||||
|
||||
# Přesun souboru do zpracovaných
|
||||
for attempt in range(3):
|
||||
try:
|
||||
dest = os.path.join(cestazpracovana, row[6])
|
||||
if not os.path.exists(dest):
|
||||
shutil.move(os.path.join(cesta, row[6]), dest)
|
||||
else:
|
||||
ts = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
shutil.move(os.path.join(cesta, row[6]),
|
||||
os.path.join(cestazpracovana, row[6][:-4] + " " + ts + ".pdf"))
|
||||
print(" Přesun OK!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < 2:
|
||||
print(" Retrying in 5 seconds...")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(" Max retries reached. Command failed.")
|
||||
|
||||
filenameforbookmark = row[2].strftime('%Y-%m-%d') + ' ' + row[4] + ': ' + row[5]
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
filenameforbookmark_list.append(filenameforbookmark)
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# ── Krok 2: sestavit tělo nové sekce příloh ───────────────────────────────
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 3: rozhodovací logika (3 případy) ────────────────────────────────
|
||||
cur = conn.cursor()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# Případ 1: dnešní dekurz má sekci příloh → přidáme soubory dovnitř
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubory DO existující sekce...")
|
||||
merged_rtf = pridat_do_sekce_prilohy(existing_rtf, bookmark_list, filenameforbookmark_list)
|
||||
else:
|
||||
# Případ 2: dnešní dekurz existuje, ale sekci příloh nemá → prepend
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → vkládám sekci na začátek...")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(skupiny[key]))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# Případ 3: žádný dnešní dekurz → vytvoříme nový
|
||||
print(f"\n>>> Žádný dekurs pro dnešek → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = RTF_TEMPLATE.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(dekursid, 6, 2, 2, idpac, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
print("\n=== HOTOVO ===")
|
||||
conn.close()
|
||||
@@ -0,0 +1,397 @@
|
||||
import os, shutil, fdb, time, threading
|
||||
import re, datetime, funkce, funkce_ext
|
||||
|
||||
# Connect to the Firebird database
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password="masterkey",
|
||||
charset="win1250")
|
||||
|
||||
cesta = r"u:\testimport"
|
||||
cestazpracovana = r"u:\testimportzpracovana"
|
||||
|
||||
# Konstanty pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─── Helper funkce ────────────────────────────────────────────────────────────
|
||||
|
||||
def restore_files_for_import(retezec):
|
||||
drop = r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
|
||||
next = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
|
||||
if not os.path.exists(drop):
|
||||
print(f"The directory '{drop}' does not exist.")
|
||||
return
|
||||
for item in os.listdir(drop):
|
||||
item_path = os.path.join(drop, item)
|
||||
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
print(f"Deleted file: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
print(f"Deleted directory: {item_path}")
|
||||
for item in os.listdir(next):
|
||||
item_path = os.path.join(next, item)
|
||||
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
|
||||
shutil.copy(item_path, os.path.join(drop, item))
|
||||
print(f"Copied file: {item_path}")
|
||||
|
||||
|
||||
def kontrola_rc(rc, connection):
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac", (rc,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def kontrola_struktury(souborname, connection):
|
||||
if souborname.endswith('.pdf'):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
vpohode = True
|
||||
if match and len(match.groups()) == 5:
|
||||
datum = match.group(2)
|
||||
try:
|
||||
datetime.datetime.strptime(datum, "%Y-%m-%d").date()
|
||||
except:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
|
||||
row = cur.fetchone()[0]
|
||||
if row != 1:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
return vpohode
|
||||
|
||||
|
||||
def vrat_info_o_souboru(souborname, connection):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
rc = match.group(1)
|
||||
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
|
||||
jmeno = match.group(3)
|
||||
prvnizavorka = match.group(4)
|
||||
druhazavorka = match.group(5)
|
||||
cur = connection.cursor()
|
||||
cur.execute("select idpac from kar where rodcis=?", (rc,))
|
||||
idpac = cur.fetchone()[0]
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta, souborname)))
|
||||
return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
|
||||
|
||||
|
||||
def prejmenuj_chybny_soubor(souborname, cesta):
|
||||
if souborname[0] != "♥":
|
||||
soubornovy = "♥" + souborname
|
||||
os.rename(os.path.join(cesta, souborname), os.path.join(cesta, soubornovy))
|
||||
|
||||
|
||||
def _pokus_o_zamek(dekurs_id, vysledek):
|
||||
"""Běží ve vlákně: pokusí se zamknout dekurz přes separátní spojení.
|
||||
Výsledek zapíše do slovníku vysledek: {'ok': True} nebo {'chyba': str}.
|
||||
Pokud vlákno stále běží po uplynutí timeoutu → záznam je zamčený.
|
||||
"""
|
||||
conn_t = None
|
||||
try:
|
||||
conn_t = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur_t = conn_t.cursor()
|
||||
cur_t.execute(
|
||||
"SELECT ID FROM DEKURS WHERE ID = ? FOR UPDATE WITH LOCK",
|
||||
(dekurs_id,)
|
||||
)
|
||||
cur_t.fetchone()
|
||||
conn_t.rollback() # Uvolni zámek – sloužil jen k ověření
|
||||
vysledek['ok'] = True
|
||||
except Exception as e:
|
||||
vysledek['chyba'] = str(e)
|
||||
finally:
|
||||
if conn_t:
|
||||
try:
|
||||
conn_t.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def zkus_zamknout_dnesni_dekurs(conn, idpac, datum_vlozeni, timeout_sec=2):
|
||||
"""Zjistí zda existuje dnešní dekurz a ověří že není zamčený.
|
||||
|
||||
Vrátí:
|
||||
(id, rtf) – dnešní dekurz existuje a není zamčený
|
||||
None – žádný dnešní dekurz (bude se dělat INSERT, zámek není potřeba)
|
||||
|
||||
Vyhodí RuntimeError pokud je záznam zamčený jiným uživatelem (Medicus ho má otevřený).
|
||||
|
||||
Poznámka: NOWAIT transakci fdb neumí spolehlivě nastavit, proto spustíme
|
||||
pokus o zámek ve vlákně s timeoutem. Pokud vlákno do timeout_sec sekund
|
||||
neskončí, záznam je zamčený a přeskočíme celou skupinu.
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
|
||||
# Krok 1: přečti ID, datum a obsah posledního dekurzu (běžný SELECT)
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
print(f" Žádný dekurz pro pacienta IDPAC={idpac}")
|
||||
return None
|
||||
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
|
||||
if dekurs_datum != datum_vlozeni:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový (INSERT)")
|
||||
return None
|
||||
|
||||
# Krok 2: ověř přes vlákno s timeoutem zda záznam není zamčený
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓ – ověřuji zámek (timeout {timeout_sec}s)...")
|
||||
vysledek = {}
|
||||
t = threading.Thread(target=_pokus_o_zamek, args=(dekurs_id, vysledek), daemon=True)
|
||||
t.start()
|
||||
t.join(timeout=timeout_sec)
|
||||
|
||||
if t.is_alive():
|
||||
# Vlákno stále čeká na zámek = záznam drží Medicus
|
||||
raise RuntimeError(f"DEKURZ ID={dekurs_id} je zamčený (Medicus má záznam otevřený)")
|
||||
|
||||
if 'chyba' in vysledek:
|
||||
raise fdb.DatabaseError(vysledek['chyba'])
|
||||
|
||||
print(f" → záznam volný, pokračuji se zápisem")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list):
|
||||
"""Přidá více souborů do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nové indexy začínají od N
|
||||
2. Vloží nové \\pard řádky před uzavírací prázdný řádek sekce
|
||||
3. Přidá nové bookmarky na konec {\\info{\\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
n_files = 0
|
||||
print(f" Počet existujících Files odkazů: {n_files}, přidávám {len(bookmark_list)} nových")
|
||||
|
||||
# 2. Vložit nové \pard řádky před PRILOHY_CLOSING
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pards = ''
|
||||
for i, fname in enumerate(filenameforbookmark_list):
|
||||
idx = n_files + i
|
||||
new_pards += (r'\pard\s10{\*\bkmkstart ' + str(idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + fname
|
||||
+ r'{\*\bkmkend ' + str(idx) + r'}\par' + '\n')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pards + rtf[closing_pos:]
|
||||
|
||||
# 3. Přidat nové bookmarky na konec {\info{\bookmarks ...}}
|
||||
def append_bookmarks(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.extend(bookmark_list)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', append_bookmarks, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží novou sekci příloh na ZAČÁTEK stávajícího dekurzu (sekce tam ještě není)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# Šablona RTF pro nový dekurs
|
||||
RTF_TEMPLATE = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
# ─── Hlavní tělo skriptu ──────────────────────────────────────────────────────
|
||||
|
||||
info = []
|
||||
for soubor in os.listdir(cesta):
|
||||
if os.path.isfile(os.path.join(cesta, soubor)):
|
||||
print(soubor)
|
||||
if kontrola_struktury(soubor, conn):
|
||||
info.append(vrat_info_o_souboru(soubor, conn))
|
||||
else:
|
||||
prejmenuj_chybny_soubor(soubor, cesta)
|
||||
|
||||
info = sorted(info, key=lambda x: (x[0], x[1]))
|
||||
print(info)
|
||||
|
||||
skupiny = {}
|
||||
for row in info:
|
||||
skupiny[row[0]] = []
|
||||
for row in info:
|
||||
skupiny[row[0]].append(row)
|
||||
|
||||
for key in skupiny.keys():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RC: {key}, souborů: {len(skupiny[key])}")
|
||||
|
||||
idpac = skupiny[key][0][1]
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
|
||||
# ── PRE-CHECK: zkus zamknout dnešní dekurz PŘED zpracováním souborů ──────
|
||||
print(f"\n>>> Kontrola zámku dekurzu pro IDPAC={idpac}...")
|
||||
try:
|
||||
existujici = zkus_zamknout_dnesni_dekurs(conn, idpac, datumzapisu)
|
||||
except RuntimeError as e:
|
||||
# Vlákno nepřišlo do timeoutu = záznam drží Medicus
|
||||
print(f"\n!!! DEKURZ ZAMČEN – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
except fdb.DatabaseError as e:
|
||||
chyba = str(e).lower()
|
||||
if 'deadlock' in chyba or 'lock conflict' in chyba or 'update conflict' in chyba:
|
||||
print(f"\n!!! DEKURZ ZAMČEN (DB konflikt) – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
raise # jiná DB chyba – propaguj dál
|
||||
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
bookmark_list = []
|
||||
filenameforbookmark_list = []
|
||||
bookmarks_body = ''
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB + přesunout do zpracovaných ────
|
||||
for row in skupiny[key]:
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=row[1],
|
||||
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
|
||||
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
|
||||
print(f" → FILES.ID = {fileid} ({row[6]})")
|
||||
|
||||
# Přesun souboru do zpracovaných
|
||||
for attempt in range(3):
|
||||
try:
|
||||
dest = os.path.join(cestazpracovana, row[6])
|
||||
if not os.path.exists(dest):
|
||||
shutil.move(os.path.join(cesta, row[6]), dest)
|
||||
else:
|
||||
ts = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
shutil.move(os.path.join(cesta, row[6]),
|
||||
os.path.join(cestazpracovana, row[6][:-4] + " " + ts + ".pdf"))
|
||||
print(" Přesun OK!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < 2:
|
||||
print(" Retrying in 5 seconds...")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(" Max retries reached. Command failed.")
|
||||
|
||||
filenameforbookmark = row[2].strftime('%Y-%m-%d') + ' ' + row[4] + ': ' + row[5]
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
filenameforbookmark_list.append(filenameforbookmark)
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# ── Krok 2: sestavit tělo nové sekce příloh ───────────────────────────────
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 3: rozhodovací logika (3 případy) ────────────────────────────────
|
||||
cur = conn.cursor()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# Případ 1: dnešní dekurz má sekci příloh → přidáme soubory dovnitř
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubory DO existující sekce...")
|
||||
merged_rtf = pridat_do_sekce_prilohy(existing_rtf, bookmark_list, filenameforbookmark_list)
|
||||
else:
|
||||
# Případ 2: dnešní dekurz existuje, ale sekci příloh nemá → prepend
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → vkládám sekci na začátek...")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(skupiny[key]))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# Případ 3: žádný dnešní dekurz → vytvoříme nový
|
||||
print(f"\n>>> Žádný dekurs pro dnešek → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = RTF_TEMPLATE.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(dekursid, 6, 2, 2, idpac, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
print("\n=== HOTOVO ===")
|
||||
conn.close()
|
||||
@@ -0,0 +1,226 @@
|
||||
# s03soubory_01_FINAL.py – dokumentace
|
||||
|
||||
**Finální verze importního skriptu pro vkládání PDF dokumentů do dekurzů Medicusu.**
|
||||
|
||||
Datum finalizace: 2026-04-04
|
||||
Autor: Vladimír Buzalka + Claude (Anthropic)
|
||||
|
||||
---
|
||||
|
||||
## Co skript dělá
|
||||
|
||||
Zpracuje PDF soubory v určené složce (`cesta`) a pro každý soubor:
|
||||
1. Ověří správnost názvu souboru (formát, RC, datum)
|
||||
2. Zkontroluje, zda cílový dekurz není zamčený v Medicusu
|
||||
3. Zapíše soubor do externí databáze souborů (tabulka FILES)
|
||||
4. Přesune soubor do složky zpracovaných
|
||||
5. Vloží odkaz (bookmark) do dekurzu pacienta jako RTF záznam
|
||||
|
||||
---
|
||||
|
||||
## Adresáře
|
||||
|
||||
| Proměnná | Cesta (testovací) | Popis |
|
||||
|---|---|---|
|
||||
| `cesta` | `u:\testimport` | Vstupní složka – sem patří soubory ke zpracování |
|
||||
| `cestazpracovana` | `u:\testimportzpracovana` | Cílová složka – sem se přesouvají zpracované soubory |
|
||||
|
||||
> V produkci tyto cesty nahradit skutečnými složkami (Nextcloud/Dropbox).
|
||||
|
||||
---
|
||||
|
||||
## Formát názvu souboru
|
||||
|
||||
Každý PDF soubor musí mít název ve tvaru:
|
||||
|
||||
```
|
||||
RC YYYY-MM-DD Příjmení, Jméno [typ dokumentu] [poznámka].pdf
|
||||
```
|
||||
|
||||
**Příklad:**
|
||||
```
|
||||
7309208104 2020-10-16 Buzalka, Vladimír [LZ ortopedie] [VAS LS páteře, obstřik].pdf
|
||||
```
|
||||
|
||||
| Část | Popis |
|
||||
|---|---|
|
||||
| `RC` | Rodné číslo pacienta (9 nebo 10 číslic) – musí existovat v tabulce KAR |
|
||||
| `YYYY-MM-DD` | Datum dokumentu |
|
||||
| `Příjmení, Jméno` | Jméno pacienta (jen pro čitelnost, nepoužívá se k vyhledání) |
|
||||
| `[typ dokumentu]` | První závorka – druh nálezu (LZ ortopedie, EKG, Lab. nález…) |
|
||||
| `[poznámka]` | Druhá závorka – krátký popis obsahu (může být prázdná `[]`) |
|
||||
|
||||
**Chybný soubor** (špatný název, RC nenalezeno v DB) je přejmenován přidáním prefixu `♥`:
|
||||
```
|
||||
♥chybny soubor.pdf
|
||||
```
|
||||
Skript ho přeskočí a nechá v složce pro ruční opravu.
|
||||
|
||||
---
|
||||
|
||||
## Databázové tabulky
|
||||
|
||||
| Tabulka | DB | Popis |
|
||||
|---|---|---|
|
||||
| `KAR` | Medicus (`medicus.fdb`) | Kartotéka pacientů – lookup RC → IDPAC |
|
||||
| `DEKURS` | Medicus (`medicus.fdb`) | Dekurzy – čtení a zápis RTF záznamu |
|
||||
| `FILES` | Externí DB (`MEDICUS_FILES_YYYYMM.fdb`) | Binární uložení PDF souborů |
|
||||
|
||||
---
|
||||
|
||||
## Klíčová novinka oproti s03soubory.py – ochrana před zamčeným dekurzem
|
||||
|
||||
### Problém
|
||||
Medicus drží **exkluzivní zámek** (Firebird row lock) na záznamu tabulky DEKURS po celou dobu, kdy má lékařka pacienta otevřeného. Kdyby skript provedl `UPDATE DEKURS` do zamčeného záznamu, přepsal by lékařčiny neuložené změny.
|
||||
|
||||
### Řešení – detekce zámku pomocí vlákna s timeoutem
|
||||
|
||||
Firebird neumí NOWAIT nastavit per-statement v SQL (syntaxe `FOR UPDATE WITH LOCK NOWAIT` není platná). Nastavení NOWAIT je vlastnost transakce, nikoliv dotazu. Knihovna `fdb` navíc toto nastavení spolehlivě nepodporuje.
|
||||
|
||||
**Zvolené řešení:** spuštění pokusu o zámek ve vedlejším vlákně s timeoutem 2 sekundy.
|
||||
|
||||
```
|
||||
hlavní vlákno vedlejší vlákno (_pokus_o_zamek)
|
||||
───────────────── ─────────────────────────────────
|
||||
t.start() ──────► fdb.connect() [nové spojení]
|
||||
t.join(timeout=2s) SELECT ... FOR UPDATE WITH LOCK
|
||||
├── záznam volný → fetchone() → rollback() → konec
|
||||
└── záznam zamčený → čeká (blokuje)...
|
||||
─────────────────
|
||||
po 2 sekundách:
|
||||
t.is_alive()?
|
||||
ANO → záznam zamčený → RuntimeError → přeskoč skupinu
|
||||
NE → záznam volný → pokračuj se zápisem
|
||||
```
|
||||
|
||||
### Důležitý detail – pořadí operací
|
||||
|
||||
**Kontrola zámku probíhá PŘED zápisem do FILES a PŘED přesunem souboru.**
|
||||
Kdyby se pořadí obrátilo, mohlo by dojít k situaci:
|
||||
- soubor zapsán do FILES ✓
|
||||
- soubor přesunut do zpracovaných ✓
|
||||
- dekurz zamčen → UPDATE selže
|
||||
- soubor je pryč ze vstupní složky, ale odkaz v dekurzu chybí
|
||||
|
||||
Správné pořadí:
|
||||
```
|
||||
1. Zkontroluj zámek dekurzu (NOWAIT)
|
||||
└── zamčeno → přeskoč (soubory zůstanou v cesta)
|
||||
2. Zapiš soubory do ext. DB (FILES)
|
||||
3. Přesuň soubory do zpracovaných
|
||||
4. Sestav RTF
|
||||
5. UPDATE nebo INSERT do DEKURS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logika vkládání do dekurzu – 3 případy
|
||||
|
||||
Po úspěšné kontrole zámku skript rozhodne, co s dekurzem udělat:
|
||||
|
||||
```
|
||||
Existuje dnešní dekurz pro pacienta?
|
||||
│
|
||||
├── ANO → obsahuje sekci "Vložené přílohy"?
|
||||
│ ├── ANO → Případ 1: přidá nové soubory DOVNITŘ sekce
|
||||
│ └── NE → Případ 2: vloží celou sekci na ZAČÁTEK dekurzu (prepend)
|
||||
│
|
||||
└── NE → Případ 3: vytvoří NOVÝ dekurz ze šablony RTF_TEMPLATE
|
||||
```
|
||||
|
||||
### Případ 1 – `pridat_do_sekce_prilohy()`
|
||||
|
||||
Dnešní dekurz **existuje a má** sekci „Vložené přílohy".
|
||||
|
||||
- Spočítá kolik odkazů (Files:) už sekce obsahuje → nové indexy bookmarků navazují
|
||||
- Nové `\pard` řádky vloží **před** uzavírací prázdný řádek sekce (`PRILOHY_CLOSING`)
|
||||
- Nové bookmarky přidá na **konec** `{\info{\bookmarks ...}}`
|
||||
- Provede `UPDATE DEKURS SET DEKURS = ? WHERE ID = ?`
|
||||
|
||||
### Případ 2 – `merge_rtf_prepend()`
|
||||
|
||||
Dnešní dekurz **existuje, ale nemá** sekci příloh (lékařka do něj napsala text).
|
||||
|
||||
- Přečísluje existující bookmarky (posunutí o počet nových souborů)
|
||||
- Novou sekci „Vložené přílohy" vloží **na začátek** těla RTF (před `\uc1\pard`)
|
||||
- Nové bookmarky předřadí před existující v `{\info{\bookmarks ...}}`
|
||||
- Lékařčin text zůstane zachován, jen se posune níž
|
||||
- Provede `UPDATE DEKURS SET DEKURS = ? WHERE ID = ?`
|
||||
|
||||
### Případ 3 – nový INSERT
|
||||
|
||||
Pro dnešní datum **neexistuje žádný dekurz**.
|
||||
|
||||
- Vyplní `RTF_TEMPLATE` (bookmarky + tělo sekce příloh)
|
||||
- Provede `INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)`
|
||||
- `iduzi=6` (Vladimír Buzalka), `idprac=2`, `idodd=2`
|
||||
|
||||
---
|
||||
|
||||
## RTF formát dekurzu
|
||||
|
||||
### Struktura bookmarku
|
||||
Každý přiložený soubor je reprezentován jako:
|
||||
1. **Bookmark entry** v `{\info{\bookmarks ...}}`:
|
||||
```
|
||||
"2020-10-16 LZ ortopedie: VAS LS páteře, obstřik","Files:21923",9
|
||||
```
|
||||
- `"popis"` – zobrazený text odkazu
|
||||
- `"Files:ID"` – odkaz na záznam v tabulce FILES (slouží Medicusu k načtení souboru)
|
||||
- `9` – číslo fontu/stylu (od 9, každý další +7)
|
||||
|
||||
2. **Vizuální řádek** v těle RTF:
|
||||
```rtf
|
||||
\pard\s10{\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 2020-10-16 LZ ortopedie: VAS LS páteře, obstřik{\*\bkmkend 0}\par
|
||||
```
|
||||
- `\bkmkstart N` / `\bkmkend N` – index bookmarku (0, 1, 2…)
|
||||
- `\cs32\ul\cf1` – styl „Odkaz" (modrý podtržený text)
|
||||
|
||||
### Konstanty pro detekci sekce příloh (win1250 RTF escape)
|
||||
```python
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:" # "Vložené přílohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par' # uzavírací prázdný řádek
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funkce – přehled
|
||||
|
||||
| Funkce | Popis |
|
||||
|---|---|
|
||||
| `restore_files_for_import(retezec)` | Debug utilita – vrátí soubory z Nextcloudu zpět do Dropboxu. Nepoužívá se v produkci. |
|
||||
| `kontrola_rc(rc, connection)` | Ověří zda RC existuje v KAR, vrátí IDPAC nebo False. |
|
||||
| `kontrola_struktury(souborname, connection)` | Ověří formát názvu souboru a existenci RC v DB. |
|
||||
| `vrat_info_o_souboru(souborname, connection)` | Parsuje název souboru, dohledá IDPAC, vrátí tuple s metadaty. |
|
||||
| `prejmenuj_chybny_soubor(souborname, cesta)` | Přidá prefix `♥` k chybnému souboru. |
|
||||
| `_pokus_o_zamek(dekurs_id, vysledek)` | Interní – běží ve vlákně, zkouší zamknout dekurz. |
|
||||
| `zkus_zamknout_dnesni_dekurs(conn, idpac, datum_vlozeni, timeout_sec=2)` | Zjistí zda dnešní dekurz existuje a není zamčený. Vyhodí RuntimeError pokud je zamčený. |
|
||||
| `ma_sekci_prilohy(rtf)` | Vrátí True pokud RTF obsahuje sekci „Vložené přílohy". |
|
||||
| `pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list)` | Případ 1 – přidá soubory do existující sekce příloh. |
|
||||
| `merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new)` | Případ 2 – vloží sekci příloh na začátek existujícího dekurzu. |
|
||||
|
||||
---
|
||||
|
||||
## Ošetření chyb
|
||||
|
||||
| Situace | Chování |
|
||||
|---|---|
|
||||
| Soubor má chybný název | Přejmenován na `♥soubor.pdf`, přeskočen |
|
||||
| RC nenalezeno v KAR | Přejmenován na `♥soubor.pdf`, přeskočen |
|
||||
| Dekurz zamčený (timeout vlákna) | Skupina přeskočena, soubory zůstanou v `cesta` |
|
||||
| DB konflikt při zamykání (-913 deadlock) | Skupina přeskočena, soubory zůstanou v `cesta` |
|
||||
| Přesun souboru selže | 3 pokusy s 5s pauzou, poté varování |
|
||||
| Jiná DB chyba | Výjimka se propaguje, skript havaruje |
|
||||
|
||||
---
|
||||
|
||||
## Vývoj a testování
|
||||
|
||||
| Verze | Soubor | Co přibilo |
|
||||
|---|---|---|
|
||||
| Prototyp | `test_import_FINAL.py` | Ruční zadání IDPAC a DATUM, ověření RTF logiky |
|
||||
| v1 | `s03soubory.py` | Automatický parsing RC z názvu, dávkování po skupinách |
|
||||
| **v1 FINAL** | `s03soubory_01_FINAL.py` | Ochrana před zamčeným dekurzem (threading + timeout) |
|
||||
|
||||
### Jak byl objeven problém se zámky
|
||||
Experimentem bylo ověřeno, že Medicus drží Firebird row lock na záznamu DEKURS po celou dobu, kdy má lékařka pacienta otevřeného (`SELECT FIRST 1 ... FOR UPDATE WITH LOCK` z Pythonu čekalo dokud lékařka neuložila). NOWAIT nelze nastavit přes SQL syntaxi ani spolehlivě přes fdb TPB bajty, proto bylo zvoleno řešení přes vlákno s timeoutem.
|
||||
@@ -0,0 +1,108 @@
|
||||
"""s04_presun_externi_db.py – Přesun externích DB souborů z u:\ do u:\externi\
|
||||
|
||||
Spustit na Windows s ZAVŘENÝM Medicusem!
|
||||
|
||||
Co dělá:
|
||||
1. Připojí se k hlavní medicus.fdb
|
||||
2. Načte všechny záznamy z EXTERNI_DB
|
||||
3. Pro každý záznam zkopíruje FDB soubor z původní lokace do u:\externi\
|
||||
4. Aktualizuje EXTERNI_DB.PATH na novou lokaci
|
||||
5. Vytiskne přehled výsledků
|
||||
|
||||
Po spuštění zkus v Medicusu otevřít přílohu pacienta – mělo by to fungovat.
|
||||
Pokud ne, zálohuj si databázi a spusť znovu (skript je idempotentní).
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import fdb
|
||||
|
||||
# ── Konfigurace ──────────────────────────────────────────────────────────────
|
||||
MAIN_DB = r'c:\medicus 3\data\medicus.fdb'
|
||||
CÍL_SLOŽKA = r'u:\externi'
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
os.makedirs(CÍL_SLOŽKA, exist_ok=True)
|
||||
print(f"Cílová složka: {CÍL_SLOŽKA}")
|
||||
print()
|
||||
|
||||
print("Připojuji se k hlavní DB...")
|
||||
conn = fdb.connect(
|
||||
dsn=f'localhost:{MAIN_DB}',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Načti všechny záznamy z EXTERNI_DB
|
||||
cur.execute("SELECT DBNAME, SERVER, PATH, HESLO FROM EXTERNI_DB ORDER BY DBNAME")
|
||||
zaznamy = cur.fetchall()
|
||||
print(f"Nalezeno {len(zaznamy)} záznamů v EXTERNI_DB:")
|
||||
print()
|
||||
|
||||
ok = 0
|
||||
preskoceno = 0
|
||||
chyba = 0
|
||||
|
||||
for dbname, server, path_puvodni, heslo in zaznamy:
|
||||
print(f" [{dbname}] {path_puvodni}")
|
||||
|
||||
# Název souboru (jen basename, bez cesty)
|
||||
basename = os.path.basename(path_puvodni)
|
||||
# Normalize: Medicus někdy ukládá s malým .fdb, někdy velkým .FDB
|
||||
path_cil = os.path.join(CÍL_SLOŽKA, basename)
|
||||
|
||||
# Zkontroluj, jestli zdrojový soubor existuje
|
||||
# (zkus obě varianty přípony)
|
||||
path_src = None
|
||||
for kandidat in [path_puvodni,
|
||||
path_puvodni.replace('.fdb', '.FDB'),
|
||||
path_puvodni.replace('.FDB', '.fdb')]:
|
||||
if os.path.isfile(kandidat):
|
||||
path_src = kandidat
|
||||
break
|
||||
|
||||
if path_src is None:
|
||||
print(f" ⚠ ZDROJOVÝ SOUBOR NENALEZEN: {path_puvodni} – přeskakuji")
|
||||
chyba += 1
|
||||
continue
|
||||
|
||||
# Pokud je soubor už v cíli, přeskoč kopírování
|
||||
if os.path.isfile(path_cil):
|
||||
velikost = os.path.getsize(path_cil)
|
||||
print(f" → Soubor již existuje v cíli ({velikost:,} B), přeskakuji kopírování")
|
||||
else:
|
||||
velikost = os.path.getsize(path_src)
|
||||
print(f" → Kopíruji ({velikost:,} B)...", end=' ', flush=True)
|
||||
shutil.copy2(path_src, path_cil)
|
||||
print("OK")
|
||||
|
||||
# Aktualizuj PATH v EXTERNI_DB pokud se liší
|
||||
if path_puvodni != path_cil:
|
||||
cur.execute(
|
||||
"UPDATE EXTERNI_DB SET PATH = ? WHERE DBNAME = ?",
|
||||
(path_cil, dbname)
|
||||
)
|
||||
print(f" → EXTERNI_DB.PATH aktualizováno: {path_cil}")
|
||||
else:
|
||||
print(f" → PATH již ukazuje na cíl, není třeba měnit")
|
||||
preskoceno += 1
|
||||
|
||||
ok += 1
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"Hotovo! Zpracováno: {ok}, přeskočeno: {preskoceno}, chyb: {chyba}")
|
||||
print()
|
||||
if chyba > 0:
|
||||
print("⚠ Některé soubory nebyly nalezeny – viz výpis výše.")
|
||||
print(" Pokud jsou to staré DB které již neexistují, nevadí to.")
|
||||
print("Nyní spusť Medicus a zkus otevřít přílohu pacienta.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,37 @@
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
print("=== NEJSTARŠÍ záznamy ===")
|
||||
cur.execute("""
|
||||
SELECT FIRST 5 ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE DEKURS IS NOT NULL
|
||||
ORDER BY DATUM ASC
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
text = row[2].read() if hasattr(row[2], 'read') else row[2]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
print(f"\n--- ID={row[0]} DATUM={row[1]} ---")
|
||||
print(repr(text[:300]))
|
||||
|
||||
print("\n\n=== NEJNOVĚJŠÍ záznamy ===")
|
||||
cur.execute("""
|
||||
SELECT FIRST 5 ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE DEKURS IS NOT NULL
|
||||
ORDER BY DATUM DESC
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
text = row[2].read() if hasattr(row[2], 'read') else row[2]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
print(f"\n--- ID={row[0]} DATUM={row[1]} ---")
|
||||
print(repr(text[:300]))
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,25 @@
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE DEKURS IS NOT NULL
|
||||
ORDER BY DATUM DESC
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
text = row[2].read() if hasattr(row[2], 'read') else row[2]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
|
||||
print(f"ID={row[0]} DATUM={row[1]}")
|
||||
print(f"Délka: {len(text)} znaků")
|
||||
print("\n--- CELÝ TEXT ---")
|
||||
print(text)
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,35 @@
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Načti všechny záznamy pacienta a zjisti délku v Pythonu
|
||||
cur.execute("""
|
||||
SELECT ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE IDPAC = 9742 AND DEKURS IS NOT NULL
|
||||
ORDER BY DATUM DESC
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
|
||||
def read_blob(val):
|
||||
text = val.read() if hasattr(val, 'read') else val
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
return text
|
||||
|
||||
# Seřaď podle délky
|
||||
data = [(r[0], r[1], read_blob(r[2])) for r in rows]
|
||||
data.sort(key=lambda x: len(x[2]), reverse=True)
|
||||
|
||||
print("Top 10 nejdelších záznamů:")
|
||||
for d in data[:10]:
|
||||
print(f" ID={d[0]} datum={d[1]} délka={len(d[2])}")
|
||||
|
||||
print("\n--- Nejdelší záznam (celý RTF) ---")
|
||||
print(data[0][2])
|
||||
|
||||
conn.close()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
"""Test připojení k externí DB a prozkoumání její struktury - spusť na Windows"""
|
||||
import fdb, os, glob
|
||||
|
||||
# Najde první existující externí FDB soubor
|
||||
soubory = glob.glob(r'u:\MEDICUS_FILES_*.fdb')
|
||||
if not soubory:
|
||||
soubory = glob.glob(r'u:\externi\MEDICUS_FILES_*.fdb')
|
||||
|
||||
if not soubory:
|
||||
print("Žádné externí FDB soubory nenalezeny!")
|
||||
exit()
|
||||
|
||||
soubor = sorted(soubory)[-1] # vezme nejnovější
|
||||
print(f"Zkouším: {soubor}")
|
||||
|
||||
# Test 1: masterkey
|
||||
try:
|
||||
conn = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='masterkey', charset='win1250')
|
||||
print("✓ Připojení s masterkey FUNGUJE!")
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
# Jaké tabulky jsou v externí DB?
|
||||
cur.execute("SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG=0 AND RDB$VIEW_BLR IS NULL")
|
||||
tabulky = [r[0].strip() for r in cur.fetchall()]
|
||||
print(f"Tabulky v externí DB: {tabulky}")
|
||||
|
||||
# Kolik záznamů v FILES?
|
||||
if 'FILES' in tabulky:
|
||||
cur.execute("SELECT COUNT(*), MIN(ID), MAX(ID) FROM FILES")
|
||||
row = cur.fetchone()
|
||||
print(f"FILES: {row[0]} záznamů, ID od {row[1]} do {row[2]}")
|
||||
|
||||
cur.execute("SELECT ID, IDPAC, FILENAME, DATUM, OCTET_LENGTH(BODY) FROM FILES ORDER BY ID ROWS 3")
|
||||
for r in cur.fetchall():
|
||||
print(f" ID={r[0]} IDPAC={r[1]} FILE={r[2]} DATUM={r[3]} BODY={r[4]}B")
|
||||
|
||||
# Zjistit generátor
|
||||
cur.execute("SELECT RDB$GENERATOR_NAME, RDB$GENERATOR_ID FROM RDB$GENERATORS WHERE RDB$SYSTEM_FLAG=0")
|
||||
for r in cur.fetchall():
|
||||
print(f"Generátor: {r[0].strip()} = {r[1]}")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"✗ masterkey NEFUNGUJE: {e}")
|
||||
|
||||
# Test s jiným heslem - zkus prázdné
|
||||
try:
|
||||
conn = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='', charset='win1250')
|
||||
print("✓ Připojení s prázdným heslem FUNGUJE!")
|
||||
conn.close()
|
||||
except Exception as e2:
|
||||
print(f"✗ Prázdné heslo také nefunguje: {e2}")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Prozkoumá strukturu tabulky DATA v externí DB - spusť na Windows"""
|
||||
import fdb, glob
|
||||
|
||||
soubory = glob.glob(r'u:\MEDICUS_FILES_*.fdb') + glob.glob(r'u:\MEDICUS_FILES_*.FDB')
|
||||
if not soubory:
|
||||
soubory = glob.glob(r'u:\externi\MEDICUS_FILES_*.fdb') + glob.glob(r'u:\externi\MEDICUS_FILES_*.FDB')
|
||||
|
||||
soubor = sorted(soubory)[-1]
|
||||
print(f"Soubor: {soubor}\n")
|
||||
|
||||
conn = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur = conn.cursor()
|
||||
|
||||
# Sloupce tabulky DATA
|
||||
print("=== Sloupce tabulky DATA ===")
|
||||
cur.execute("""
|
||||
SELECT r.RDB$FIELD_NAME, f.RDB$FIELD_TYPE, f.RDB$FIELD_LENGTH, f.RDB$SEGMENT_LENGTH
|
||||
FROM RDB$RELATION_FIELDS r
|
||||
JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = r.RDB$FIELD_SOURCE
|
||||
WHERE r.RDB$RELATION_NAME = 'DATA'
|
||||
ORDER BY r.RDB$FIELD_POSITION
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
typ = {7:'SMALLINT',8:'INTEGER',12:'DATE',13:'TIME',14:'CHAR',16:'INT64',35:'TIMESTAMP',37:'VARCHAR',261:'BLOB'}.get(row[1], str(row[1]))
|
||||
print(f" {row[0].strip():30} {typ} len={row[2]}")
|
||||
|
||||
# Generátory
|
||||
print("\n=== Generátory ===")
|
||||
cur.execute("SELECT RDB$GENERATOR_NAME, RDB$GENERATOR_ID FROM RDB$GENERATORS WHERE RDB$SYSTEM_FLAG=0")
|
||||
for row in cur.fetchall():
|
||||
print(f" {row[0].strip()} = {row[1]}")
|
||||
|
||||
# Počet záznamů a ukázka
|
||||
print("\n=== Počet záznamů ===")
|
||||
cur.execute("SELECT COUNT(*) FROM DATA")
|
||||
print(f" {cur.fetchone()[0]} záznamů")
|
||||
|
||||
print("\n=== Ukázka 3 záznamů (bez BLOB) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, OCTET_LENGTH(BODY) as VELIKOST
|
||||
FROM DATA ORDER BY ID ROWS 3
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
print(f" ID={row[0]} BODY={row[1]}B")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Zjistí propojení mezi hlavní DB a externí DB - spusť na Windows"""
|
||||
import fdb, glob
|
||||
|
||||
# Hlavní DB
|
||||
conn_main = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur = conn_main.cursor()
|
||||
|
||||
# Najít exportované záznamy v FILES (BODY není velký BLOB)
|
||||
print("=== FILES záznamy po exportu (malý BODY = reference na externí DB) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, IDPAC, FILENAME, DATUM, EXT_ID, DOCID,
|
||||
OCTET_LENGTH(BODY) as BODY_LEN,
|
||||
CAST(BODY AS VARCHAR(100)) as BODY_TEXT
|
||||
FROM FILES
|
||||
WHERE OCTET_LENGTH(BODY) < 100
|
||||
ORDER BY ID DESC ROWS 5
|
||||
""")
|
||||
for r in cur.fetchall():
|
||||
print(f" ID={r[0]} IDPAC={r[1]} FILE={r[2]}")
|
||||
print(f" DATUM={r[3]} EXT_ID={r[4]} DOCID={r[5]}")
|
||||
print(f" BODY_LEN={r[6]} BODY_TEXT='{r[7]}'")
|
||||
|
||||
# Ukázka záznamu s plným BODY (ještě ne exportovaný)
|
||||
print("\n=== FILES záznamy PŘED exportem (velký BODY = binary data) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, FILENAME, DATUM, EXT_ID, OCTET_LENGTH(BODY) as BODY_LEN
|
||||
FROM FILES
|
||||
WHERE OCTET_LENGTH(BODY) > 1000
|
||||
ORDER BY ID DESC ROWS 3
|
||||
""")
|
||||
for r in cur.fetchall():
|
||||
print(f" ID={r[0]} FILE={r[1]} DATUM={r[2]} EXT_ID={r[3]} BODY={r[4]}B")
|
||||
|
||||
conn_main.close()
|
||||
|
||||
# Porovnat s externí DB
|
||||
print("\n=== DATA záznamy v externí DB ===")
|
||||
soubory = glob.glob(r'u:\MEDICUS_FILES_*.fdb') + glob.glob(r'u:\MEDICUS_FILES_*.FDB')
|
||||
soubor = sorted(soubory)[0] # vezmeme nejstarší
|
||||
print(f"Soubor: {soubor}")
|
||||
conn_ext = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur_ext = conn_ext.cursor()
|
||||
cur_ext.execute("SELECT UID, DATASIZE, CHUNK FROM DATA ORDER BY CHUNK ROWS 5")
|
||||
for r in cur_ext.fetchall():
|
||||
print(f" UID='{r[0]}' DATASIZE={r[1]} CHUNK={r[2]}")
|
||||
conn_ext.close()
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Čte přesné bajty BODY z exportovaného záznamu FILES - spusť na Windows"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur = conn.cursor()
|
||||
|
||||
# Vezmi jeden exportovaný záznam
|
||||
cur.execute("SELECT ID, BODY FROM FILES WHERE ID = 10487")
|
||||
row = cur.fetchone()
|
||||
fileid = row[0]
|
||||
body_bytes = row[1] # fdb vrací BLOB jako bytes přímo
|
||||
|
||||
print(f"FILE ID: {fileid}")
|
||||
print(f"BODY délka: {len(body_bytes)} bajtů")
|
||||
print(f"BODY hex: {body_bytes.hex()}")
|
||||
print(f"BODY repr: {repr(body_bytes)}")
|
||||
print(f"BODY jako string (latin1): {body_bytes.decode('latin1')}")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,108 @@
|
||||
"""test_import_3files.py – jednorázový test: 3 soubory jednoho pacienta → 1 dekurs s klikacími odkazy
|
||||
Testuje opravenou RTF logiku (cs32, Přílohy: inline, poradi++)
|
||||
Spustit na Windows.
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import fdb
|
||||
import funkce
|
||||
import funkce_ext
|
||||
|
||||
CESTA = r'u:\\'
|
||||
IDPAC = 9742 # Buzalka Vladimír, RC 7309208104
|
||||
|
||||
SOUBORY = [
|
||||
{
|
||||
'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy1].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude copy1',
|
||||
'datum': datetime.date(2026, 3, 18),
|
||||
},
|
||||
{
|
||||
'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy2].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude copy2',
|
||||
'datum': datetime.date(2026, 3, 18),
|
||||
},
|
||||
{
|
||||
'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude',
|
||||
'datum': datetime.date(2026, 3, 18),
|
||||
},
|
||||
]
|
||||
|
||||
# ── Připojení ─────────────────────────────────────────────────────────────────
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250'
|
||||
)
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB ─────────────────────────────────────
|
||||
bookmark = ''
|
||||
bookmarks = ''
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
|
||||
for s in SOUBORY:
|
||||
cesta_souboru = os.path.join(CESTA, s['souborname'])
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getmtime(cesta_souboru))
|
||||
|
||||
print(f"\n>>> Zpracovávám: {s['souborname']}")
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection = conn,
|
||||
idpac = IDPAC,
|
||||
cesta = CESTA,
|
||||
souborname = s['souborname'],
|
||||
prvnizavorka = s['prvnizavorka'],
|
||||
soubordate = s['datum'],
|
||||
souborfiledate = datumsouboru,
|
||||
poznamka = s['druhazavorka'],
|
||||
)
|
||||
print(f" → FILES.ID = {fileid}")
|
||||
|
||||
# {\info{\bookmarks ...}} sekce
|
||||
filenameforbookmark = s['datum'].strftime('%Y-%m-%d') + ' ' + s['prvnizavorka'] + ': ' + s['druhazavorka']
|
||||
bookmark += '"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo) + ';'
|
||||
cislo += 7
|
||||
|
||||
# tělo RTF – klikatelné záložky (cs32, všechny stejný formát)
|
||||
bookmarks += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark +
|
||||
r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
bookmark = bookmark[:-1] # odstranit poslední ;
|
||||
|
||||
# ── Krok 2: sestavit RTF ──────────────────────────────────────────────────────
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
rtf = rtf.replace('BOOKMARKNAMES', bookmark)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', bookmarks)
|
||||
|
||||
print('\n=== Výsledný RTF ===')
|
||||
print(rtf)
|
||||
|
||||
# ── Krok 3: zapsat dekurs ─────────────────────────────────────────────────────
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?,?,?,?,?,?,?,?)",
|
||||
(dekursid, 6, 2, 2, IDPAC, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print(f'\n=== HOTOVO ===')
|
||||
print(f'DEKURS.ID = {dekursid}')
|
||||
print('Otevři Medicus → karta Buzalka Vladimír → najdi dnešní záznam → klikej na odkazy!')
|
||||
@@ -0,0 +1,238 @@
|
||||
"""test_import_single.py – vloží 1 soubor do dekurzu s rozšířenou logikou:
|
||||
|
||||
1. Poslední dekurs pacienta je z dnešního dne A má sekci 'Vložené přílohy'
|
||||
→ soubor se přidá DO té sekce (ne nová sekce)
|
||||
2. Poslední dekurs je z dnešního dne, ale sekci 'Vložené přílohy' nemá
|
||||
→ prepend nové sekce na začátek
|
||||
3. Poslední dekurs je z jiného dne / neexistuje
|
||||
→ nový dekurs pro dnešek
|
||||
|
||||
Spustit na Windows.
|
||||
"""
|
||||
import datetime, os, re, fdb
|
||||
import funkce, funkce_ext
|
||||
|
||||
CESTA = r'u:\\'
|
||||
IDPAC = 9742
|
||||
DATUM = datetime.date(2026, 3, 18)
|
||||
|
||||
SOUBORY = [
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude - zařazení].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude - zařazení',
|
||||
'datum': DATUM},
|
||||
]
|
||||
|
||||
# Vzor pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni):
|
||||
"""Vrátí (id, rtf) posledního dekurzu pacienta pokud je z dnešního dne."""
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
if dekurs_datum == datum_vlozeni:
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
else:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový")
|
||||
return None
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, new_bkm_entry, filenameforbookmark):
|
||||
"""Přidá soubor do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nový index = N
|
||||
2. Posune bkmkstart/bkmkend >= N o +1 (uvolní místo pro nový)
|
||||
3. Vloží nový \pard před uzavírací prázdný řádek sekce
|
||||
4. Vloží bookmark na pozici N do {\info{\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů v bookmarks listu
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
bkm_entries = []
|
||||
n_files = 0
|
||||
|
||||
new_idx = n_files
|
||||
print(f" Počet existujících Files odkazů: {n_files} → nový bkmkstart={new_idx}")
|
||||
|
||||
# 2. Posunout bkmkstart/bkmkend >= n_files o +1
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + (
|
||||
str(int(m.group(1)) + 1) if int(m.group(1)) >= n_files
|
||||
else m.group(1)),
|
||||
rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + (
|
||||
str(int(m.group(1)) + 1) if int(m.group(1)) >= n_files
|
||||
else m.group(1)),
|
||||
rtf)
|
||||
|
||||
# 3. Najít uzavírající prázdný řádek sekce (první výskyt po hlavičce)
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pard = (r'\pard\s10{\*\bkmkstart ' + str(new_idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(new_idx) + r'}\par')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pard + '\n' + rtf[closing_pos:]
|
||||
|
||||
# 4. Vložit bookmark na pozici n_files do {\info{\bookmarks}}
|
||||
def insert_bookmark(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.insert(n_files, new_bkm_entry)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', insert_bookmark, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží nový obsah na ZAČÁTEK stávajícího dekurzu (žádná existující sekce)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250')
|
||||
|
||||
# ── Krok 1: vložit soubory do ext DB ─────────────────────────────────────────
|
||||
bookmark_list = []
|
||||
bookmarks_body = ''
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
|
||||
for s in SOUBORY:
|
||||
cesta_souboru = os.path.join(CESTA, s['souborname'])
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getmtime(cesta_souboru))
|
||||
|
||||
print(f"\n>>> Zpracovávám: {s['souborname']}")
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=IDPAC,
|
||||
cesta=CESTA, souborname=s['souborname'],
|
||||
prvnizavorka=s['prvnizavorka'],
|
||||
soubordate=s['datum'], souborfiledate=datumsouboru,
|
||||
poznamka=s['druhazavorka'],
|
||||
)
|
||||
print(f" → FILES.ID = {fileid}")
|
||||
|
||||
filenameforbookmark = (s['datum'].strftime('%Y-%m-%d') + ' '
|
||||
+ s['prvnizavorka'] + ': ' + s['druhazavorka'])
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 2: rozhodovací logika ────────────────────────────────────────────────
|
||||
print(f"\n>>> Hledám poslední dekurs pro IDPAC={IDPAC}...")
|
||||
existujici = najdi_posledni_dekurs_dnes(conn, IDPAC, DATUM)
|
||||
cur = conn.cursor()
|
||||
now = datetime.datetime.now()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# ── Případ 1: dnešní dekurs s existující sekcí → přidáme do ní ──────
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubor DO existující sekce...")
|
||||
filenameforbookmark = (SOUBORY[0]['datum'].strftime('%Y-%m-%d') + ' '
|
||||
+ SOUBORY[0]['prvnizavorka'] + ': ' + SOUBORY[0]['druhazavorka'])
|
||||
merged_rtf = pridat_do_sekce_prilohy(
|
||||
existing_rtf,
|
||||
bookmark_list[0],
|
||||
filenameforbookmark
|
||||
)
|
||||
else:
|
||||
# ── Případ 2: dnešní dekurs bez sekce → prepend ───────────────────────
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → prepend nové sekce")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(SOUBORY))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# ── Případ 3: žádný dnešní dekurs → nový ─────────────────────────────────
|
||||
print(f"\n>>> Žádný dekurs pro {DATUM} → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
rtf = rtf.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?,?,?,?,?,?,?,?)",
|
||||
(dekursid, 6, 2, 2, IDPAC, now.date(), now.time(), rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
conn.close()
|
||||
print("\n=== HOTOVO ===")
|
||||
print("Otevři Medicus → karta Buzalka → zkontroluj dekurs!")
|
||||
@@ -0,0 +1,156 @@
|
||||
# test_import_FINAL.py – detailní dokumentace
|
||||
|
||||
## Co skript dělá
|
||||
|
||||
Importuje PDF soubory (lékařské zprávy) do Medicus DB. Konkrétně:
|
||||
|
||||
1. Uloží fyzický soubor do **externí Firebird DB** (tabulka FILES)
|
||||
2. Vloží nebo aktualizuje **dekurs pacienta** (tabulka DEKURS) s klikacím RTF odkazem na soubor
|
||||
|
||||
---
|
||||
|
||||
## Vstupní data (konfigurace nahoře)
|
||||
|
||||
```python
|
||||
CESTA = r'u:\\' # adresář se zdrojovými PDF soubory
|
||||
IDPAC = 9742 # ID pacienta v DB
|
||||
DATUM = datetime.date(2026, 3, 18) # datum zprávy (ne dnešek!)
|
||||
|
||||
SOUBORY = [
|
||||
{
|
||||
'souborname': 'název souboru.pdf',
|
||||
'prvnizavorka': 'typ zprávy', # např. "vyšetření"
|
||||
'druhazavorka': 'poznámka', # volný text
|
||||
'datum': DATUM,
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
Pozor: `DATUM` je datum zprávy (ne dnešek). Podle tohoto data se hledá existující dekurs.
|
||||
|
||||
---
|
||||
|
||||
## Rozhodovací logika – 3 scénáře
|
||||
|
||||
```
|
||||
Poslední dekurs pacienta
|
||||
│
|
||||
├─ z JINÉHO dne / neexistuje
|
||||
│ └─→ SCÉNÁŘ 3: vytvoří nový dekurs
|
||||
│
|
||||
└─ z DNEŠNÍHO dne (= DATUM)
|
||||
│
|
||||
├─ MÁ sekci "Vložené přílohy"
|
||||
│ └─→ SCÉNÁŘ 1: přidá odkaz DO existující sekce
|
||||
│
|
||||
└─ NEMÁ sekci "Vložené přílohy"
|
||||
└─→ SCÉNÁŘ 2: prepend nové sekce na začátek
|
||||
```
|
||||
|
||||
Klíčová funkce pro detekci: `ma_sekci_prilohy(rtf)` – hledá RTF string `Vlo\'9een\'e9 p\'f8\'edlohy:` (= „Vložené přílohy:" zakódováno win1250).
|
||||
|
||||
---
|
||||
|
||||
## Krok 1 – uložení souboru do ext DB
|
||||
|
||||
Volá `funkce_ext.zapis_file_ext(...)` pro každý soubor. Vrátí `fileid` (ID záznamu v tabulce FILES).
|
||||
|
||||
Z každého souboru se postaví:
|
||||
- **bookmark entry** pro `{\info{\bookmarks ...}}` blok RTF:
|
||||
`"2026-03-18 vyšetření: poznámka","Files:1234",9`
|
||||
- **RTF pard** (klikací odkaz) pro tělo dekurzu:
|
||||
`\pard\s10{\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 2026-03-18 vyšetření: poznámka{\*\bkmkend 0}\par`
|
||||
|
||||
Číslo `cislo` začíná na 9 a roste po 7 (interní Medicus konvence). Index `poradi` (bkmkstart) začíná na 0 a roste po 1.
|
||||
|
||||
---
|
||||
|
||||
## Krok 2 – práce s dekurzem
|
||||
|
||||
### Scénář 1: přidání DO existující sekce (`pridat_do_sekce_prilohy`)
|
||||
|
||||
Situace: dnešní dekurs již má blok „Vložené přílohy" s nějakými odkazy.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet existujících `Files:` odkazů v `{\info{\bookmarks}}` → to je index nového (`new_idx`)
|
||||
2. Posune všechny `\bkmkstart N` / `\bkmkend N` kde `N >= new_idx` o +1 (uvolní místo)
|
||||
3. Vloží nový `\pard` řádek **před** uzavírací `\pard\s10\plain\cs15\f0\fs20 \par` sekce
|
||||
4. Vloží nový bookmark na pozici `new_idx` v `{\info{\bookmarks}}`
|
||||
|
||||
Výsledek: soubor se přidá na konec existujícího seznamu příloh, indexy zůstanou konzistentní.
|
||||
|
||||
### Scénář 2: prepend nové sekce (`merge_rtf_prepend`)
|
||||
|
||||
Situace: dnešní dekurs existuje, ale ještě nemá blok příloh.
|
||||
|
||||
Postup:
|
||||
1. Posune všechny existující `\bkmkstart N` / `\bkmkend N` o +n_new (počet nových souborů)
|
||||
2. Přidá nové bookmarky **na začátek** `{\info{\bookmarks}}` bloku
|
||||
- Pokud `{\info{\bookmarks}}` neexistuje, vloží ho za `\deflang1029`
|
||||
3. Vloží nové tělo (záhlaví „Vložené přílohy:" + řádky s odkazy) **před** první `\uc1\pard` těla stávajícího dekurzu
|
||||
|
||||
Výsledek: sekce příloh je viditelně nahoře, stávající text dekurzu zůstane pod ní.
|
||||
|
||||
### Scénář 3: nový dekurs
|
||||
|
||||
Situace: žádný dnešní dekurs neexistuje.
|
||||
|
||||
Sestaví RTF šablonu s:
|
||||
- `{\info{\bookmarks ...}}` – všechny bookmarky
|
||||
- záhlaví „Vložené přílohy:" + klikací řádky
|
||||
- uzavírací prázdný řádek
|
||||
|
||||
Vloží jako nový řádek do tabulky DEKURS s `iduzi=6, idprac=2, idodd=2` (Vladimír Buzalka, ordinace).
|
||||
|
||||
---
|
||||
|
||||
## RTF formát dekurzu
|
||||
|
||||
```rtf
|
||||
{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029
|
||||
{\info{\bookmarks "2026-03-18 vyšetření: poznámka","Files:1234",9}}
|
||||
{\fonttbl{\f0\fnil\fcharset238 Arial;} ...}
|
||||
{\colortbl ;\red0\green0\blue255; ...}
|
||||
{\stylesheet ... {\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 Vložené přílohy:\par
|
||||
\pard\s10{\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 2026-03-18 vyšetření: poznámka{\*\bkmkend 0}\par
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}
|
||||
```
|
||||
|
||||
- **cs20** = kurzíva (záhlaví sekce)
|
||||
- **cs32** = podtržený modrý text (klikací odkaz)
|
||||
- **cs15** = normální text
|
||||
- `\cf1` = modrá barva (první v colortbl)
|
||||
|
||||
---
|
||||
|
||||
## Závislosti
|
||||
|
||||
| Import | Odkud | Co dělá |
|
||||
|--------|-------|---------|
|
||||
| `funkce_ext.zapis_file_ext` | `funkce_ext.py` | Uloží soubor do ext DB (tabulka FILES), vrátí fileid |
|
||||
| `funkce.get_dekurs_id` | `funkce.py` | Vrátí nové ID pro INSERT do tabulky DEKURS |
|
||||
| `fdb` | pip | Připojení k Firebird DB |
|
||||
|
||||
---
|
||||
|
||||
## Tabulky v DB
|
||||
|
||||
| Tabulka | DB | Popis |
|
||||
|---------|----|-------|
|
||||
| `DEKURS` | hlavní (`medicus.fdb`) | Záznamy dekurzu, pole `DEKURS` obsahuje RTF text |
|
||||
| `FILES` | ext DB (`MEDICUS_FILES_*.fdb`) | Binární obsah souborů |
|
||||
|
||||
---
|
||||
|
||||
## Jak spustit
|
||||
|
||||
Skript se spouští jednorázově na Windows stroji s přístupem k Firebird DB. Před spuštěním:
|
||||
1. Upravit `SOUBORY` – seznam PDF souborů ke zpracování
|
||||
2. Zkontrolovat `IDPAC`, `DATUM`, `CESTA`
|
||||
3. Ověřit, že PDF soubory fyzicky existují na `CESTA`
|
||||
|
||||
Po spuštění ověřit v Medicus: karta pacienta → záložka Dekurzy → kliknout na odkaz.
|
||||
@@ -0,0 +1,47 @@
|
||||
"""test_import_april2026.py – jednorázový testovací import do DB202604
|
||||
|
||||
Spustit na Windows (Medicus může být spuštěný, jen ne na kartě tohoto pacienta).
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import fdb
|
||||
import funkce_ext
|
||||
|
||||
SOUBOR_CESTA = r'u:\\'
|
||||
SOUBOR_NAZEV = '7309208104 Buzalka, Vladimír 2026-04-01 [testovaci zprava] [všechno OK].pdf'
|
||||
|
||||
IDPAC = 9742 # Buzalka Vladimír (RC 7309208104)
|
||||
PRVNI_ZAVORKA = 'testovaci zprava'
|
||||
POZNAMKA = 'všechno OK'
|
||||
DATUM_ZPRAVY = datetime.date(2026, 4, 1)
|
||||
DATUM_SOUBORU = datetime.datetime.fromtimestamp(
|
||||
os.path.getmtime(os.path.join(SOUBOR_CESTA, SOUBOR_NAZEV)))
|
||||
|
||||
print(f"Soubor: {os.path.join(SOUBOR_CESTA, SOUBOR_NAZEV)}")
|
||||
print(f"Datum zprávy: {DATUM_ZPRAVY}")
|
||||
print(f"Datum souboru:{DATUM_SOUBORU}")
|
||||
print()
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250'
|
||||
)
|
||||
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn,
|
||||
idpac=IDPAC,
|
||||
cesta=SOUBOR_CESTA,
|
||||
souborname=SOUBOR_NAZEV,
|
||||
prvnizavorka=PRVNI_ZAVORKA,
|
||||
soubordate=DATUM_ZPRAVY,
|
||||
souborfiledate=DATUM_SOUBORU,
|
||||
poznamka=POZNAMKA,
|
||||
)
|
||||
|
||||
conn.close()
|
||||
print()
|
||||
print(f"Hotovo! FILES.ID = {fileid}")
|
||||
print("Otevři Medicus, přejdi na kartu Buzalka Vladimír a zkontroluj záložku Soubory.")
|
||||
@@ -0,0 +1,182 @@
|
||||
"""test_import_merge.py – vloží přílohy do dekurzu daného dne.
|
||||
Pokud dekurs pro daný den existuje → vloží přílohy NAHORU před stávající text.
|
||||
Pokud neexistuje → vytvoří nový dekurs.
|
||||
Spustit na Windows.
|
||||
"""
|
||||
import datetime, os, re, fdb
|
||||
import funkce, funkce_ext
|
||||
|
||||
CESTA = r'u:\\'
|
||||
IDPAC = 9742 # Buzalka Vladimír
|
||||
DATUM = datetime.date(2026, 3, 18)
|
||||
|
||||
SOUBORY = [
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy1].pdf',
|
||||
'prvnizavorka': 'vyšetření', 'druhazavorka': 'ahoj Claude copy1', 'datum': DATUM},
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy2].pdf',
|
||||
'prvnizavorka': 'vyšetření', 'druhazavorka': 'ahoj Claude copy2', 'datum': DATUM},
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude].pdf',
|
||||
'prvnizavorka': 'vyšetření', 'druhazavorka': 'ahoj Claude', 'datum': DATUM},
|
||||
]
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni):
|
||||
"""Najde POSLEDNÍ dekurs pacienta (jakýkoli datum) a vrátí (id, rtf)
|
||||
pouze pokud je ze stejného dne jako datum_vlozeni. Jinak vrátí None.
|
||||
|
||||
Logika: zajímá nás jen poslední zápis – pokud je z dnešního dne,
|
||||
vložíme přílohy do něj. Pokud je starší, zakládáme nový dnešní záznam.
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
if dekurs_datum == datum_vlozeni:
|
||||
print(f" → shoduje se s dneškem ({datum_vlozeni}), budeme mergovat")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
else:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový")
|
||||
return None
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží nový obsah (přílohy) na ZAČÁTEK stávajícího dekurzu.
|
||||
|
||||
existing_rtf – stávající RTF string z DB
|
||||
new_bkm_list – list stringů typu '"popis","Files:123",9'
|
||||
new_body_pards – RTF string: header \par + \pard řádky s odkazy
|
||||
n_new – počet nových bkmkstart indexů (= počet souborů)
|
||||
"""
|
||||
rtf = existing_rtf
|
||||
|
||||
# 1. Posunout stávající bkmkstart/bkmkend indexy o n_new,
|
||||
# aby nedošlo ke kolizi s našimi novými (0, 1, 2 …)
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
# 2. Přidat naše bookmarky na ZAČÁTEK {\info{\bookmarks ...}}
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
# žádný {\info} blok – vložíme za \deflang...
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
# 3. Vložit naše tělo před první \uc1\pard těla stávajícího dekurzu
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = rtf.index(r'\uc1\pard', match.start())
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
else:
|
||||
# fallback – připojit před poslední }
|
||||
rtf = rtf.rstrip()
|
||||
if rtf.endswith('}'):
|
||||
rtf = rtf[:-1] + new_body_pards + '\n}'
|
||||
|
||||
return rtf
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250')
|
||||
|
||||
# ── Krok 1: vložit soubory do ext DB ─────────────────────────────────────────
|
||||
bookmark_list = []
|
||||
bookmarks_body = ''
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
|
||||
for s in SOUBORY:
|
||||
cesta_souboru = os.path.join(CESTA, s['souborname'])
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getmtime(cesta_souboru))
|
||||
|
||||
print(f"\n>>> Zpracovávám: {s['souborname']}")
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=IDPAC,
|
||||
cesta=CESTA, souborname=s['souborname'],
|
||||
prvnizavorka=s['prvnizavorka'],
|
||||
soubordate=s['datum'], souborfiledate=datumsouboru,
|
||||
poznamka=s['druhazavorka'],
|
||||
)
|
||||
print(f" → FILES.ID = {fileid}")
|
||||
|
||||
filenameforbookmark = (s['datum'].strftime('%Y-%m-%d') + ' '
|
||||
+ s['prvnizavorka'] + ': ' + s['druhazavorka'])
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# Tělo přílohy (záhlaví + odkaz řádky + prázdný řádek)
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 2: existující dekurs pro tento den? ──────────────────────────────────
|
||||
print(f"\n>>> Hledám poslední dekurs pro IDPAC={IDPAC}...")
|
||||
existujici = najdi_posledni_dekurs_dnes(conn, IDPAC, DATUM)
|
||||
cur = conn.cursor()
|
||||
now = datetime.datetime.now()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
print(f"\n>>> Nalezen existující dekurs pro {DATUM}: ID={dekurs_id}")
|
||||
print(">>> Vkládám přílohy na začátek...")
|
||||
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(SOUBORY))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
print(f"\n>>> Žádný dekurs pro {DATUM} nenalezen – vytvářím nový...")
|
||||
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
rtf = rtf.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?,?,?,?,?,?,?,?)",
|
||||
(dekursid, 6, 2, 2, IDPAC, now.date(), now.time(), rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
conn.close()
|
||||
print("\n=== HOTOVO ===")
|
||||
print("Otevři Medicus → karta Buzalka → zkontroluj dekurs!")
|
||||
Reference in New Issue
Block a user