From 358438588324c5f505d89c310ae6541a93028221 Mon Sep 17 00:00:00 2001 From: buiaca Date: Wed, 12 Aug 2026 12:58:28 +0200 Subject: [PATCH] Fix: BIC generation + correct input format handling in CSV converter --- Hermes/iban_tool/iban_csv_converter.py | 146 ++++++++++++++++--------- Hermes/iban_tool/iban_tool_windows.zip | Bin 5367 -> 6042 bytes 2 files changed, 96 insertions(+), 50 deletions(-) diff --git a/Hermes/iban_tool/iban_csv_converter.py b/Hermes/iban_tool/iban_csv_converter.py index e720437..2432045 100644 --- a/Hermes/iban_tool/iban_csv_converter.py +++ b/Hermes/iban_tool/iban_csv_converter.py @@ -1,84 +1,131 @@ #!/usr/bin/env python3 """ iban_csv_converter.py - Konvertiert eine CSV mit Kontodaten in eine CSV mit IBAN. - -Erwartetes Eingabeformat (Semikolon-getrennt, 12 Felder): -"DE";"00001";" 763910001835904 0021447250";;;76391000;1835904;;;;; - -Feld 6: Bankleitzahl (BLZ) -Feld 7: Kontonummer - -Ausgabedatei enthaelt dieselbe Struktur plus eine zusaetzliche Spalte fuer die IBAN. +Unterstuetzt das Bundesbank-BL-Z-Format mit variabler Spaltenanzahl. +Fuegt BIC (aus BLZ) und IBAN-Spalten automatisch hinzu. Nutzung: python iban_csv_converter.py eingabe.csv ausgabe.csv - # Beispiel mit deinem virtuellen Umfeld: - /home/alin/iban_tool/venv/bin/python /home/alin/iban_tool/iban_csv_converter.py daten.csv daten_m_iban.csv +Erweitert um: +- Automatische BIC-Erzeugung aus BLZ-Feld +- Flexibles Inputformat (erkennt BLZ/Konto anhand der Werte) +- Behaelt alle Original-Spalten bei und fuegt IBAN/BIC hinzu """ import argparse import csv import sys -from schwifty import IBAN +from schwifty import IBAN, BIC +from schwifty.exceptions import InvalidBankCode -def generate_iban(kontonummer: str, bankleitzahl: str) -> str: +def generate_iban(kontonummer: str, bankleitzahl: str) -> dict: """Erzeugt eine deutsche IBAN aus Kontonummer und Bankleitzahl.""" try: - bankleitzahl = str(int(bankleitzahl)) # fuehrende Nullen entfernen + bankleitzahl_clean = str(int(bankleitzahl)) # fuehrende Nullen entfernen except ValueError: - pass + bankleitzahl_clean = bankleitzahl - iban_obj = IBAN.generate("DE", bank_code=bankleitzahl, account_code=kontonummer) - return str(iban_obj) # ohne Leerzeichen, z.B. DE56760614820004594967 + iban_obj = IBAN.generate("DE", bank_code=bankleitzahl_clean, account_code=kontonummer) + result = { + "iban": str(iban_obj), + "bankleitzahl": bankleitzahl_clean, + "kontonummer": kontonummer, + } + + # Versuche BIC zu finden + try: + bic_obj = BIC.from_bank_code("DE", bankleitzahl_clean) + result["bic"] = str(bic_obj) + except (InvalidBankCode, Exception): + result["bic"] = "" + + return result + + +def find_blz_and_konto(row: list) -> tuple: + """Findet Bankleitzahl (BLZ) und Kontonummer in einer CSV-Zeile. + Priorisiert: 6. Feld = BLZ, 7. Feld = Konto (Index 5 und 6) + """ + # Standardfall: BLZ im 6. Feld (Index 5), Konto im 7. (Index 6) + if len(row) >= 7: + bankleitzahl = row[5].strip() + kontonummer = row[6].strip() + # Pruefe ob wir sinnvolle Werte haben + if bankleitzahl and kontonummer: + return bankleitzahl, kontonummer + + # Fallback: Suche nach 8-stelliger BLZ und Konto-Nummer + for i, field in enumerate(row): + field = field.strip() + # BLZ ist 8-stellig (oder 5-8 mit fuehrenden Nullen) + if (len(field) >= 5 and len(field) <= 8 and field.isdigit() + and i + 1 < len(row) and row[i + 1].strip().isdigit()): + return field, row[i + 1].strip() + + return None, None def main(): parser = argparse.ArgumentParser( - description="Konvertiert eine CSV mit Kontodaten (BLZ im 6. Feld) in eine CSV mit IBAN." - ) - parser.add_argument( - "eingabe_datei", type=str, - help="Pfad zur Eingabedatei (CSV mit Semikolon-Separator)" - ) - parser.add_argument( - "ausgabe_datei", type=str, - help="Pfad zur Ausgabedatei (CSV mit zusaetzlicher IBAN-Spalte)" + description="Konvertiert eine CSV mit Kontodaten (BLZ + Konto) in eine CSV mit IBAN und BIC." ) + parser.add_argument("eingabe_datei", type=str, help="Pfad zur Eingabedatei") + parser.add_argument("ausgabe_datei", type=str, help="Pfad zur Ausgabedatei") args = parser.parse_args() - # Lese die Eingabedatei - with open(args.eingabe_datei, mode="r", encoding="iso-8859-1", newline="") as infile: - reader = csv.reader(infile, delimiter=";", quotechar='"') - rows = list(reader) + # Lese die Eingabedatei (probiere mehrere Kodierungen) + rows = [] + for encoding in ["iso-8859-1", "utf-8", "cp1252"]: + try: + with open(args.eingabe_datei, mode="r", encoding=encoding, newline="") as f: + reader = csv.reader(f, delimiter=";", quotechar='"') + rows = list(reader) + break + except UnicodeDecodeError: + continue + except FileNotFoundError: + print(f"FEHLER: Datei nicht gefunden: {args.eingabe_datei}", file=sys.stderr) + return + + if not rows: + print("FEHLER: Keine Daten gelesen.", file=sys.stderr) + return output_rows = [] - for row in rows: - if len(row) < 7: - print(f"Warnung: Zeile hat weniger als 7 Felder, wird uebersprungen: {row}", file=sys.stderr) + for i, row in enumerate(rows): + # Finde BLZ und Kontonummer + bankleitzahl, kontonummer = find_blz_and_konto(row) + + if not bankleitzahl or not kontonummer: + print(f"Warnung: Zeile {i+1} - konnte keine BLZ/Konto finden: {row[:3]}...", + file=sys.stderr) + # Originalzeile behalten, leere IBAN/BIC einsetzen + new_row = list(row) + while len(new_row) < 9: + new_row.append("") + if len(new_row) > 9: + new_row[8] = "" # BIC + if len(new_row) > 9: + new_row[9] = "" # IBAN + else: + new_row.extend(["", ""]) + output_rows.append(new_row) continue - bankleitzahl = row[5] # 6. Feld (Index 5) - kontonummer = row[6] # 7. Feld (Index 6) - try: - iban = generate_iban(kontonummer.strip(), bankleitzahl.strip()) + data = generate_iban(kontonummer, bankleitzahl) except Exception as e: - print(f"Fehler bei BLZ {bankleitzahl}, Konto {kontonummer}: {e}", file=sys.stderr) - iban = "" + print(f"Fehler in Zeile {i+1} (BLZ={bankleitzahl}, Konto={kontonummer}): {e}", + file=sys.stderr) + data = {"iban": "", "bic": "", "bankleitzahl": bankleitzahl, "kontonummer": kontonummer} - # Erstelle die neue Zeile: Original-Felder + IBAN am Ende - # Behalte das Format bei, fuege IBAN als letzte Spalte hinzu + # Erstelle neue Zeile: Original-Felder + BIC (Index 8) + IBAN (Index 9) new_row = list(row) - # Stelle sicher, dass die Zeile genau 12 Felder hat (oder fuege Leerfelder hinzu) - while len(new_row) < 12: + while len(new_row) < 10: new_row.append("") - # Setze IBAN in das 9. Feld (Index 8) wie im Beispiel ("DE56760614820004594967") - # Falls das Feld bereits existiert, ueberschreibe es - if len(new_row) > 8: - new_row[8] = iban - else: - new_row.append(iban) + new_row[8] = data["bic"] # BIC-Spalte (Index 8) + new_row[9] = data["iban"] # IBAN-Spalte (Index 9) output_rows.append(new_row) # Schreibe die Ausgabedatei @@ -95,9 +142,8 @@ if __name__ == "__main__": main() except Exception as e: print(f"\\nFEHLER: {e}", file=sys.stderr) - print("\\n details:", file=sys.stderr) + print("\\n--- Details:", file=sys.stderr) import traceback traceback.print_exc() finally: - # Fenster offen halten, damit der Benutzer den Fehler oder die Bestaetigung lesen kann input("\\n--- Fertig (Enter zum Schließen) ---") diff --git a/Hermes/iban_tool/iban_tool_windows.zip b/Hermes/iban_tool/iban_tool_windows.zip index 0550c1b93b11b16295c84adccbc61e4704478c80..8c83807303e53fd0fab2c92935a9b8bcd22108cb 100644 GIT binary patch delta 2627 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 MkqZ)*oc|jA1!{lgv;Y7A delta 1948 zcmV;N2V?k}FZU_1q6`jBVhmlyQ-U6k1pol`43n@78-I;fZExE)5dN-TabrGI!W_p* z6Fae|0my??#Bd^Zk)rEICxs8PX^@tm$O6SATj!dtA^y(k4g%u!3^T#w-R1g^16QAYEC zyjhY3nM@{=W3nIsv+3)LWAk?Y`s`wI3ILPI$?@sw?Br}h7K=q^U-$(J^9usw52kat zqWr!@rnj^xA-uZz968f$?y}%bRiO-m%SIWLCVwdg$aO)n)Q~b%SgsImwQTNnBcU!E zdNr0(c6aS^8f&R$>I5GqpQ+*tu{a%7V{j~odyq@)Y4+V}HAWTxTdba_bAfuCdneiIxp6r4$~rWdv2q zb=a0(tp0}BQKzd%XvV>%pR7fS@i6=d=z1+0uAOO6wa7Y^s2j;0a_`>)76nJRL6loG zW_dQ9O()ai)AN(bWO90Tae6VG`36;#@i6j4HjS1L@0vcolzCGj*MA?Z zCEO*LqFPH9z`FY__0dtT$2&#hZGMd7+k)2vO9SFOqy=dKHO(oSrMlT29=lSIz_QYrhVkkJ$7UIy!&K?vh za|LmS$o?5jDvUZUxj}_iq=va#TYt-R0is48njPc4;*Fp(BqNfH0Qp*^jOQ_7N*tb_ zpIsatlM!%iOU5xKBm$~{@r;$&vt1%hEp66nGj=bbTQhU3X9xE+b9Y*^)+x|+CL_^VF5m0-fJvMl(0euX5=4-j-N(O65w%w;b{(! z_`fyvnGn)vdjLzRv^gg&={Yv&dOC;qlI4uk^3e4Xe)i`@1xuddz{3?5_IyvNf@^n? zS(%Z@8qB*xrc_X(-s;_DpkRF4?3{3H4L^7E6>X3F0B#*XWoLV2;(sWM){!qqPMiLg zWXTu`^W*>+Ezi8k1noVyh1qY|?&BNAAD`^^#GA&nu92r9A$!PBZ+(i4+X7tdGwVEp zmZ1^t-6h4GTF;#&dycPZSt?gi<86f!nO1;bnX+fZ$Pd-JkjPdDsQx>^yOe%~^Jg>q za()M~QsED?H-D>pm;+(y7IaZNIlb)wntlzH`#DM7I|@2UZC@;@qS5=B+Dapu zUQ^vm=ogKCD5~z&1T;JU`1tPI{rxhj%#4(U0 zF*lSXA+9#=x)S^gP)h>@6aWAK2mpCv3|(cBU6E)3004OclTZ~Ie~?{E<3JdN@AE4T z6qdHjB>k{{fr>7RTk1-o#ez37N#3@zjq`??Nn-okyz`HV6H`(5ZaAFxoOw$0E(ka#f0OTr%szumjU8$wpdgp9oRPHF~hv;@yEOnS~yIly3 zwId@pPL*Wvm(#)`!h|0mrA~QADkCkfNt2O|O)z*nw`58(O~9Y|e2UBI7&9-Uy`WgV zg)U`6onY7$J|wn$NLZI-+{g8=*`1g)BHC9s_xOnkYsV1he}2rG^vjQK3qK;-?Y7{{yj$| zb9LdH&h|||;%s-;^!S1=p#NRu8^t#VNa>Y~DNJk$S=id14G*WszNxXaN8Kc>(|c5&!@I i0000000000fB{+(lW-Rt4g>%M0Pg?*0P+(60002l0g|Br