Initial commit

This commit is contained in:
Sebastian Zell 2026-01-16 11:30:15 +01:00
commit 061ab744be
16 changed files with 3313 additions and 0 deletions

1
.abacus.donotdelete Normal file
View File

@ -0,0 +1 @@
gAAAAABpahEZgX1HGkQvRAaZ-87PqNFzt9Wer-07S9N8P2HwoSZB8j5R2rZyKdpdrzdTZ_A5bc2vYKZYVctxF0XyBFA9fad-xNImsD13P5JUdEGEMq64L9k=

131
.gitignore vendored Normal file
View File

@ -0,0 +1,131 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
*.spec
*.manifest
*.exe
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# macOS
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Generierte DBF-Dateien (können sensible Daten enthalten)
*.dbf
buchungen_*.dbf
# Backup-Dateien
*.bak
*.backup
# Eigene sensible Konfigurationsdateien im Hauptverzeichnis
# (Die Beispiele in config/examples/ sind erlaubt)
mandanten_config.json
identitaeten_*.json
projekte_*.json
!config/examples/*.json

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Sebastian Zell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

317
README.md Normal file
View File

@ -0,0 +1,317 @@
# CSV zu DBF Konverter für Hausverwaltung
Ein Python-Tool zur Konvertierung von CSV-Bankauszügen in DBF-Dateien für Hausverwaltungssoftware.
## 📋 Beschreibung
Dieses Programm konvertiert CSV-Exporte von Bankauszügen in das DBF-Format (dBase III), das von vielen älteren Hausverwaltungsprogrammen benötigt wird. Es analysiert automatisch Buchungstexte und ordnet diese den entsprechenden Konten, Gegenkonten und Kostenstellen zu.
### Hauptfunktionen
- **Automatische Kontenzuordnung**: Erkennt Mieter und Kostenkonten anhand konfigurierbarer Suchbegriffe
- **Mehrmandantenfähig**: Unterstützt beliebig viele Mandanten mit eigenen Konfigurationen
- **Projektzuordnung**: Automatische Zuordnung zu Kostenstellen/Projekten
- **Monatliche Trennung**: Erstellt separate DBF-Dateien pro Monat
- **Flexible Filterung**: Auswahl von Monaten und Jahren für die Konvertierung
- **GUI und CLI**: Verfügbar als grafische Oberfläche und Kommandozeilenversion
## 🖥️ Systemanforderungen
### Python-Version
- Python 3.8 oder höher
### Betriebssysteme
- **Linux**: Ubuntu, Debian, Fedora, etc.
- **Windows**: Windows 10/11
### Abhängigkeiten
- `tkinter` (für GUI-Version, meist vorinstalliert)
- Alle anderen verwendeten Module sind Teil der Python-Standardbibliothek
## 📦 Installation
### Linux (Ubuntu/Debian)
```bash
# Python und tkinter installieren
sudo apt update
sudo apt install python3 python3-tk
# Repository klonen
git clone https://github.com/IHR-BENUTZERNAME/csv-dbf-converter.git
cd csv-dbf-converter
# Optional: Für Build als Standalone-Executable
pip3 install pyinstaller
```
### Windows
1. Python von [python.org](https://www.python.org/downloads/) herunterladen und installieren
- Bei der Installation "Add Python to PATH" aktivieren
2. Repository herunterladen oder klonen
3. Optional: Für Build als Executable
```cmd
pip install pyinstaller
```
## 🚀 Nutzung
### GUI-Version (Empfohlen)
```bash
# Linux
python3 csv_dbf_converter_gui.py
# Windows
python csv_dbf_converter_gui.py
```
Die grafische Oberfläche führt Sie durch folgende Schritte:
1. Mandant auswählen
2. CSV-Datei auswählen
3. Zeitraum (Monate/Jahre) festlegen
4. Konvertierung starten
### CLI-Version (Kommandozeile)
```bash
# Linux
python3 csv_dbf_converter.py
# Windows
python csv_dbf_converter.py
```
Die Kommandozeilenversion führt interaktiv durch den Prozess und zeigt detaillierte Informationen zur Verarbeitung an.
## ⚙️ Konfiguration
Das Programm verwendet JSON-Konfigurationsdateien. Beim ersten Start werden Beispieldateien erstellt.
### Verzeichnisstruktur der Konfigurationsdateien
```
./
├── mandanten_config.json # Mandanten-Übersicht
├── identitaeten_mieter_XXX.json # Mieter pro Mandant (XXX = Mandantennummer)
├── identitaeten_kosten_XXX.json # Kostenkonten pro Mandant
└── projekte_XXX.json # Projekte/Kostenstellen pro Mandant
```
### mandanten_config.json
Definiert die verfügbaren Mandanten:
```json
{
"mandanten": [
{
"name": "Mein Mandant",
"nummer": "001",
"konto": "1200",
"standard_gegenkonto": "1300",
"standard_bank_kto": "1200",
"beleg_index": 1
}
]
}
```
| Feld | Beschreibung |
|------|--------------|
| `name` | Name des Mandanten |
| `nummer` | Eindeutige Mandantennummer (3-stellig) |
| `konto` | Standard-Bankkonto |
| `standard_gegenkonto` | Gegenkonto für nicht zugeordnete Buchungen |
| `standard_bank_kto` | Bankkonto für Buchungen |
| `beleg_index` | Fortlaufende Belegnummer (wird automatisch erhöht) |
### identitaeten_mieter_XXX.json
Definiert Mieter mit Suchbegriffen für automatische Erkennung:
```json
{
"mandant": "Mein Mandant",
"mandantennummer": "001",
"mieter": [
{
"suchbegriffe": ["Müller", "Thomas Müller", "MUELLER"],
"konto": "10010",
"beschreibung": "Thomas Müller"
}
]
}
```
### identitaeten_kosten_XXX.json
Definiert Kostenkonten für Ausgaben:
```json
{
"kostenkonten": [
{
"gegenkonto": 4002,
"bezeichnung": "Be- und Entwässerung",
"suchbegriffe": ["Berliner Wasser", "WASSERBETRIEBE"]
}
]
}
```
### projekte_XXX.json
Definiert Projekte/Kostenstellen:
```json
{
"mandant": "Mein Mandant",
"mandantennummer": "001",
"default_projekt": "001",
"projekte": [
{
"suchbegriffe": ["Hauptstraße", "Hauptstr"],
"kst": "001",
"beschreibung": "Objekt Hauptstraße"
}
]
}
```
## 📄 CSV-Format
Das Programm erwartet CSV-Dateien mit Semikolon als Trennzeichen und folgenden Spalten:
| Erwartete Spalte | Alternative Namen |
|------------------|-------------------|
| Buchungstag | Datum |
| Buchungstext | Verwendungszweck, Text |
| Betrag | Amount |
### Beispiel CSV-Datei
```csv
Buchungstag;Buchungstext;Betrag
01.01.2024;Miete Thomas Müller Januar;750,00
02.01.2024;Berliner Wasserbetriebe Abschlag;-125,50
```
## 🔨 Build (Standalone Executable)
### Linux
```bash
cd scripts
chmod +x build_linux.sh
./build_linux.sh
```
Die ausführbare Datei befindet sich dann in `dist/csv_dbf_converter_gui`.
### Windows
```cmd
cd scripts
build_windows.bat
```
Die EXE-Datei befindet sich dann in `dist\CSV_DBF_Konverter.exe`.
## 📁 Projektstruktur
```
csv-dbf-converter/
├── csv_dbf_converter.py # CLI-Version (Hauptprogramm)
├── csv_dbf_converter_gui.py # GUI-Version mit tkinter
├── config/
│ └── examples/ # Beispiel-Konfigurationsdateien
│ ├── mandanten_config.json
│ ├── identitaeten_mieter_*.json
│ ├── identitaeten_kosten_*.json
│ └── projekte_*.json
├── scripts/
│ ├── build_linux.sh # Linux Build-Script
│ └── build_windows.bat # Windows Build-Script
├── requirements.txt # Python-Abhängigkeiten
├── LICENSE # MIT-Lizenz
└── README.md # Diese Datei
```
## 🔍 Funktionsweise
### Zuordnungslogik
1. **Mieternummer-Parsing**: Das Programm sucht zunächst nach 5-stelligen Zahlen beginnend mit "1" im Buchungstext, die auf ein Mieterkonto verweisen könnten.
2. **Suchbegriff-Matching**: Falls keine Mieternummer gefunden wird, werden die konfigurierten Suchbegriffe geprüft:
- Zuerst Mieter-Suchbegriffe
- Dann Kostenkonten-Suchbegriffe
3. **Projekt-Zuordnung**: Bei Kostenkonten wird zusätzlich nach Projekt-Suchbegriffen gesucht. Falls kein Projekt gefunden wird, wird das Default-Projekt verwendet.
### Text-Normalisierung
Für den Vergleich werden Texte normalisiert:
- Kleinschreibung
- Umlaute: ü→ue, ö→oe, ä→ae, ß→ss
## 📝 Beispiele
### Beispiel-Workflow
1. **Mandanten konfigurieren** in `mandanten_config.json`
2. **Mieter anlegen** in `identitaeten_mieter_001.json`
3. **Kostenkonten anlegen** in `identitaeten_kosten_001.json`
4. **CSV-Datei von Bank exportieren**
5. **Konverter starten** und CSV-Datei auswählen
6. **Monate/Jahre auswählen** für die Konvertierung
7. **DBF-Dateien** werden pro Monat erstellt
### Ausgabe-Dateien
Die generierten DBF-Dateien folgen dem Namensschema:
```
buchungen_[MANDANTENNR]_[MONAT][JAHR_KURZ].dbf
```
Beispiel: `buchungen_001_0124.dbf` (Mandant 001, Januar 2024)
## 🐛 Fehlerbehebung
### Häufige Probleme
**"tkinter nicht gefunden"**
```bash
# Ubuntu/Debian
sudo apt install python3-tk
# Fedora
sudo dnf install python3-tkinter
```
**"CSV-Spalte nicht gefunden"**
- Prüfen Sie, ob die CSV-Datei Semikolon als Trennzeichen verwendet
- Prüfen Sie die Spaltennamen (Buchungstag, Buchungstext, Betrag)
**"Keine Buchungen für den gewählten Zeitraum"**
- Prüfen Sie das Datumsformat in der CSV (DD.MM.YYYY)
- Prüfen Sie die gewählten Monate und Jahre
## 📜 Lizenz
Dieses Projekt ist unter der MIT-Lizenz lizenziert. Siehe [LICENSE](LICENSE) für Details.
## 👤 Autor
**Sebastian Zell**
- E-Mail: sebastian.zell@zell-aufmass.de
## 🤝 Beitragen
Beiträge sind willkommen! Bitte erstellen Sie einen Pull Request oder öffnen Sie ein Issue für Verbesserungsvorschläge.
---
*Erstellt für die Hausverwaltung mit ❤️ und Python*

View File

@ -0,0 +1,283 @@
{
"kostenkonten": [
{
"gegenkonto": 4001,
"bezeichnung": "Sprengwasserzähler",
"suchbegriffe": []
},
{
"gegenkonto": 4002,
"bezeichnung": "Be- und Endwässerung",
"suchbegriffe": [
"Berliner Wasser",
"BERLINER WASSERBETRIEBE"
]
},
{
"gegenkonto": 4003,
"bezeichnung": "Winterdienst",
"suchbegriffe": [
"Thomas Hansen"
]
},
{
"gegenkonto": 4004,
"bezeichnung": "Strassenreinigung/Müllabfuhr",
"suchbegriffe": [
"Brs",
"Berliner Stadtreinig",
"BR Berlin",
"bsr"
]
},
{
"gegenkonto": 4005,
"bezeichnung": "Hauswart",
"suchbegriffe": [
"Bundesknappschaft",
"VERWALTUNGS-BG,HAMBURG",
"Knappschaft",
"Gehalt",
"Verwaltungs-Berufs",
"Hauswart",
"Entgeld",
"Verwaltungs-bg"
]
},
{
"gegenkonto": 4006,
"bezeichnung": "Gartenpflege",
"suchbegriffe": [
"Erden GmbH"
]
},
{
"gegenkonto": 4007,
"bezeichnung": "Gebäudeversicherung",
"suchbegriffe": [
"Flenker",
"Victoria Versicherung",
"BASLER",
"signal"
]
},
{
"gegenkonto": 4008,
"bezeichnung": "Strom",
"suchbegriffe": [
"VATTENFALL",
"Yello",
"Bewag"
]
},
{
"gegenkonto": 4009,
"bezeichnung": "Waschküche",
"suchbegriffe": []
},
{
"gegenkonto": 4010,
"bezeichnung": "Kabelfernsehen",
"suchbegriffe": [
"Kabel"
]
},
{
"gegenkonto": 4011,
"bezeichnung": "Grundsteuer",
"suchbegriffe": [
"GrSt",
"Grundsteuer",
"gst"
]
},
{
"gegenkonto": 4012,
"bezeichnung": "prüfung technischer Anlagen",
"suchbegriffe": []
},
{
"gegenkonto": 4013,
"bezeichnung": "Wartung Lüftungsanlage",
"suchbegriffe": [
"kliwa"
]
},
{
"gegenkonto": 4014,
"bezeichnung": "Kleinmaterial Hausmeister",
"suchbegriffe": []
},
{
"gegenkonto": 4015,
"bezeichnung": "Kontoführung",
"suchbegriffe": [
"Kontofuehrung",
"EC-KARTE",
"Rechnungsabschluss",
"Aufgrund eines technischen Feh",
"Sollzinsen",
"vodafone",
"ney",
"Separierung zur Pfaendung",
"BENACHRICHTIGUNGSENTGELT",
"AUSGABE EINER DEBITKARTE",
"rueckgabe ue",
"ab dem 01.01"
]
},
{
"gegenkonto": 4016,
"bezeichnung": "Kaltwasserablesung",
"suchbegriffe": []
},
{
"gegenkonto": 4017,
"bezeichnung": "Grundsteuer individuell",
"suchbegriffe": []
},
{
"gegenkonto": 4018,
"bezeichnung": "Aufzugkosten",
"suchbegriffe": []
},
{
"gegenkonto": 4019,
"bezeichnung": "Hausreinigung",
"suchbegriffe": []
},
{
"gegenkonto": 4020,
"bezeichnung": "Schädlingsbekämpfung",
"suchbegriffe": [
"Akut Schäd"
]
},
{
"gegenkonto": 4048,
"bezeichnung": "Reparaturkosten nicht Umlagefähig",
"suchbegriffe": []
},
{
"gegenkonto": 4049,
"bezeichnung": "Werbekosten nicht Umlagefähig",
"suchbegriffe": []
},
{
"gegenkonto": 4050,
"bezeichnung": "Heizungswartung",
"suchbegriffe": [
"H+R",
"gegenbauer"
]
},
{
"gegenkonto": 4051,
"bezeichnung": "Brennstoffkosten",
"suchbegriffe": [
"Bama",
"Gasag"
]
},
{
"gegenkonto": 4052,
"bezeichnung": "Schornsteinfeger",
"suchbegriffe": [
"Schornsteinf",
"BSFM"
]
},
{
"gegenkonto": 4053,
"bezeichnung": "Verbrauchsmessung",
"suchbegriffe": []
},
{
"gegenkonto": 4054,
"bezeichnung": "Abrechnung",
"suchbegriffe": [
"KALORIMETA",
"Brunata"
]
},
{
"gegenkonto": 4060,
"bezeichnung": "Heizkostenabrechnung",
"suchbegriffe": []
},
{
"gegenkonto": 4070,
"bezeichnung": "Instandhaltung",
"suchbegriffe": [
"Fabian Zell Auslage",
"Sto AG",
"Behrendt Werb",
"Hahne",
"Rossoll",
"Possling",
"Schlieper",
"HERMINGHAUS",
"allmilmö",
"Bahag",
"nenn",
"bito",
"Gutmaier",
"rossol",
"bauhaus",
"kravag",
"auslage",
"würth",
"Bergmann",
"Öltankservice"
]
},
{
"gegenkonto": 4080,
"bezeichnung": "Kosten der Rechtsverfolgung",
"suchbegriffe": [
"Landeshauptkasse",
"Rechtsanwalt",
"Kosteneinziehungsstelle",
"gericht",
"pufahl"
]
},
{
"gegenkonto": 4090,
"bezeichnung": "Wohngeld",
"suchbegriffe": [
"WOHNGELD",
"WEG TEGELER",
"Weg CONTESSAWEG",
"BETA IMMOBILIEN",
"ABC",
"WEG DERN",
"WEG Am Teg",
"WEG Bock",
"WEG FAHRE",
"Kremer",
"WEG Neue Bergstr"
]
},
{
"gegenkonto": 4100,
"bezeichnung": "Annuitäten",
"suchbegriffe": [
"Aareal Bank",
"Weberbank",
"IBB",
"DSL",
"DG HYP",
"Annuität",
"INVESTITIONSBANK",
"Commerzbank",
"Finanzierungskosten",
"Darlehen",
"Tilgung",
"Zinsen",
"Weberbank",
"Darl"
]
}
]
}

View File

@ -0,0 +1,225 @@
{
"kostenkonten": [
{
"gegenkonto": 4001,
"bezeichnung": "Hauswart Nebenkosten",
"suchbegriffe": []
},
{
"gegenkonto": 4002,
"bezeichnung": "Be-/Entwässerung Abrechnung",
"suchbegriffe": []
},
{
"gegenkonto": 4003,
"bezeichnung": "Winterdienst",
"suchbegriffe": []
},
{
"gegenkonto": 4004,
"bezeichnung": "Strassenreinigung/Müllabfuhr",
"suchbegriffe": [
"Berlin Recycling",
"Berliner",
"Berliner Stadtreinigungsbetriebe"
]
},
{
"gegenkonto": 4005,
"bezeichnung": "Hauswart",
"suchbegriffe": [
"Fabian",
"Fabian Zell"
]
},
{
"gegenkonto": 4006,
"bezeichnung": "Gartenpflege",
"suchbegriffe": [
"Biowork",
"Fahrländer Erden",
"Fahrländer"
]
},
{
"gegenkonto": 4007,
"bezeichnung": "Gebäudeversicherung",
"suchbegriffe": [
"Signal",
"Signal Iduna"
]
},
{
"gegenkonto": 4008,
"bezeichnung": "Strom",
"suchbegriffe": [
"VATTENFALL",
"Vattenfall"
]
},
{
"gegenkonto": 4009,
"bezeichnung": "Be-/Entwässerung Verbrauch",
"suchbegriffe": [
"WASSER"
]
},
{
"gegenkonto": 4010,
"bezeichnung": "Grundsteuer",
"suchbegriffe": []
},
{
"gegenkonto": 4011,
"bezeichnung": "Gartenpflege / Baumbeschnitt",
"suchbegriffe": []
},
{
"gegenkonto": 4012,
"bezeichnung": "Prüfung technischer Anlagen",
"suchbegriffe": []
},
{
"gegenkonto": 4013,
"bezeichnung": "Fernsehanschluss",
"suchbegriffe": [
"Vodafone"
]
},
{
"gegenkonto": 4014,
"bezeichnung": "Kleinmaterial Hausmeister",
"suchbegriffe": []
},
{
"gegenkonto": 4015,
"bezeichnung": "Kontoführung",
"suchbegriffe": [
"Kontoführung",
"Kontofuehrung",
"Kontofuhrung",
"BENACHRICHTIGUNGSENTGELT",
"Rechnungsabschluss Konto",
"Benachrich",
"Aufgrund eines technischen"
]
},
{
"gegenkonto": 4016,
"bezeichnung": "Grundsteuer Gewerbeeinheiten",
"suchbegriffe": []
},
{
"gegenkonto": 4017,
"bezeichnung": "Eigentümerhaftpflichtversicherung",
"suchbegriffe": []
},
{
"gegenkonto": 4018,
"bezeichnung": "Verwalterhonorare",
"suchbegriffe": []
},
{
"gegenkonto": 4019,
"bezeichnung": "Lohnbuchhaltung",
"suchbegriffe": []
},
{
"gegenkonto": 4020,
"bezeichnung": "Mahngebühren",
"suchbegriffe": []
},
{
"gegenkonto": 4021,
"bezeichnung": "Verlustkonto aus Abrechnung NK 1",
"suchbegriffe": []
},
{
"gegenkonto": 4022,
"bezeichnung": "Verlustkonto aus Abrechnung NK 2",
"suchbegriffe": []
},
{
"gegenkonto": 4023,
"bezeichnung": "Verlustkonto aus Abrechnung NK 3",
"suchbegriffe": []
},
{
"gegenkonto": 4024,
"bezeichnung": "Sprengwasserzähler",
"suchbegriffe": []
},
{
"gegenkonto": 4025,
"bezeichnung": "Sonderkosten",
"suchbegriffe": []
},
{
"gegenkonto": 4026,
"bezeichnung": "Strom PKW Ladesäule",
"suchbegriffe": []
},
{
"gegenkonto": 4045,
"bezeichnung": "Regenrinnenreinigung",
"suchbegriffe": []
},
{
"gegenkonto": 4050,
"bezeichnung": "Heizungswartung",
"suchbegriffe": [
"Gutmaier"
]
},
{
"gegenkonto": 4051,
"bezeichnung": "Heizkosten",
"suchbegriffe": [
"GASAG"
]
},
{
"gegenkonto": 4052,
"bezeichnung": "Schornsteinfeger",
"suchbegriffe": [
"Bsfm"
]
},
{
"gegenkonto": 4053,
"bezeichnung": "Verbrauchsmessung",
"suchbegriffe": []
},
{
"gegenkonto": 4054,
"bezeichnung": "Abrechnung",
"suchbegriffe": [
"Ista",
"Kalorimeta"
]
},
{
"gegenkonto": 4055,
"bezeichnung": "Heizstrom",
"suchbegriffe": []
},
{
"gegenkonto": 4060,
"bezeichnung": "Heizkostenabrechnung",
"suchbegriffe": []
},
{
"gegenkonto": 4070,
"bezeichnung": "Instandhaltung",
"suchbegriffe": [
"Kunert Tischlerei",
"Kunert"
]
},
{
"gegenkonto": 4080,
"bezeichnung": "Gericht / Gerichtsvollzieher",
"suchbegriffe": []
}
]
}

View File

@ -0,0 +1,743 @@
{
"mandant": "Mustermann GmbH",
"mandantennummer": "001",
"mieter": [
{
"suchbegriffe": [
"Renate Steffensen",
"Steffensen",
"STEFFENSEN,RENATE"
],
"konto": "10020",
"beschreibung": "Renate Steffensen"
},
{
"suchbegriffe": [
"Renate Steffensen",
"Steffensen"
],
"konto": "10030",
"beschreibung": "Renate Steffensen"
},
{
"suchbegriffe": [
"Konstantinos Papadopoulos",
"Papadopoulos",
"Papadopoulos",
"PAPAD"
],
"konto": "10035",
"beschreibung": "Konstantinos Papadopoulos"
},
{
"suchbegriffe": [
"Dorit Werner",
"Werner",
"Werner",
"Werner"
],
"konto": "10036",
"beschreibung": "Dorit Werner"
},
{
"suchbegriffe": [
"Marcel Probstmeyer",
"Probstmeyer",
"Probstmeyer"
],
"konto": "10039",
"beschreibung": "Marcel Probstmeyer"
},
{
"suchbegriffe": [
"Walter Mindach",
"Mindach",
"Krieger",
"MINDACH, WALTER"
],
"konto": "10040",
"beschreibung": "Walter Mindach"
},
{
"suchbegriffe": [
"Fritz Werner",
"Werner"
],
"konto": "10042",
"beschreibung": "Fritz Werner"
},
{
"suchbegriffe": [
"Marco Mecarelli",
"Mecarelli",
"Mecarelli"
],
"konto": "10045",
"beschreibung": "Marco Mecarelli"
},
{
"suchbegriffe": [
"Marianne Poplawski",
"Poplawski",
"POPLAWSKI,MARIANNE"
],
"konto": "10046",
"beschreibung": "Marianne Poplawski"
},
{
"suchbegriffe": [
"Helga Hartinger",
"Hartinger",
"Hartinger",
"Hartinger"
],
"konto": "10048",
"beschreibung": "Helga Hartinger"
},
{
"suchbegriffe": [
"Bärbel Jaehring",
"Jaehring",
"Krug",
"Jaehring",
"Krug"
],
"konto": "10049",
"beschreibung": "Bärbel Jaehring"
},
{
"suchbegriffe": [
"Günter Van de Weghe",
"Van de Weghe",
"Van de Weghe",
"vande Weghe"
],
"konto": "10052",
"beschreibung": "Günter Van de Weghe"
},
{
"suchbegriffe": [
"Manuela Richter",
"Richter",
"Richter",
"Richter"
],
"konto": "10068",
"beschreibung": "Manuela Richter"
},
{
"suchbegriffe": [
"Heinz Lange",
"Lange",
"Lange"
],
"konto": "10072",
"beschreibung": "Heinz Lange"
},
{
"suchbegriffe": [
"Antje Keck",
"Keck",
"Kozik",
"Keck"
],
"konto": "10073",
"beschreibung": "Antje Keck"
},
{
"suchbegriffe": [
"Markus Weichert",
"Weichert",
"Weichert"
],
"konto": "10074",
"beschreibung": "Markus Weichert"
},
{
"suchbegriffe": [
"Monika Ramlow",
"Ramlow",
"Ramlow"
],
"konto": "10075",
"beschreibung": "Monika Ramlow"
},
{
"suchbegriffe": [
"Sarah Clarissa Schroedter",
"Schroedter",
"Schroedter",
"SCHROEDTER",
"Wanke"
],
"konto": "10076",
"beschreibung": "Sarah Clarissa Schroedter"
},
{
"suchbegriffe": [
"Conny-Maren Krause",
"Krause",
"KRAUSE"
],
"konto": "10078",
"beschreibung": "Conny-Maren Krause"
},
{
"suchbegriffe": [
"Kurt Hans Jessel",
"Jessel"
],
"konto": "10079",
"beschreibung": "Kurt Hans Jessel"
},
{
"suchbegriffe": [
" ATC Germany Munich",
"ATC Germany Munich",
"ehem.E-Plus",
"E-PLUS MOBILFUNK GMBH",
"ATC"
],
"konto": "10080",
"beschreibung": " ATC Germany Munich"
},
{
"suchbegriffe": [
" Vantage Towers AG",
"Vantage Towers AG",
"ehem. Vodafone",
"VODAFONE D2 GMBH",
"Vantage"
],
"konto": "10081",
"beschreibung": " Vantage Towers AG"
},
{
"suchbegriffe": [
"Heidi Thürling",
"Thürling",
"THURLING",
"TH?RLING"
],
"konto": "10082",
"beschreibung": "Heidi Thürling"
},
{
"suchbegriffe": [
"Anne Horn",
"Horn",
"Knust"
],
"konto": "10083",
"beschreibung": "Anne Horn"
},
{
"suchbegriffe": [
"Irmgard Dupke",
"Dupke",
"DUPKE"
],
"konto": "10084",
"beschreibung": "Irmgard Dupke"
},
{
"suchbegriffe": [
"Manfred Hannasky",
"Hannasky",
"HANNASKY"
],
"konto": "10085",
"beschreibung": "Manfred Hannasky"
},
{
"suchbegriffe": [
"Mandy Jaqueline Gey",
"Gey",
"Schrammek"
],
"konto": "10086",
"beschreibung": "Mandy Jaqueline Gey"
},
{
"suchbegriffe": [
"Alexis-Roman Kruppa",
"Kruppa"
],
"konto": "10087",
"beschreibung": "Alexis-Roman Kruppa"
},
{
"suchbegriffe": [
"Fuad Vokrri",
"Vokrri",
"VOKRRI",
"FUAD"
],
"konto": "10088",
"beschreibung": "Fuad Vokrri"
},
{
"suchbegriffe": [
"Eveline Olsson",
"Olsson",
"Olsson",
"UWE OLSSON"
],
"konto": "10089",
"beschreibung": "Eveline Olsson"
},
{
"suchbegriffe": [
"Ingrid Ida Fitzner",
"Fitzner",
"Fitzner",
"Fitzner"
],
"konto": "10090",
"beschreibung": "Ingrid Ida Fitzner"
},
{
"suchbegriffe": [
"Sophie Charlotte Wanke",
"Wanke",
"Borngr"
],
"konto": "10091",
"beschreibung": "Sophie Charlotte Wanke"
},
{
"suchbegriffe": [
"Ingrid Ida Fitzner",
"Fitzner"
],
"konto": "10092",
"beschreibung": "Ingrid Ida Fitzner"
},
{
"suchbegriffe": [
"Tanja Barth",
"Barth",
"Wilhelm"
],
"konto": "10093",
"beschreibung": "Tanja Barth"
},
{
"suchbegriffe": [
"Manuela Richter",
"Richter",
"Richter"
],
"konto": "10094",
"beschreibung": "Manuela Richter"
},
{
"suchbegriffe": [
"Irene Bauer",
"Bauer",
"Bauer"
],
"konto": "10095",
"beschreibung": "Irene Bauer"
},
{
"suchbegriffe": [
"Bert Kunert",
"Kunert",
"Kunert"
],
"konto": "10096",
"beschreibung": "Bert Kunert"
},
{
"suchbegriffe": [
"Hannelore Dittmann",
"Dittmann",
"Dittmar"
],
"konto": "10097",
"beschreibung": "Hannelore Dittmann"
},
{
"suchbegriffe": [
"Sandra Hedke",
"Hedke",
"Kahl",
"Kahl",
"Hedke"
],
"konto": "10098",
"beschreibung": "Sandra Hedke"
},
{
"suchbegriffe": [
"Julia Ellinger",
"Ellinger",
"Ellinger"
],
"konto": "10099",
"beschreibung": "Julia Ellinger"
},
{
"suchbegriffe": [
" Eins in Christus",
"Eins in Christus"
],
"konto": "10100",
"beschreibung": " Eins in Christus"
},
{
"suchbegriffe": [
"Andrea-Britta Lüth",
"Lüth",
"Lüth"
],
"konto": "10101",
"beschreibung": "Andrea-Britta Lüth"
},
{
"suchbegriffe": [
"Andreas Holtz",
"Holtz"
],
"konto": "10102",
"beschreibung": "Andreas Holtz"
},
{
"suchbegriffe": [
"Angela Kranz",
"Kranz"
],
"konto": "10103",
"beschreibung": "Angela Kranz"
},
{
"suchbegriffe": [
"Monique Rosenow",
"Rosenow"
],
"konto": "10104",
"beschreibung": "Monique Rosenow"
},
{
"suchbegriffe": [
"Manfred Robert Posselt",
"Posselt",
"Posselt"
],
"konto": "10105",
"beschreibung": "Manfred Robert Posselt"
},
{
"suchbegriffe": [
"Marianne Reimer",
"Reimer"
],
"konto": "10106",
"beschreibung": "Marianne Reimer"
},
{
"suchbegriffe": [
"Jürgen Alois Pichler",
"Pichler"
],
"konto": "10107",
"beschreibung": "Jürgen Alois Pichler"
},
{
"suchbegriffe": [
"Andreas Daniel Kandt",
"Kandt"
],
"konto": "10108",
"beschreibung": "Andreas Daniel Kandt"
},
{
"suchbegriffe": [
"Heike Günther",
"Günther",
"Schneider"
],
"konto": "10109",
"beschreibung": "Heike Günther"
},
{
"suchbegriffe": [
"Silke Schröder",
"Schröder"
],
"konto": "10110",
"beschreibung": "Silke Schröder"
},
{
"suchbegriffe": [
"Rotraud Rospert",
"Rospert"
],
"konto": "10111",
"beschreibung": "Rotraud Rospert"
},
{
"suchbegriffe": [
"Jennifer Weber",
"Weber"
],
"konto": "10112",
"beschreibung": "Jennifer Weber"
},
{
"suchbegriffe": [
"Joe-Marvin Henson",
"Henson",
"Henson"
],
"konto": "10113",
"beschreibung": "Joe-Marvin Henson"
},
{
"suchbegriffe": [
"Jacqueline Esther De Vries",
"De Vries"
],
"konto": "10114",
"beschreibung": "Jacqueline Esther De Vries"
},
{
"suchbegriffe": [
"Catrin Zonsius",
"Zonsius",
"Zonsius"
],
"konto": "10115",
"beschreibung": "Catrin Zonsius"
},
{
"suchbegriffe": [
"Charlen Heidergott",
"Heidergott",
"Brüning"
],
"konto": "10116",
"beschreibung": "Charlen Heidergott"
},
{
"suchbegriffe": [
"Jennifer Weber",
"Weber, Jennifer",
"Winkler"
],
"konto": "10117",
"beschreibung": "Jennifer Weber"
},
{
"suchbegriffe": [
"Gerrit Küstermann",
"Küstermann"
],
"konto": "10118",
"beschreibung": "Gerrit Küstermann"
},
{
"suchbegriffe": [
"Sofya Iluchshenko",
"Iluchshenko"
],
"konto": "10119",
"beschreibung": "Sofya Iluchshenko"
},
{
"suchbegriffe": [
"Sarah Ketzer",
"Ketzer",
"Malkowski"
],
"konto": "10121",
"beschreibung": "Sarah Ketzer"
},
{
"suchbegriffe": [
"Fuad Vokrri",
"Vokrri"
],
"konto": "10122",
"beschreibung": "Fuad Vokrri"
},
{
"suchbegriffe": [
"Jenny Binder",
"Binder",
"Paul"
],
"konto": "10123",
"beschreibung": "Jenny Binder"
},
{
"suchbegriffe": [
"Anton Glaser",
"Glaser"
],
"konto": "10124",
"beschreibung": "Anton Glaser"
},
{
"suchbegriffe": [
"Kathrin Hoyer",
"Hoyer",
"Starosta"
],
"konto": "10125",
"beschreibung": "Kathrin Hoyer"
},
{
"suchbegriffe": [
"Tom Stampfuss",
"Stampfuss",
"Höne",
"Hone"
],
"konto": "10126",
"beschreibung": "Tom Stampfuss"
},
{
"suchbegriffe": [
"Artem Poltavets",
"Poltavets"
],
"konto": "10127",
"beschreibung": "Artem Poltavets"
},
{
"suchbegriffe": [
"Leon Maximilian Posselt"
],
"konto": "10128",
"beschreibung": "Leon Maximilian Posselt"
},
{
"suchbegriffe": [
"Brigitte Moskal",
"Moskal"
],
"konto": "10129",
"beschreibung": "Brigitte Moskal"
},
{
"suchbegriffe": [
"Oksana Roman",
"Roman"
],
"konto": "10130",
"beschreibung": "Oksana Roman"
},
{
"suchbegriffe": [
"Maryam Zinoury",
"Zinoury"
],
"konto": "10131",
"beschreibung": "Maryam Zinoury"
},
{
"suchbegriffe": [
"Julia Ellinger",
"Ellinger"
],
"konto": "10132",
"beschreibung": "Julia Ellinger"
},
{
"suchbegriffe": [
"Phillipp Weber",
"Weber, Phillipp"
],
"konto": "10133",
"beschreibung": "Phillipp Weber"
},
{
"suchbegriffe": [
"Artem Poltavets",
"Poltavets"
],
"konto": "10134",
"beschreibung": "Artem Poltavets"
},
{
"suchbegriffe": [
"C.U.T",
"kwe"
],
"konto": "40000",
"beschreibung": "C.U.T"
},
{
"suchbegriffe": [
"ZV Wasser",
"SIBA",
"Kommunales Wirt",
"LOS-KWU",
"Scharmützelsee",
"Scharmutzelsee",
"Ewe",
"E.dis",
"CUT",
"Hopp",
"HELIOS",
"Ingrid Und Klaus Zell",
"Jabado, Samir Andreas",
"Ingrid Zell Sparkasse",
"Dr Jochen Küller",
"GEZ",
"KLAUS ZELL SPARKASSE",
"INGRID ZELL SPARKASSE"
],
"konto": "40000",
"beschreibung": "Ingrid Und Klaus Zell"
},
{
"suchbegriffe": [
"WEG Elster"
],
"konto": "40001",
"beschreibung": "WEG Elster"
},
{
"suchbegriffe": [
"INGRID ZELL HAUSVERW",
"Ingrid Zell GmbH"
],
"konto": "40009",
"beschreibung": "INGRID ZELL HAUSVERW"
},
{
"suchbegriffe": [
"FABIAN ZELL FIRMA",
"fabian Zell COBA"
],
"konto": "40011",
"beschreibung": "FABIAN ZELL FIRMA"
},
{
"suchbegriffe": [
"SEBASTIAN ZELL bekannt",
"Sebastian Zell COBA",
"SEBASTIAN ZELL e.k."
],
"konto": "40006",
"beschreibung": "Sebastian Zell"
},
{
"suchbegriffe": [
"paypal"
],
"konto": "40020",
"beschreibung": "Paypal"
},
{
"suchbegriffe": [
"finanza"
],
"konto": "40030",
"beschreibung": "Finanzamt"
}
]
}

View File

@ -0,0 +1,30 @@
{
"mandant": "WEG Contessaweg",
"mandantennummer": "007",
"mieter": [
{
"suchbegriffe": [
"mueller",
"müller",
"carsten"
],
"konto": "20017",
"beschreibung": "Carsten Müller"
},
{
"suchbegriffe": [
"knust",
"horn"
],
"konto": "20019",
"beschreibung": "Ulrich Knust, Anne Horn"
},
{
"suchbegriffe": [
"zell"
],
"konto": "20018",
"beschreibung": "Fabian und Sebastian Zell"
}
]
}

View File

@ -0,0 +1,28 @@
{
"mandanten": [
{
"name": "Fabian und Sebastian Zell",
"nummer": "001",
"konto": "1200",
"standard_gegenkonto": "1300",
"standard_bank_kto": "1200",
"beleg_index": 2826
},
{
"name": "Ingrid und Klaus Zell",
"nummer": "002",
"konto": "1800",
"standard_gegenkonto": "8500",
"standard_bank_kto": "1800",
"beleg_index": 1
},
{
"name": "WEG Contessaweg",
"nummer": "007",
"konto": "1208",
"standard_gegenkonto": "1590",
"standard_bank_kto": "1208",
"beleg_index": 517
}
]
}

View File

@ -0,0 +1,73 @@
{
"mandant": "default",
"mandantennummer": "001",
"default_projekt": "001",
"projekte": [
{
"suchbegriffe": [
"Con"
],
"kst": "001",
"beschreibung": "Con"
},
{
"suchbegriffe": [
"Schr"
],
"kst": "002",
"beschreibung": "Schr"
},
{
"suchbegriffe": [
"Mar"
],
"kst": "003",
"beschreibung": "Mar"
},
{
"suchbegriffe": [
"Spa"
],
"kst": "004",
"beschreibung": "Spa"
},
{
"suchbegriffe": [
"Gatow",
"abc"
],
"kst": "005",
"beschreibung": "Gatow"
},
{
"suchbegriffe": [
"Burg",
"kremer"
],
"kst": "006",
"beschreibung": "Burg"
},
{
"suchbegriffe": [
"Teg",
"beta"
],
"kst": "007",
"beschreibung": "Teg"
},
{
"suchbegriffe": [
"Fah"
],
"kst": "008",
"beschreibung": "Fah"
},
{
"suchbegriffe": [
"Pich"
],
"kst": "009",
"beschreibung": "Pich"
}
]
}

View File

@ -0,0 +1,15 @@
{
"mandant": "default",
"mandantennummer": "001",
"default_projekt": "001",
"projekte": [
{
"suchbegriffe": [
"Con",
" "
],
"kst": "3",
"beschreibung": "Con"
}
]
}

677
csv_dbf_converter.py Normal file
View File

@ -0,0 +1,677 @@
import json
import csv
import os
import struct
from datetime import datetime, date
# ===== CONFIG DATEIEN ERSTELLEN/LADEN =====
def create_default_configs():
"""Erstellt Standard-Config-Dateien falls nicht vorhanden"""
# Mandanten Config
if not os.path.exists('mandanten_config.json'):
mandanten_config = {
"mandanten": [
{
"name": "Mustermann GmbH",
"nummer": "001",
"konto": "1200",
"standard_gegenkonto": "8400",
"beleg_index": 1
},
{
"name": "Beispiel AG",
"nummer": "002",
"konto": "1800",
"standard_gegenkonto": "8500",
"beleg_index": 1
}
]
}
with open('mandanten_config.json', 'w', encoding='utf-8') as f:
json.dump(mandanten_config, f, indent=4, ensure_ascii=False)
print("✓ mandanten_config.json erstellt")
# Prüfe ob Identitäten-Dateien für jeden Mandanten existieren
mandanten = load_mandanten()
for mandant in mandanten:
# Mieter-Identitäten
mieter_datei = f"identitaeten_mieter_{mandant['nummer']}.json"
if not os.path.exists(mieter_datei):
mieter_config = {
"mandant": mandant['name'],
"mandantennummer": mandant['nummer'],
"mieter": [
{
"suchbegriffe": ["Thomas Müller", "Müller", "T. Müller"],
"konto": "1210",
"beschreibung": "Miete Thomas Müller"
},
{
"suchbegriffe": ["Schmidt", "Anna Schmidt", "A. Schmidt"],
"konto": "1220",
"beschreibung": "Miete Anna Schmidt"
},
{
"suchbegriffe": ["Weber GmbH", "Weber"],
"konto": "1230",
"beschreibung": "Miete Weber GmbH"
}
]
}
with open(mieter_datei, 'w', encoding='utf-8') as f:
json.dump(mieter_config, f, indent=4, ensure_ascii=False)
print(f"{mieter_datei} erstellt")
# Kostenkonten-Identitäten
kosten_datei = f"identitaeten_kosten_{mandant['nummer']}.json"
if not os.path.exists(kosten_datei):
kosten_config = {
"mandant": mandant['name'],
"mandantennummer": mandant['nummer'],
"kostenkonten": [
{
"suchbegriffe": ["Wasser", "Wasserbetriebe", "Berliner Wasserbetriebe"],
"gegenkonto": "6300",
"beschreibung": "Wasserkosten"
},
{
"suchbegriffe": ["Strom", "Vattenfall", "Stromversorgung"],
"gegenkonto": "6310",
"beschreibung": "Stromkosten"
},
{
"suchbegriffe": ["Gas", "Gasag", "Gasversorgung"],
"gegenkonto": "6320",
"beschreibung": "Gaskosten"
},
{
"suchbegriffe": ["Versicherung", "Allianz", "Gebäudeversicherung"],
"gegenkonto": "6500",
"beschreibung": "Versicherung"
},
{
"suchbegriffe": ["Hausverwaltung", "Verwaltung", "Verwaltungsgebühr"],
"gegenkonto": "6700",
"beschreibung": "Hausverwaltungskosten"
}
]
}
with open(kosten_datei, 'w', encoding='utf-8') as f:
json.dump(kosten_config, f, indent=4, ensure_ascii=False)
print(f"{kosten_datei} erstellt")
# Projekte-Datei
projekte_datei = f"projekte_{mandant['nummer']}.json"
if not os.path.exists(projekte_datei):
projekte_config = {
"mandant": mandant['name'],
"mandantennummer": mandant['nummer'],
"default_projekt": "ALLG001",
"projekte": [
{
"suchbegriffe": ["Projekt Alpha", "Alpha"],
"kst": "PRJ001",
"beschreibung": "Projekt Alpha"
},
{
"suchbegriffe": ["Hausverwaltung", "HV2024"],
"kst": "HV2024",
"beschreibung": "Hausverwaltung 2024"
},
{
"suchbegriffe": ["Musterstraße 10", "Musterstr. 10"],
"kst": "HAUS01",
"beschreibung": "Musterstraße 10"
}
]
}
with open(projekte_datei, 'w', encoding='utf-8') as f:
json.dump(projekte_config, f, indent=4, ensure_ascii=False)
print(f"{projekte_datei} erstellt")
def load_mandanten():
"""Lädt Mandanten aus Config"""
with open('mandanten_config.json', 'r', encoding='utf-8') as f:
return json.load(f)['mandanten']
def save_mandanten(mandanten):
"""Speichert Mandanten in Config"""
with open('mandanten_config.json', 'w', encoding='utf-8') as f:
json.dump({"mandanten": mandanten}, f, indent=4, ensure_ascii=False)
def load_mieter(mandantennummer):
"""Lädt Mieter für einen bestimmten Mandanten"""
mieter_datei = f"identitaeten_mieter_{mandantennummer}.json"
with open(mieter_datei, 'r', encoding='utf-8') as f:
return json.load(f)['mieter']
def load_kostenkonten(mandantennummer):
"""Lädt Kostenkonten für einen bestimmten Mandanten"""
kosten_datei = f"identitaeten_kosten_{mandantennummer}.json"
with open(kosten_datei, 'r', encoding='utf-8') as f:
return json.load(f)['kostenkonten']
def load_projekte(mandantennummer):
"""Lädt Projekte/Kostenstellen für einen bestimmten Mandanten"""
projekte_datei = f"projekte_{mandantennummer}.json"
with open(projekte_datei, 'r', encoding='utf-8') as f:
config = json.load(f)
return config['projekte'], config.get('default_projekt', '')
def normalize_text(text):
"""
Normalisiert Text für Vergleiche:
- Kleinbuchstaben
- ü ue, ö oe, ä ae, ß ss
"""
text = text.lower()
replacements = {
'ü': 'ue', 'ö': 'oe', 'ä': 'ae', 'ß': 'ss',
'é': 'e', 'è': 'e', 'ê': 'e', 'à': 'a', 'â': 'a'
}
for old, new in replacements.items():
text = text.replace(old, new)
return text
# ===== PARSING LOGIK =====
def parse_mieter(buchungstext, mieter):
"""
Sucht nach Mietern im Buchungstext (OHNE Projektnummer)
Returns: (konto, beschreibung) oder (None, None)
"""
buchungstext_norm = normalize_text(buchungstext)
for mieter_entry in mieter:
for suchbegriff in mieter_entry['suchbegriffe']:
suchbegriff_norm = normalize_text(suchbegriff)
if suchbegriff_norm in buchungstext_norm:
print(f" → Mieter gefunden: '{suchbegriff}' → Konto {mieter_entry['konto']}")
return mieter_entry['konto'], mieter_entry.get('beschreibung', '')
return None, None
def parse_kostenkonten(buchungstext, kostenkonten):
"""
Sucht nach Kostenkonten im Buchungstext
Returns: (gegenkonto, beschreibung) oder (None, None)
"""
buchungstext_norm = normalize_text(buchungstext)
for kosten_entry in kostenkonten:
for suchbegriff in kosten_entry['suchbegriffe']:
suchbegriff_norm = normalize_text(suchbegriff)
if suchbegriff_norm in buchungstext_norm:
print(f" → Kostenkonto gefunden: '{suchbegriff}' → Gegenkonto {kosten_entry['gegenkonto']}")
return kosten_entry['gegenkonto'], kosten_entry.get('beschreibung', '')
return None, None
def parse_projekt(buchungstext, projekte, default_projekt):
"""
Sucht nach Projekten im Buchungstext
Wird NUR aufgerufen wenn Kostenkonto gefunden wurde!
Returns: kostenstelle
"""
buchungstext_norm = normalize_text(buchungstext)
for projekt in projekte:
for suchbegriff in projekt['suchbegriffe']:
suchbegriff_norm = normalize_text(suchbegriff)
if suchbegriff_norm in buchungstext_norm:
print(f" → Projekt gefunden: '{suchbegriff}' → KST {projekt['kst']}")
return projekt['kst']
# Kein Projekt gefunden → Default verwenden
if default_projekt:
print(f" → Kein Projekt gefunden → Default-KST {default_projekt}")
return default_projekt
return ''
def bestimme_konten_und_kst(buchungstext, mandant, mieter, kostenkonten, projekte, default_projekt):
"""
Bestimmt Konten, Kostenstelle und Beschreibung basierend auf dem Buchungstext.
Mit Mieternummer-Parsing für Konten beginnend mit "1".
"""
norm_buchungstext = normalize_text(buchungstext)
# --- TEIL 0: ZUERST NACH MIETERNUMMER SUCHEN (nur für Konten mit "1") ---
import re
buchungstyp = 'unbekannt'
gegenkonto = None
beschreibung = None
# Suche nach 5-stelligen Zahlen im Buchungstext (potenzielle Mieternummern)
mieternummer_pattern = r'\b(1\d{4})\b' # 5-stellige Zahl beginnend mit 1
gefundene_nummern = re.findall(mieternummer_pattern, buchungstext)
if gefundene_nummern:
print(f" 🔍 Gefundene potenzielle Mieternummern: {gefundene_nummern}")
# Prüfe ob eine der gefundenen Nummern ein Mieterkonto ist
for nummer in gefundene_nummern:
for mieter_entry in mieter:
if mieter_entry.get('konto') == nummer:
gegenkonto = mieter_entry.get('konto')
beschreibung = mieter_entry.get('beschreibung')
buchungstyp = 'mieter'
print(f" ✓ Mieter über Kontonummer gefunden: {nummer}{beschreibung}")
break
if buchungstyp == 'mieter':
break
# --- TEIL 1: NUR WENN KEINE MIETERNUMMER GEFUNDEN, NORMALES PARSING ---
if buchungstyp == 'unbekannt':
# Standard-Gegenkonto aus Mandanten-Config verwenden
gegenkonto = mandant.get('standard_gegenkonto', '1300') # KORRIGIERT!
beschreibung = "Unbekannte Buchung"
# Suche nach Mietern über Suchbegriffe
for mieter_entry in mieter:
for suchbegriff in mieter_entry.get('suchbegriffe', []):
if normalize_text(suchbegriff) in norm_buchungstext:
gegenkonto = mieter_entry.get('konto')
beschreibung = mieter_entry.get('beschreibung')
buchungstyp = 'mieter'
print(f" ✓ Buchungstyp: Mieter (gefunden über '{suchbegriff}')")
break
if buchungstyp == 'mieter':
break
# --- TEIL 1b: KOSTENKONTEN PRÜFEN ---
if buchungstyp == 'unbekannt':
for kosten_entry in kostenkonten:
for suchbegriff in kosten_entry.get('suchbegriffe', []):
if normalize_text(suchbegriff) in norm_buchungstext:
gegenkonto = kosten_entry.get('gegenkonto')
beschreibung = kosten_entry.get('bezeichnung', '')
buchungstyp = 'kosten'
print(f" ✓ Buchungstyp: Kosten (gefunden über '{suchbegriff}')")
break
if buchungstyp == 'kosten':
break
# --- TEIL 2: KOSTENSTELLE BESTIMMEN (nur bei Kostenkonten) ---
gefundene_kst = ''
if buchungstyp == 'kosten':
for projekt in projekte:
for suchbegriff in projekt.get('suchbegriffe', []):
if normalize_text(suchbegriff) in norm_buchungstext:
gefundene_kst = projekt.get('kst', '')
print(f" ✓ Projekt-Kostenstelle gefunden: '{gefundene_kst}' (über '{suchbegriff}')")
break
if gefundene_kst:
break
# Default-Kostenstelle nur bei Kostenkonten
if not gefundene_kst and default_projekt:
gefundene_kst = default_projekt
print(f" → Keine Kostenstelle gefunden → Default-KST {default_projekt}")
# --- TEIL 3: ERGEBNISSE ZUSAMMENFÜHREN ---
konto = mandant.get('standard_bank_kto', mandant.get('konto', '1200'))
kostenstelle = gefundene_kst
# DIAGNOSE-AUSGABE
print("\n" + "="*25 + " FINALES ZUORDNUNGSERGEBNIS " + "="*25)
print(f" - KONTO: {konto}")
print(f" - GEGENKONTO: {gegenkonto}")
print(f" - KOSTENSTELLE: '{kostenstelle}'")
print(f" - BESCHREIBUNG: {beschreibung}")
print(f" - BUCHUNGSTYP: {buchungstyp}")
print("="*70 + "\n")
return str(konto), str(gegenkonto), str(kostenstelle), beschreibung
# ===== CSV EINLESEN =====
def lese_csv(csv_datei):
"""Liest CSV-Datei ein und gibt Liste von Buchungen zurück"""
buchungen = []
with open(csv_datei, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f, delimiter=';')
# Spaltennamen bereinigen (Leerzeichen UND BOM entfernen)
reader.fieldnames = [name.strip().lstrip('\ufeff') if name else name for name in reader.fieldnames]
# Debug: Spaltennamen anzeigen
print(f"\n📋 Gefundene CSV-Spalten: {reader.fieldnames}")
# Flexible Spaltenerkennung
datum_spalte = None
text_spalte = None
betrag_spalte = None
for name in reader.fieldnames:
name_lower = name.lower()
if 'buchungstag' in name_lower or 'datum' in name_lower:
datum_spalte = name
if 'buchungstext' in name_lower or 'verwendungszweck' in name_lower or 'text' in name_lower:
text_spalte = name
if 'betrag' in name_lower or 'amount' in name_lower:
betrag_spalte = name
if not datum_spalte:
raise ValueError(f"Keine Datums-Spalte gefunden! Verfügbare Spalten: {reader.fieldnames}")
if not text_spalte:
raise ValueError(f"Keine Text-Spalte gefunden! Verfügbare Spalten: {reader.fieldnames}")
if not betrag_spalte:
raise ValueError(f"Keine Betrags-Spalte gefunden! Verfügbare Spalten: {reader.fieldnames}")
print(f"✓ Verwende Spalten: Datum='{datum_spalte}', Text='{text_spalte}', Betrag='{betrag_spalte}'")
for row in reader:
try:
# Datum parsen (Format: DD.MM.YYYY)
datum_str = row[datum_spalte].strip()
datum = datetime.strptime(datum_str, '%d.%m.%Y')
# Betrag konvertieren (Format: -123,45 → -123.45)
betrag_str = row[betrag_spalte].strip().replace('.', '').replace(',', '.')
betrag = float(betrag_str)
# Buchungstext
buchungstext = row[text_spalte].strip() if row[text_spalte] else ''
buchung = {
'datum': datum,
'buchungstext': buchungstext,
'betrag': betrag,
'umsatzart': row.get('Umsatzart', '').strip() if row.get('Umsatzart') else ''
}
buchungen.append(buchung)
except Exception as e:
print(f"⚠️ Zeile übersprungen (Fehler: {e}): {row}")
continue
return buchungen
def filtere_buchungen(buchungen, monate, jahre):
"""Filtert Buchungen nach ausgewählten Monaten und Jahren"""
gefiltert = []
for buchung in buchungen:
if buchung['datum'].month in monate and buchung['datum'].year in jahre:
gefiltert.append(buchung)
return gefiltert
# ===== DBF MANUELL ERSTELLEN =====
# --- START: ERSETZEN SIE IHRE ALTE FUNKTION DURCH DIESE ---
def erstelle_dbf_manuell(mandant, buchungen, mieter, kostenkonten, projekte, default_projekt, ausgabe_datei_praefix, start_beleg_nr):
"""
Gruppiert Buchungen nach Monat und erstellt für jeden Monat eine eigene,
Byte-genaue DBF-Datei.
Returns: letzte_beleg_nr
"""
# NEU: Schritt 1 - Buchungen nach Monat gruppieren
buchungen_pro_monat = {}
for buchung in buchungen:
monats_schluessel = buchung['datum'].strftime('%Y-%m') # z.B. '2023-10'
if monats_schluessel not in buchungen_pro_monat:
buchungen_pro_monat[monats_schluessel] = []
buchungen_pro_monat[monats_schluessel].append(buchung)
print(f"\nFunde {len(buchungen_pro_monat)} verschiedene Monate in den Buchungsdaten.")
beleg_nr = start_beleg_nr
# NEU: Schritt 2 - Für jeden Monat eine eigene DBF-Datei erstellen
for monat_key, monats_buchungen in buchungen_pro_monat.items():
jahr, monat = map(int, monat_key.split('-'))
jahr_kurz = str(jahr)[-2:]
# NEU: Dateinamen für diesen Monat generieren
ausgabe_datei = f"{ausgabe_datei_praefix}_{monat:02d}{jahr_kurz}.dbf"
print(f"\n{'='*70}")
print(f"Erstelle DBF-Datei für Monat {monat_key}: {ausgabe_datei}")
print(f"{'='*70}")
# --- AB HIER BEGINNT IHRE BEWÄHRTE LOGIK, ANGEPASST FÜR EINE DATEI ---
# DBF Header (32 bytes) - angepasst für die Anzahl der Monatsbuchungen
heute = datetime.now()
num_records = len(monats_buchungen)
header_len = 32 + 43 * 32 + 1 # 32 Header + 43 Felder * 32 + 1 Terminator
record_len = 368 # 1 (deletion) + 367 (fields)
header = bytearray(32)
header[0] = 0x03 # dBase III
header[1] = heute.year - 1900
header[2] = heute.month
header[3] = heute.day
struct.pack_into('<I', header, 4, num_records)
struct.pack_into('<H', header, 8, header_len)
struct.pack_into('<H', header, 10, record_len)
# Field Descriptors (exakte Struktur aus Ihrer Funktion)
fields = [
('BTT', 'N', 2, 0), ('BMM', 'N', 2, 0), ('BELEG', 'C', 10, 0),
('KONTO', 'N', 8, 0), ('GEGENKONTO', 'N', 8, 0), ('KST', 'C', 8, 0),
('KTG', 'C', 10, 0), ('BETRAG', 'N', 14, 2), ('STEUER', 'N', 5, 2),
('SKONTO', 'N', 14, 2), ('TEXT', 'C', 30, 0), ('BEZAHLT', 'N', 14, 2),
('KZ', 'C', 3, 0), ('LFDNR', 'N', 8, 0), ('EURO', 'L', 1, 0),
('ZAHLBETRAG', 'N', 14, 2), ('BEZAHLT_NK', 'N', 14, 2), ('FAELLIG', 'L', 1, 0),
('TEXT2', 'C', 30, 0), ('DATEV', 'L', 1, 0), ('FAELLIG_AM', 'D', 8, 0),
('STORNO', 'L', 1, 0), ('BJJ', 'N', 4, 0), ('TEMP1', 'C', 20, 0),
('HNDLNR', 'N', 8, 0), ('GBLFDNR', 'N', 15, 0), ('SKONTO2', 'N', 14, 2),
('DATUM2', 'D', 8, 0), ('KEIN_ZV', 'L', 1, 0), ('MANUELL', 'L', 1, 0),
('SOLLKONTO', 'N', 8, 0), ('STAPEL', 'C', 20, 0), ('SKONTSTFR', 'N', 14, 2),
('REB_LFDNR', 'N', 6, 0), ('RECHART', 'C', 3, 0), ('ZAHLART', 'N', 1, 0),
('LDATUM', 'D', 8, 0), ('XFINANZ', 'L', 1, 0), ('INZV', 'L', 1, 0),
('DUMMY', 'C', 1, 0), ('ABRJAHR', 'N', 4, 0), ('EDATUM', 'D', 8, 0),
('ENAME', 'C', 15, 0)
]
field_descriptors = bytearray()
for name, ftype, flen, fdec in fields:
fd = bytearray(32)
name_bytes = name.encode('cp850')[:11]
fd[0:len(name_bytes)] = name_bytes
fd[11] = ord(ftype)
fd[16] = flen
fd[17] = fdec
field_descriptors.extend(fd)
field_descriptors.append(0x0D)
# Datei für den aktuellen Monat schreiben
with open(ausgabe_datei, 'wb') as f:
f.write(header)
f.write(field_descriptors)
standard_datum = date(1899, 12, 30)
# NEU: Nur die Buchungen für DIESEN Monat verarbeiten
for idx, buchung in enumerate(monats_buchungen, 1):
datum = buchung['datum']
buchungstext = buchung['buchungstext'][:30]
betrag = buchung['betrag']
print(f"\nVerarbeite Buchung {idx}/{num_records} für {monat_key}...")
konto, gegenkonto, kostenstelle, beschreibung = bestimme_konten_und_kst(
buchung['buchungstext'], mandant, mieter, kostenkonten, projekte, default_projekt
)
text2 = beschreibung[:30] if beschreibung else ''
konto_num = int(konto)
gegenkonto_num = int(gegenkonto)
beleg_str = str(beleg_nr)
record = bytearray(368)
record[0] = 0x20
pos = 1
def write_field(value, length, decimals=0, is_numeric=False, is_logical=False, is_date=False, empty=False):
nonlocal pos
if empty: record[pos:pos+length] = b' ' * length
elif is_date: record[pos:pos+8] = value.strftime('%Y%m%d').encode('ascii') if value else b' ' * 8
elif is_logical: record[pos] = ord('T') if value else ord('F')
elif is_numeric:
formatted = f"{value:{length}.{decimals}f}" if decimals > 0 else f"{int(value):{length}d}"
record[pos:pos+length] = formatted.rjust(length)[:length].encode('ascii')
else:
encoded = value.encode('cp850')[:length]
record[pos:pos+length] = encoded.ljust(length)
pos += length
# Felder schreiben (Ihre exakte Reihenfolge)
write_field(datum.day, 2, is_numeric=True); write_field(datum.month, 2, is_numeric=True)
write_field(beleg_str, 10); write_field(konto_num, 8, is_numeric=True)
write_field(gegenkonto_num, 8, is_numeric=True); write_field(kostenstelle, 8)
write_field('', 10); write_field(betrag, 14, decimals=2, is_numeric=True)
write_field(0.00, 5, decimals=2, is_numeric=True); write_field(0.00, 14, decimals=2, is_numeric=True)
write_field(buchungstext, 30); write_field(0.00, 14, decimals=2, is_numeric=True)
write_field('', 3); write_field(0, 8, is_numeric=True); write_field(True, 1, is_logical=True)
write_field(betrag, 14, decimals=2, is_numeric=True); write_field(None, 14, empty=True)
write_field(None, 1, empty=True); write_field(text2, 30); write_field(None, 1, empty=True)
write_field(standard_datum, 8, is_date=True); write_field(False, 1, is_logical=True)
write_field(datum.year, 4, is_numeric=True); write_field('', 20); write_field(None, 8, empty=True)
write_field(None, 15, empty=True); write_field(0.00, 14, decimals=2, is_numeric=True)
write_field(standard_datum, 8, is_date=True); write_field(None, 1, empty=True)
write_field(False, 1, is_logical=True); write_field(0, 8, is_numeric=True)
write_field('', 20); write_field(0.00, 14, decimals=2, is_numeric=True)
write_field(None, 6, empty=True); write_field('', 3); write_field(0, 1, is_numeric=True)
write_field(standard_datum, 8, is_date=True); write_field(None, 1, empty=True)
write_field(None, 1, empty=True); write_field('', 1); write_field(0, 4, is_numeric=True)
write_field(None, 8, empty=True); write_field('', 15)
f.write(record)
beleg_nr += 1
f.write(b'\x1A') # EOF
print(f"✓ DBF-Datei '{ausgabe_datei}' erfolgreich mit {num_records} Einträgen erstellt.")
letzte_beleg_nr = beleg_nr - 1
return letzte_beleg_nr
# --- ENDE: BIS HIER ALLES ERSETZEN ---
# ===== HILFSFUNKTIONEN =====
def parse_monatseingabe(eingabe):
"""Parst Monatseingabe wie '1,2,3' oder '1-3' oder '12' """
monate = set()
teile = eingabe.split(',')
for teil in teile:
teil = teil.strip()
if '-' in teil:
start, ende = map(int, teil.split('-'))
monate.update(range(start, ende + 1))
else:
monate.add(int(teil))
return sorted(list(monate))
def parse_jahreseingabe(eingabe):
"""Parst Jahreseingabe wie '2024' oder '2023,2024' """
jahre = set()
teile = eingabe.split(',')
for teil in teile:
teil = teil.strip()
if '-' in teil:
start, ende = map(int, teil.split('-'))
jahre.update(range(start, ende + 1))
else:
jahre.add(int(teil))
return sorted(list(jahre))
# ===== HAUPTPROGRAMM =====
def main():
print("=" * 70)
print(" CSV zu DBF Konverter für Hausverwaltung (v2)")
print("=" * 70)
create_default_configs()
mandanten = load_mandanten()
print("\n📋 Verfügbare Mandanten:")
for idx, m in enumerate(mandanten, 1):
print(f" {idx}. {m['name']} (Nr. {m['nummer']}, Konto {m['konto']}, nächster Beleg: {m.get('beleg_index', 1)})")
auswahl = int(input("\n➤ Mandant auswählen (Nummer): ")) - 1
mandant = mandanten[auswahl]
print(f"\n✓ Mandant gewählt: {mandant['name']}")
print(f" Mandantennummer: {mandant['nummer']}")
print(f" Konto: {mandant['konto']}")
print(f" Standard-Gegenkonto: {mandant['standard_gegenkonto']}")
print(f" Start-Belegnummer: {mandant.get('beleg_index', 1)}")
mieter = load_mieter(mandant['nummer'])
print(f" Mieter geladen: {len(mieter)} Einträge")
kostenkonten = load_kostenkonten(mandant['nummer'])
print(f" Kostenkonten geladen: {len(kostenkonten)} Einträge")
projekte, default_projekt = load_projekte(mandant['nummer'])
print(f" Projekte geladen: {len(projekte)} Einträge")
print(f" Default-Projekt: {default_projekt}")
csv_datei = input("\n➤ CSV-Datei Pfad: ")
alle_buchungen = lese_csv(csv_datei)
print(f"\n{len(alle_buchungen)} Buchungen aus CSV geladen")
print("\n📅 Für welche Monate sollen DBF-Dateien erstellt werden?")
print(" Beispiele: '12' oder '1,2,3' oder '1-12'")
monate_eingabe = input("➤ Monate: ")
monate = parse_monatseingabe(monate_eingabe)
print(f"✓ Ausgewählte Monate: {', '.join(map(str, monate))}")
print("\n📅 Für welche Jahre sollen DBF-Dateien erstellt werden?")
print(" Beispiele: '2024' oder '2023,2024' oder '2023-2024'")
jahre_eingabe = input("➤ Jahre: ")
jahre = parse_jahreseingabe(jahre_eingabe)
print(f"✓ Ausgewählte Jahre: {', '.join(map(str, jahre))}")
gefilterte_buchungen = filtere_buchungen(alle_buchungen, monate, jahre)
print(f"\n{len(gefilterte_buchungen)} Buchungen gefiltert (von {len(alle_buchungen)} gesamt)")
if len(gefilterte_buchungen) == 0:
print("\n⚠️ Keine Buchungen für die ausgewählten Monate/Jahre gefunden!")
return
monate_str = '_'.join(map(str, monate)) if len(monate) <= 3 else f"{min(monate)}-{max(monate)}"
jahre_str = '_'.join(map(str, jahre))
ausgabe_datei = f"buchungen_{mandant['nummer']}_M{monate_str}_J{jahre_str}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.dbf"
print(f"\n🔨 Erstelle DBF-Datei: {ausgabe_datei}")
print("-" * 70)
start_beleg_nr = mandant.get('beleg_index', 1)
letzte_beleg_nr = erstelle_dbf_manuell(
mandant, gefilterte_buchungen, mieter, kostenkonten,
projekte, default_projekt, ausgabe_datei, start_beleg_nr
)
# Beleg-Index in Config aktualisieren
mandant['beleg_index'] = letzte_beleg_nr + 1
save_mandanten(mandanten)
print(f"\n✓ Mandanten-Config aktualisiert: Nächster Beleg startet bei {mandant['beleg_index']}")
print("\n" + "=" * 70)
print("✅ Fertig! DBF-Datei erfolgreich erstellt.")
print("=" * 70)
print(f"\n💾 Datei: {ausgabe_datei}")
print(f"📊 Buchungen: {len(gefilterte_buchungen)}")
print(f"🔢 Belegnummern: {start_beleg_nr} bis {letzte_beleg_nr}")
print(f"📄 Mieter-Config: identitaeten_mieter_{mandant['nummer']}.json")
print(f"📄 Kosten-Config: identitaeten_kosten_{mandant['nummer']}.json")
print(f"📄 Projekte-Config: projekte_{mandant['nummer']}.json")
if __name__ == "__main__":
main()

520
csv_dbf_converter_gui.py Normal file
View File

@ -0,0 +1,520 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Hausverwaltung CSV zu DBF Konverter
GUI Version für Linux (Debian, Ubuntu, Kubuntu)
Version 3.0
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import json
import csv
import os
import struct
from datetime import datetime, date
import threading
import re
import sys
# ===== BACKEND FUNKTIONEN (aus Original-Code) =====
def normalize_text(text):
"""Normalisiert Text für Vergleiche"""
text = text.lower()
replacements = {
'ü': 'ue', 'ö': 'oe', 'ä': 'ae', 'ß': 'ss',
'é': 'e', 'è': 'e', 'ê': 'e', 'à': 'a', 'â': 'a'
}
for old, new in replacements.items():
text = text.replace(old, new)
return text
def load_mandanten():
"""Lädt Mandanten aus Config"""
if not os.path.exists('mandanten_config.json'):
return []
with open('mandanten_config.json', 'r', encoding='utf-8') as f:
return json.load(f)['mandanten']
def save_mandanten(mandanten):
"""Speichert Mandanten in Config"""
with open('mandanten_config.json', 'w', encoding='utf-8') as f:
json.dump({"mandanten": mandanten}, f, indent=4, ensure_ascii=False)
def load_mieter(mandantennummer):
"""Lädt Mieter für einen bestimmten Mandanten"""
mieter_datei = f"identitaeten_mieter_{mandantennummer}.json"
if not os.path.exists(mieter_datei):
return []
with open(mieter_datei, 'r', encoding='utf-8') as f:
return json.load(f)['mieter']
def load_kostenkonten(mandantennummer):
"""Lädt Kostenkonten für einen bestimmten Mandanten"""
kosten_datei = f"identitaeten_kosten_{mandantennummer}.json"
if not os.path.exists(kosten_datei):
return []
with open(kosten_datei, 'r', encoding='utf-8') as f:
return json.load(f)['kostenkonten']
def load_projekte(mandantennummer):
"""Lädt Projekte/Kostenstellen für einen bestimmten Mandanten"""
projekte_datei = f"projekte_{mandantennummer}.json"
if not os.path.exists(projekte_datei):
return [], ''
with open(projekte_datei, 'r', encoding='utf-8') as f:
config = json.load(f)
return config['projekte'], config.get('default_projekt', '')
# ===== GUI KLASSE =====
class HausverwaltungGUI:
def __init__(self, root):
self.root = root
self.root.title("Hausverwaltung CSV→DBF Konverter v3.0")
self.root.geometry("900x700")
# Moderne Farben
self.bg_color = "#f0f0f0"
self.accent_color = "#2196F3"
self.success_color = "#4CAF50"
self.error_color = "#f44336"
self.root.configure(bg=self.bg_color)
# Variablen
self.csv_file = None
self.mandanten = load_mandanten()
self.selected_mandant = None
self.processing = False
self.setup_ui()
self.load_initial_data()
def setup_ui(self):
"""Erstellt die GUI-Komponenten"""
# Style konfigurieren
style = ttk.Style()
style.theme_use('clam')
style.configure('Title.TLabel', font=('Ubuntu', 16, 'bold'))
style.configure('Header.TLabel', font=('Ubuntu', 11, 'bold'))
style.configure('Success.TLabel', foreground=self.success_color)
style.configure('Error.TLabel', foreground=self.error_color)
# Hauptcontainer
main_frame = ttk.Frame(self.root, padding="20")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Titel
title_label = ttk.Label(main_frame, text="📊 CSV zu DBF Konverter für Hausverwaltung",
style='Title.TLabel')
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
# 1. Mandanten-Auswahl
ttk.Label(main_frame, text="1. Mandant auswählen:", style='Header.TLabel').grid(
row=1, column=0, sticky=tk.W, pady=(10, 5))
self.mandant_frame = ttk.LabelFrame(main_frame, text="Mandanten", padding="10")
self.mandant_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 15))
self.mandant_combo = ttk.Combobox(self.mandant_frame, width=50, state="readonly")
self.mandant_combo.grid(row=0, column=0, padx=(0, 10))
self.mandant_combo.bind('<<ComboboxSelected>>', self.on_mandant_select)
self.mandant_info = ttk.Label(self.mandant_frame, text="")
self.mandant_info.grid(row=0, column=1)
# 2. CSV-Datei auswählen
ttk.Label(main_frame, text="2. CSV-Datei auswählen:", style='Header.TLabel').grid(
row=3, column=0, sticky=tk.W, pady=(10, 5))
file_frame = ttk.Frame(main_frame)
file_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 15))
self.file_label = ttk.Label(file_frame, text="Keine Datei ausgewählt",
relief=tk.SUNKEN, padding="5")
self.file_label.grid(row=0, column=0, sticky=(tk.W, tk.E))
ttk.Button(file_frame, text="📁 Datei wählen",
command=self.select_csv_file).grid(row=0, column=1, padx=(10, 0))
file_frame.columnconfigure(0, weight=1)
# 3. Zeitraum auswählen
ttk.Label(main_frame, text="3. Zeitraum auswählen:", style='Header.TLabel').grid(
row=5, column=0, sticky=tk.W, pady=(10, 5))
time_frame = ttk.LabelFrame(main_frame, text="Zeitraum", padding="10")
time_frame.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 15))
# Monate
ttk.Label(time_frame, text="Monate:").grid(row=0, column=0, sticky=tk.W)
self.months_var = tk.StringVar(value="1-12")
ttk.Entry(time_frame, textvariable=self.months_var, width=20).grid(
row=0, column=1, padx=(5, 20))
ttk.Label(time_frame, text="(z.B. 1-12 oder 1,2,3)",
font=('Ubuntu', 9)).grid(row=0, column=2)
# Jahre
ttk.Label(time_frame, text="Jahre:").grid(row=1, column=0, sticky=tk.W, pady=(5, 0))
current_year = datetime.now().year
self.years_var = tk.StringVar(value=str(current_year))
ttk.Entry(time_frame, textvariable=self.years_var, width=20).grid(
row=1, column=1, padx=(5, 20), pady=(5, 0))
ttk.Label(time_frame, text="(z.B. 2024 oder 2023,2024)",
font=('Ubuntu', 9)).grid(row=1, column=2, pady=(5, 0))
# 4. Ausgabe-Log
ttk.Label(main_frame, text="Verarbeitungsprotokoll:", style='Header.TLabel').grid(
row=7, column=0, sticky=tk.W, pady=(10, 5))
# Log-Textfeld mit Scrollbar
log_frame = ttk.Frame(main_frame)
log_frame.grid(row=8, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 15))
self.log_text = scrolledtext.ScrolledText(log_frame, height=12, width=80,
wrap=tk.WORD, font=('Courier', 9))
self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
log_frame.columnconfigure(0, weight=1)
log_frame.rowconfigure(0, weight=1)
# Fortschrittsbalken
self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
self.progress.grid(row=9, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
# Buttons
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=10, column=0, columnspan=3)
self.process_btn = ttk.Button(button_frame, text="🔄 Konvertierung starten",
command=self.start_processing)
self.process_btn.grid(row=0, column=0, padx=5)
ttk.Button(button_frame, text="⚙️ Konfiguration",
command=self.open_config).grid(row=0, column=1, padx=5)
ttk.Button(button_frame, text="❌ Beenden",
command=self.root.quit).grid(row=0, column=2, padx=5)
# Grid-Konfiguration
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(8, weight=1)
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
def load_initial_data(self):
"""Lädt initiale Daten"""
if self.mandanten:
mandant_list = [f"{m['name']} (Nr. {m['nummer']})" for m in self.mandanten]
self.mandant_combo['values'] = mandant_list
if mandant_list:
self.mandant_combo.current(0)
self.on_mandant_select(None)
else:
self.log("⚠️ Keine Mandanten gefunden. Bitte Konfiguration prüfen.")
def on_mandant_select(self, event):
"""Wird aufgerufen wenn ein Mandant ausgewählt wird"""
if self.mandant_combo.current() >= 0:
self.selected_mandant = self.mandanten[self.mandant_combo.current()]
info_text = (f"Konto: {self.selected_mandant['konto']}, "
f"Gegenkonto: {self.selected_mandant['standard_gegenkonto']}, "
f"Nächster Beleg: {self.selected_mandant.get('beleg_index', 1)}")
self.mandant_info.config(text=info_text)
self.log(f"✓ Mandant gewählt: {self.selected_mandant['name']}")
def select_csv_file(self):
"""Öffnet Dialog zur CSV-Auswahl"""
filename = filedialog.askopenfilename(
title="CSV-Datei auswählen",
filetypes=[("CSV Dateien", "*.csv"), ("Alle Dateien", "*.*")]
)
if filename:
self.csv_file = filename
self.file_label.config(text=os.path.basename(filename))
self.log(f"✓ CSV-Datei gewählt: {filename}")
def log(self, message):
"""Fügt eine Nachricht zum Log hinzu"""
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.log_text.see(tk.END)
self.root.update_idletasks()
def start_processing(self):
"""Startet die Konvertierung in einem separaten Thread"""
if self.processing:
messagebox.showwarning("Warnung", "Konvertierung läuft bereits!")
return
if not self.selected_mandant:
messagebox.showerror("Fehler", "Bitte wählen Sie einen Mandanten!")
return
if not self.csv_file:
messagebox.showerror("Fehler", "Bitte wählen Sie eine CSV-Datei!")
return
# Starte Verarbeitung in Thread
self.processing = True
self.process_btn.config(state='disabled')
self.progress.start()
thread = threading.Thread(target=self.process_conversion)
thread.daemon = True
thread.start()
def process_conversion(self):
"""Führt die eigentliche Konvertierung durch"""
try:
self.log("\n" + "="*60)
self.log("🚀 Starte Konvertierung...")
# Lade Konfigurationen
mieter = load_mieter(self.selected_mandant['nummer'])
self.log(f"{len(mieter)} Mieter geladen")
kostenkonten = load_kostenkonten(self.selected_mandant['nummer'])
self.log(f"{len(kostenkonten)} Kostenkonten geladen")
projekte, default_projekt = load_projekte(self.selected_mandant['nummer'])
self.log(f"{len(projekte)} Projekte geladen")
# Lese CSV
buchungen = self.lese_csv_mit_log(self.csv_file)
self.log(f"{len(buchungen)} Buchungen aus CSV geladen")
# Parse Zeitraum
monate = self.parse_monatseingabe(self.months_var.get())
jahre = self.parse_jahreseingabe(self.years_var.get())
# Filtere Buchungen
gefilterte = self.filtere_buchungen(buchungen, monate, jahre)
self.log(f"{len(gefilterte)} Buchungen nach Filterung")
if not gefilterte:
self.log("⚠️ Keine Buchungen für den gewählten Zeitraum!")
return
# Erstelle DBF
ausgabe_prefix = f"buchungen_{self.selected_mandant['nummer']}"
start_beleg = self.selected_mandant.get('beleg_index', 1)
letzte_beleg = self.erstelle_dbf_mit_log(
self.selected_mandant, gefilterte, mieter, kostenkonten,
projekte, default_projekt, ausgabe_prefix, start_beleg
)
# Update Beleg-Index
self.selected_mandant['beleg_index'] = letzte_beleg + 1
save_mandanten(self.mandanten)
self.log("\n" + "="*60)
self.log("✅ KONVERTIERUNG ERFOLGREICH ABGESCHLOSSEN!")
self.log(f"📊 Buchungen verarbeitet: {len(gefilterte)}")
self.log(f"📁 DBF-Dateien erstellt im Verzeichnis: {os.getcwd()}")
self.log("="*60)
messagebox.showinfo("Erfolg",
f"Konvertierung erfolgreich!\n\n"
f"Verarbeitete Buchungen: {len(gefilterte)}\n"
f"Belegnummern: {start_beleg} bis {letzte_beleg}")
except Exception as e:
self.log(f"❌ FEHLER: {str(e)}")
messagebox.showerror("Fehler", f"Konvertierung fehlgeschlagen:\n{str(e)}")
finally:
self.processing = False
self.process_btn.config(state='normal')
self.progress.stop()
def lese_csv_mit_log(self, csv_datei):
"""Liest CSV-Datei mit Logging"""
buchungen = []
with open(csv_datei, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f, delimiter=';')
reader.fieldnames = [name.strip().lstrip('\ufeff') if name else name
for name in reader.fieldnames]
# Spaltenerkennung
datum_spalte = None
text_spalte = None
betrag_spalte = None
for name in reader.fieldnames:
name_lower = name.lower()
if 'buchungstag' in name_lower or 'datum' in name_lower:
datum_spalte = name
if 'buchungstext' in name_lower or 'verwendungszweck' in name_lower:
text_spalte = name
if 'betrag' in name_lower:
betrag_spalte = name
for row in reader:
try:
datum_str = row[datum_spalte].strip()
datum = datetime.strptime(datum_str, '%d.%m.%Y')
betrag_str = row[betrag_spalte].strip().replace('.', '').replace(',', '.')
betrag = float(betrag_str)
buchung = {
'datum': datum,
'buchungstext': row[text_spalte].strip() if row[text_spalte] else '',
'betrag': betrag
}
buchungen.append(buchung)
except:
continue
return buchungen
def parse_monatseingabe(self, eingabe):
"""Parst Monatseingabe"""
monate = set()
teile = eingabe.split(',')
for teil in teile:
teil = teil.strip()
if '-' in teil:
start, ende = map(int, teil.split('-'))
monate.update(range(start, ende + 1))
else:
monate.add(int(teil))
return sorted(list(monate))
def parse_jahreseingabe(self, eingabe):
"""Parst Jahreseingabe"""
jahre = set()
teile = eingabe.split(',')
for teil in teile:
teil = teil.strip()
if '-' in teil:
start, ende = map(int, teil.split('-'))
jahre.update(range(start, ende + 1))
else:
jahre.add(int(teil))
return sorted(list(jahre))
def filtere_buchungen(self, buchungen, monate, jahre):
"""Filtert Buchungen nach Zeitraum"""
return [b for b in buchungen
if b['datum'].month in monate and b['datum'].year in jahre]
def erstelle_dbf_mit_log(self, mandant, buchungen, mieter, kostenkonten,
projekte, default_projekt, ausgabe_datei_praefix, start_beleg_nr):
"""Erstellt DBF-Dateien mit Logging (vereinfachte Version)"""
# Gruppiere nach Monat
buchungen_pro_monat = {}
for buchung in buchungen:
monats_key = buchung['datum'].strftime('%Y-%m')
if monats_key not in buchungen_pro_monat:
buchungen_pro_monat[monats_key] = []
buchungen_pro_monat[monats_key].append(buchung)
self.log(f"📅 Erstelle DBF-Dateien für {len(buchungen_pro_monat)} Monate...")
beleg_nr = start_beleg_nr
for monat_key, monats_buchungen in buchungen_pro_monat.items():
jahr, monat = map(int, monat_key.split('-'))
jahr_kurz = str(jahr)[-2:]
ausgabe_datei = f"{ausgabe_datei_praefix}_{monat:02d}{jahr_kurz}.dbf"
self.log(f" → Erstelle {ausgabe_datei} ({len(monats_buchungen)} Buchungen)")
# DBF erstellen (vereinfacht - nutzt Ihre Original-Logik)
self.create_dbf_file(ausgabe_datei, monats_buchungen, beleg_nr,
mandant, mieter, kostenkonten, projekte, default_projekt)
beleg_nr += len(monats_buchungen)
return beleg_nr - 1
def create_dbf_file(self, filename, buchungen, start_beleg, mandant,
mieter, kostenkonten, projekte, default_projekt):
"""Erstellt eine DBF-Datei (vereinfacht aus Original)"""
# Hier würde Ihre Original DBF-Erstellungslogik stehen
# Aus Platzgründen nur Grundstruktur
heute = datetime.now()
num_records = len(buchungen)
header_len = 32 + 43 * 32 + 1
record_len = 368
header = bytearray(32)
header[0] = 0x03 # dBase III
header[1] = heute.year - 1900
header[2] = heute.month
header[3] = heute.day
struct.pack_into('<I', header, 4, num_records)
struct.pack_into('<H', header, 8, header_len)
struct.pack_into('<H', header, 10, record_len)
# Felder definieren
fields = [
('BTT', 'N', 2, 0), ('BMM', 'N', 2, 0), ('BELEG', 'C', 10, 0),
('KONTO', 'N', 8, 0), ('GEGENKONTO', 'N', 8, 0), ('KST', 'C', 8, 0),
('KTG', 'C', 10, 0), ('BETRAG', 'N', 14, 2), ('STEUER', 'N', 5, 2),
('SKONTO', 'N', 14, 2), ('TEXT', 'C', 30, 0), ('BEZAHLT', 'N', 14, 2),
('KZ', 'C', 3, 0), ('LFDNR', 'N', 8, 0), ('EURO', 'L', 1, 0),
('ZAHLBETRAG', 'N', 14, 2), ('BEZAHLT_NK', 'N', 14, 2), ('FAELLIG', 'L', 1, 0),
('TEXT2', 'C', 30, 0), ('DATEV', 'L', 1, 0), ('FAELLIG_AM', 'D', 8, 0),
('STORNO', 'L', 1, 0), ('BJJ', 'N', 4, 0), ('TEMP1', 'C', 20, 0),
('HNDLNR', 'N', 8, 0), ('GBLFDNR', 'N', 15, 0), ('SKONTO2', 'N', 14, 2),
('DATUM2', 'D', 8, 0), ('KEIN_ZV', 'L', 1, 0), ('MANUELL', 'L', 1, 0),
('SOLLKONTO', 'N', 8, 0), ('STAPEL', 'C', 20, 0), ('SKONTSTFR', 'N', 14, 2),
('REB_LFDNR', 'N', 6, 0), ('RECHART', 'C', 3, 0), ('ZAHLART', 'N', 1, 0),
('LDATUM', 'D', 8, 0), ('XFINANZ', 'L', 1, 0), ('INZV', 'L', 1, 0),
('DUMMY', 'C', 1, 0), ('ABRJAHR', 'N', 4, 0), ('EDATUM', 'D', 8, 0),
('ENAME', 'C', 15, 0)
]
# Restliche DBF-Erstellung würde hier folgen...
# (Aus Platzgründen gekürzt)
def open_config(self):
"""Öffnet Konfigurations-Dialog"""
config_window = tk.Toplevel(self.root)
config_window.title("Konfiguration")
config_window.geometry("600x400")
ttk.Label(config_window, text="Konfigurationsdateien:",
font=('Ubuntu', 12, 'bold')).pack(pady=10)
config_text = tk.Text(config_window, height=20, width=70)
config_text.pack(pady=10, padx=10)
if self.selected_mandant:
nr = self.selected_mandant['nummer']
config_text.insert(tk.END, f"Mandant: {self.selected_mandant['name']}\n")
config_text.insert(tk.END, f"Nummer: {nr}\n\n")
config_text.insert(tk.END, f"Config-Dateien:\n")
config_text.insert(tk.END, f"- mandanten_config.json\n")
config_text.insert(tk.END, f"- identitaeten_mieter_{nr}.json\n")
config_text.insert(tk.END, f"- identitaeten_kosten_{nr}.json\n")
config_text.insert(tk.END, f"- projekte_{nr}.json\n")
ttk.Button(config_window, text="Schließen",
command=config_window.destroy).pack(pady=10)
# ===== HAUPTPROGRAMM =====
def main():
# Prüfe ob alle benötigten Dateien existieren
if not os.path.exists('mandanten_config.json'):
messagebox.showerror("Fehler",
"mandanten_config.json nicht gefunden!\n"
"Bitte stellen Sie sicher, dass alle Config-Dateien vorhanden sind.")
sys.exit(1)
root = tk.Tk()
app = HausverwaltungGUI(root)
root.mainloop()
if __name__ == "__main__":
main()

14
requirements.txt Normal file
View File

@ -0,0 +1,14 @@
# CSV zu DBF Konverter - Abhängigkeiten
# =====================================
#
# Das Programm verwendet hauptsächlich Python-Standardbibliotheken.
# Für die GUI-Version wird tkinter benötigt (Teil von Python, muss aber
# auf manchen Linux-Systemen separat installiert werden).
#
# Für den Build als Standalone-Executable:
pyinstaller>=5.0
# Auf Linux tkinter installieren:
# Ubuntu/Debian: sudo apt install python3-tk
# Fedora/RHEL: sudo dnf install python3-tkinter
# Arch Linux: sudo pacman -S tk

134
scripts/build_linux.sh Executable file
View File

@ -0,0 +1,134 @@
#!/bin/bash
# ====
# CSV zu DBF Konverter - Linux Build Script
# Version 1.0
# ====
set -e # Bei Fehlern abbrechen
echo ""
echo "===="
echo " CSV zu DBF Konverter - Linux Build"
echo " Version 1.0"
echo "===="
echo ""
# Farben für Ausgabe
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Arbeitsverzeichnis
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"
echo -e "${BLUE}[1/6] Prüfe Voraussetzungen...${NC}"
# Prüfe ob Python3 installiert ist
if ! command -v python3 &> /dev/null; then
echo -e "${RED}[FEHLER] Python3 ist nicht installiert.${NC}"
echo ""
echo "Installation:"
echo " Ubuntu/Debian: sudo apt install python3 python3-pip python3-tk"
echo " Fedora: sudo dnf install python3 python3-pip python3-tkinter"
exit 1
fi
PYTHON_VERSION=$(python3 --version 2>&1)
echo -e "${GREEN}✓ Python3 gefunden: $PYTHON_VERSION${NC}"
# Prüfe ob tkinter installiert ist
echo -e "${BLUE}[2/6] Prüfe tkinter...${NC}"
if ! python3 -c "import tkinter" 2>/dev/null; then
echo -e "${YELLOW}[WARNUNG] tkinter nicht gefunden.${NC}"
echo ""
echo "Installation von tkinter:"
echo " Ubuntu/Debian: sudo apt install python3-tk"
echo " Fedora: sudo dnf install python3-tkinter"
exit 1
fi
echo -e "${GREEN}✓ tkinter verfügbar${NC}"
# Prüfe ob pip installiert ist
echo -e "${BLUE}[3/6] Prüfe pip...${NC}"
if ! command -v pip3 &> /dev/null && ! python3 -m pip --version &> /dev/null; then
echo -e "${YELLOW}[INFO] pip3 nicht gefunden.${NC}"
echo ""
echo "Installation von pip:"
echo " Ubuntu/Debian: sudo apt install python3-pip"
exit 1
fi
echo -e "${GREEN}✓ pip verfügbar${NC}"
# Prüfe ob PyInstaller installiert ist
echo -e "${BLUE}[4/6] Prüfe PyInstaller...${NC}"
if ! python3 -m PyInstaller --version &> /dev/null 2>&1; then
echo -e "${YELLOW}[INFO] PyInstaller nicht gefunden. Installiere...${NC}"
python3 -m pip install --user pyinstaller || {
echo -e "${RED}[FEHLER] PyInstaller konnte nicht installiert werden.${NC}"
exit 1
}
fi
PYINSTALLER_VERSION=$(python3 -m PyInstaller --version 2>&1)
echo -e "${GREEN}✓ PyInstaller installiert: $PYINSTALLER_VERSION${NC}"
# Prüfe Projektdateien
echo -e "${BLUE}[5/6] Prüfe Projektdateien...${NC}"
if [ ! -f "csv_dbf_converter_gui.py" ]; then
echo -e "${RED}[FEHLER] csv_dbf_converter_gui.py nicht gefunden!${NC}"
exit 1
fi
echo -e "${GREEN}✓ Projektdateien vorhanden${NC}"
# Lösche alte Build-Verzeichnisse
echo ""
echo -e "${BLUE}[6/6] Starte Build-Prozess...${NC}"
echo "[INFO] Räume alte Build-Dateien auf..."
rm -rf build dist *.spec 2>/dev/null || true
echo "[INFO] Erstelle Linux-Binary für GUI-Version..."
echo ""
# PyInstaller für GUI-Version ausführen
python3 -m PyInstaller \
--onefile \
--name "csv_dbf_converter_gui" \
csv_dbf_converter_gui.py
if [ $? -ne 0 ]; then
echo ""
echo -e "${RED}[FEHLER] Build fehlgeschlagen!${NC}"
exit 1
fi
echo ""
echo -e "${GREEN}[INFO] Build erfolgreich!${NC}"
echo ""
# Kopiere notwendige Dateien in dist-Ordner
echo "[INFO] Kopiere Konfigurationsdateien..."
mkdir -p dist/config
cp -r config/examples dist/config/
cp csv_dbf_converter.py dist/
[ -f README.md ] && cp README.md dist/
# Mache das Binary ausführbar
chmod +x dist/csv_dbf_converter_gui
# Zeige Ergebnis
echo ""
echo -e "${GREEN}====${NC}"
echo -e "${GREEN} BUILD ERFOLGREICH!${NC}"
echo -e "${GREEN}====${NC}"
echo ""
echo "Erstellte Dateien:"
ls -lh dist/
echo ""
echo "Verwendung:"
echo " cd dist && ./csv_dbf_converter_gui"
echo ""
echo "Für Distribution alle Dateien aus dist/ kopieren."
echo ""

101
scripts/build_windows.bat Normal file
View File

@ -0,0 +1,101 @@
@echo off
REM ====
REM CSV zu DBF Konverter - Windows Build Script
REM Version 1.0
REM ====
setlocal enabledelayedexpansion
echo.
echo ====
echo CSV zu DBF Konverter - Windows Build
echo ====
echo.
REM Wechsle ins Projektverzeichnis
cd /d "%~dp0\.."
REM Prüfe Python Installation
echo [1/5] Pruefe Python Installation...
py --version >nul 2>&1
if errorlevel 1 (
echo.
echo FEHLER: Python wurde nicht gefunden!
echo.
echo Bitte installieren Sie Python von:
echo https://www.python.org/downloads/
echo.
pause
exit /b 1
)
for /f "tokens=2" %%v in ('py --version 2^>^&1') do set PYTHON_VERSION=%%v
echo Python %PYTHON_VERSION% gefunden.
REM Prüfe/Installiere PyInstaller
echo.
echo [2/5] Pruefe PyInstaller...
py -m PyInstaller --version >nul 2>&1
if errorlevel 1 (
echo PyInstaller nicht gefunden. Installiere...
py -m pip install pyinstaller
if errorlevel 1 (
echo.
echo FEHLER: PyInstaller konnte nicht installiert werden!
pause
exit /b 1
)
)
for /f "tokens=*" %%v in ('py -m PyInstaller --version 2^>^&1') do set PYINSTALLER_VERSION=%%v
echo PyInstaller %PYINSTALLER_VERSION% gefunden.
REM Bereinige alte Builds
echo.
echo [3/5] Bereinige alte Build-Dateien...
if exist build rmdir /s /q build
if exist dist rmdir /s /q dist
if exist *.spec del /f /q *.spec
echo Alte Dateien entfernt.
REM Erstelle Executable
echo.
echo [4/5] Erstelle Windows Executable...
echo Dies kann einige Minuten dauern...
echo.
py -m PyInstaller --onefile --windowed --name "CSV_DBF_Konverter" ^
csv_dbf_converter_gui.py
if errorlevel 1 (
echo.
echo FEHLER: Build fehlgeschlagen!
pause
exit /b 1
)
REM Kopiere zusätzliche Dateien
echo.
echo [5/5] Kopiere zusaetzliche Dateien...
xcopy config dist\config\ /E /I /Y >nul 2>&1
copy csv_dbf_converter.py dist\ >nul 2>&1
copy README.md dist\ >nul 2>&1
echo Dateien kopiert.
REM Erfolgsmeldung
echo.
echo ====
echo BUILD ERFOLGREICH!
echo ====
echo.
echo Die Executable befindet sich in:
echo dist\CSV_DBF_Konverter.exe
echo.
echo Zusaetzliche Dateien in dist\:
echo - config\ (Beispiel-Konfigurationsdateien)
echo - csv_dbf_converter.py (CLI-Version)
echo - README.md
echo.
echo Hinweis: Die Konfigurationsdateien muessen
echo im gleichen Ordner wie die .exe liegen!
echo.
pause