diff --git a/40 fio 01.py b/40 fio 01.py new file mode 100644 index 0000000..79962d6 --- /dev/null +++ b/40 fio 01.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Import Fio banka CSV export (UTF-8, ; separated, quoted) +into MySQL database `fio.transactions`. + +- Datum je vždy ve formátu dd.mm.yyyy. +- Přidává diagnostický fingerprint do sloupce `uniq_fp` (indexovaný). +- Zatím NEblokuje duplicity; jen je snadno zobrazí pro ladění. +""" + +import csv +from pathlib import Path +from datetime import datetime +import pymysql +from pymysql.cursors import DictCursor + +# ======== CONFIG ======== +CSV_PATH = Path(r"u:\Dropbox\!!!Days\Downloads Z230\Vyhledane pohyby (1).csv") +TABLE_NAME = "transactions" + +MYSQL_CONFIG = { + "host": "192.168.1.76", + "port": 3307, + "user": "root", + "password": "Vlado9674+", + "database": "fio", + "charset": "utf8mb4", + "cursorclass": DictCursor, + "autocommit": True, +} + +# ---------- helpers ---------- +def clean(s: str): + """Normalize text for consistent comparison (trim + lowercase).""" + if not s: + return None + s = s.strip().lower() + return s or None + +def build_fingerprint(data: dict) -> str: + """Sestaví diagnostický fingerprint z normalizovaných polí.""" + parts = [ + data["datum"].strftime("%Y-%m-%d") if data["datum"] else "", + f"{data['objem']:.2f}" if data["objem"] is not None else "", + data.get("cislo_uctu") or "", + data.get("protiucet") or "", + data.get("kod_banky") or "", + data.get("vs") or "", + (data.get("zprava") or "")[:100], + (data.get("poznamka") or "")[:100], + ] + return "|".join(parts) + +# ======== DB SETUP ======== +def get_mysql_connection(): + return pymysql.connect(**MYSQL_CONFIG) + +def ensure_table_exists(conn): + sql_create = f""" + CREATE TABLE IF NOT EXISTS `{TABLE_NAME}` ( + id INT AUTO_INCREMENT PRIMARY KEY, + datum DATE, + objem DECIMAL(12,2), + mena CHAR(3), + cislo_uctu VARCHAR(40), + protiucet VARCHAR(40), + kod_banky VARCHAR(20), + ks VARCHAR(20), + vs VARCHAR(20), + ss VARCHAR(20), + zprava VARCHAR(500), + poznamka VARCHAR(500), + uniq_fp VARCHAR(512), + imported_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + """ + with conn.cursor() as cur: + cur.execute(sql_create) + + # ✅ přidej sloupec pouze pokud chybí + cur.execute(f"SHOW COLUMNS FROM `{TABLE_NAME}` LIKE 'uniq_fp'") + if not cur.fetchone(): + cur.execute(f"ALTER TABLE `{TABLE_NAME}` ADD COLUMN uniq_fp VARCHAR(512) NULL") + print("🆕 Sloupec uniq_fp přidán.") + + # ✅ vytvoř index pokud ještě není + cur.execute(f"SHOW INDEX FROM `{TABLE_NAME}` WHERE Key_name = 'idx_uniq_fp'") + if not cur.fetchone(): + cur.execute(f"CREATE INDEX idx_uniq_fp ON `{TABLE_NAME}` (uniq_fp)") + print("🆕 Index idx_uniq_fp vytvořen.") + + print("✅ Tabulka a index zkontrolovány.") + +# ======== IMPORT ======== +def import_fio_csv(): + with open(CSV_PATH, "r", encoding="utf-8-sig", newline="") as f: + reader = csv.DictReader(f, delimiter=";", quotechar='"') + rows = list(reader) + + with get_mysql_connection() as conn: + ensure_table_exists(conn) + inserted = 0 + + for row in rows: + # --- DATUM --- + raw_date = (row.get("Datum") or "").strip() + try: + datum = datetime.strptime(raw_date, "%d.%m.%Y").date() if raw_date else None + except ValueError: + datum = None # případně continue, pokud chceš řádek zahodit + + # --- OBJEM --- + objem_str = (row.get("Objem") or "").replace(" ", "").replace(",", ".") + try: + objem = float(objem_str) + except ValueError: + objem = None + + # --- normalizace textů --- + data = { + "datum": datum, + "objem": objem, + "mena": clean(row.get("Měna")), + "cislo_uctu": clean(row.get("Číslo účtu")), + "protiucet": clean(row.get("Protiúčet")), + "kod_banky": clean(row.get("Kód banky")), + "ks": clean(row.get("KS")), + "vs": clean(row.get("VS")), + "ss": clean(row.get("SS")), + "zprava": clean(row.get("Zpráva pro příjemce")), + "poznamka": clean(row.get("Poznámka")), + } + + # --- fingerprint pro ladění duplicit --- + data["uniq_fp"] = build_fingerprint(data) + + # --- INSERT (záměrně bez IGNORE, ať duplicitní řádky opravdu uvidíme) --- + placeholders = ", ".join(["%s"] * len(data)) + sql = f"INSERT INTO `{TABLE_NAME}` ({', '.join(data.keys())}) VALUES ({placeholders})" + with conn.cursor() as cur: + cur.execute(sql, list(data.values())) + inserted += 1 + + print(f"✅ Import hotový: vloženo {inserted} řádků.") + + # ----- rychlý report duplicit podle fingerprintu ----- + with conn.cursor() as cur: + cur.execute(f""" + SELECT uniq_fp, COUNT(*) AS c + FROM `{TABLE_NAME}` + GROUP BY uniq_fp + HAVING c > 1 + ORDER BY c DESC + LIMIT 10; + """) + dups = cur.fetchall() + + if dups: + print("\n⚠️ TOP 10 duplicitních fingerprintů (uniq_fp, count):") + for r in dups: + print(f" {r['uniq_fp']} -> {r['c']}") + print("\n💡 Tip: SELECT * FROM `transactions` WHERE uniq_fp = '...';") + else: + print("✅ Žádné duplicity podle uniq_fp nenalezeny.") + +# ======== MAIN ======== +if __name__ == "__main__": + if not CSV_PATH.exists(): + raise SystemExit(f"❌ Soubor {CSV_PATH} nenalezen.") + import_fio_csv() diff --git a/40 fio 02 diagnostika.py b/40 fio 02 diagnostika.py new file mode 100644 index 0000000..bdbc79a --- /dev/null +++ b/40 fio 02 diagnostika.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Diagnostický test: načti Fio CSV a ověř parsování datumu. +Nenačítá se do MySQL – pouze vypíše výsledek. +""" + +import csv +from datetime import datetime +from pathlib import Path + +# ✅ Tvoje cesta k souboru +CSV_PATH = Path(r"u:\Dropbox\!!!Days\Downloads Z230\Vyhledane pohyby (1).csv") + + +def parse_czech_date(s: str): + """Očistí řetězec a zkusí dd.mm.yyyy.""" + if not s: + return None + s = s.strip().replace("\u00A0", "").replace("\ufeff", "") + try: + return datetime.strptime(s, "%d.%m.%Y").date() + except Exception: + return None + + +def main(): + with open(CSV_PATH, "r", encoding="utf-8-sig", newline="") as f: + reader = csv.DictReader(f, delimiter=";", quotechar='"') + rows = list(reader) + + print(f"Načteno {len(rows)} řádků.\n") + print("Ukázka prvních 10 řádků s hodnotou Datum:\n") + + for i, row in enumerate(rows[:10], start=1): + raw = row.get("Datum") + parsed = parse_czech_date(raw) + print(f"{i:02d}. raw={repr(raw)} -> parsed={parsed}") + + input("\n🔸 Stiskni Enter pro pokračování nebo ukončení... ") + + +if __name__ == "__main__": + if not CSV_PATH.exists(): + raise SystemExit(f"❌ Soubor {CSV_PATH} nenalezen.") + main() diff --git a/40 fio struktura gpc.txt b/40 fio struktura gpc.txt new file mode 100644 index 0000000..546656a --- /dev/null +++ b/40 fio struktura gpc.txt @@ -0,0 +1,45 @@ +Struktura GPC formátu (pro elektronické výpisy) +Výstupní soubor obsahuje záznamy "Data - výpis v Kč", "Data - obratová položka", které mají pevnou délku +130 znaků. Mezi jednotlivými údaji v záznamu není žádný oddělovač. Do pevné délky jsou údaje doplňovány +zleva příslušným počtem znaků nula. +Struktura záznamu "Data - výpis v Kč" : +byty Obsah +1 - 3 "074" = označení typu záznamu "Data - výpis v Kč" +4 - 19 přidělené č. účtu s vodícími nulami +20 - 39 20 alfanumerických znaků zkráceného názvu účtu, doplněných případně mezerami zprava +40 - 45 datum starého zůstatku ve formátu DDMMRR +46 - 59 starý zůstatek v haléřích 14 numerických znaků s vodícími nulami +60 znaménko starého zůstatku, 1 znak "+" či "-" +61 - 74 nový zůstatek v haléřích 14 numerických znaků s vodícími nulami +75 znaménko nového zůstatku, 1 znak "+" či "-" +76 - 89 obraty debet (MD) v haléřích 14 numerických znaků s vodícími nulami +90 znaménko obratů debet (MD), 1 znak "0" či "-" +91 -104obraty kredit (D) v haléřích 14 numerických znaků s vodícími nulami +105 znaménko obratů kredit (D), 1 znak "0" či "-" +106-108 pořadové číslo výpisu +109-114 datum účtování ve formátu DDMMRR +115-128 (vyplněno 14 znaky mezera z důvodu sjednocení délky záznamů) +129-130 ukončovací znaky CR a LF +Struktura záznamu "Data - obratová položka v Kč" : +byty obsah +1 - 3 "075" = označení typu záznamu "Data - obratová položka" +4 - 19 přidělené číslo účtu 16 numerických znaků s vodícími nulami +20 – 35číslo účtu 16 numerických znaků s vodícími nulami (případně v pořadí předčíslí + číslo účtu) +36 – 48číslo dokladu 13 numerických znaků +49 – 60částka v haléřích 12 numerických znaků s vodícími nulami +61 kód účtování vztažený k číslu účtu: +1 = položka debet +2 = položka kredit +4 = storno položky debet +5 = storno položky kredit +62 – 71variabilní symbol 10 numerických znaků s vodícími nulami +72 – 81konstantní symbol 10 numerických znaků s vodícími nulami ve tvaru BBBBKSYM, kde: +BBBB = kód banky, +KSYM = konstantní symbol +82 – 91specifický symbol 10 numerických znaků s vodícími nulami +92 – 97"000000" = valuta +98 –117 20 alfanumerických znaků zkráceného názvu klienta, doplněno případně mezerami zprava +118 "0" +119-122 "0203" = kód měny pro Kč +123-128 datum splatnosti ve formátu DDMMRR +129-130 ukončovací znaky CR a LF \ No newline at end of file