Compare commits
5
Commits
v1.2.1
...
34730276d7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34730276d7 | ||
|
|
c12b15e021 | ||
|
|
d027bedcb8 | ||
|
|
eb1dd45e57 | ||
|
|
6f1d807f3c |
No files matched your search
@@ -7,6 +7,31 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||
|
||||
---
|
||||
|
||||
## [1.3] – 2026-08-11
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
- **Monatsauswahl pro Mitarbeiter**: Tätigkeitsnachweise können nun für individuell gewählte Monate erzeugt werden.
|
||||
- Die Monatsauswahl erscheint rechts neben der Mitarbeiterliste für die zuletzt angeklickte Person.
|
||||
- Die Auswahl wird in Kurzform direkt hinter dem Namen angezeigt, z. B. `(Jan, Mär, Dez)`.
|
||||
- Standardmäßig sind die letzten zwei vorhandenen Monate ausgewählt.
|
||||
- Schaltflächen zum Auswählen bzw. Abwählen aller Monate ergänzen die Monatsliste.
|
||||
|
||||
- **Versionsprüfung**: Die App prüft beim Start im zentralen SMB-Verzeichnis auf neuere
|
||||
`Tätigkeitsnachweis-Generator vX.Y.Z.zip`- und künftig auch `.exe`-Dateien.
|
||||
|
||||
- **Versionsanzeige**: Die aktuell installierte Programmversion wird dezent am unteren rechten Rand der App angezeigt.
|
||||
|
||||
### Geändert
|
||||
|
||||
- **Mitarbeiterauswahl**: Mitarbeiter sind nach dem Einlesen einer Input-Datei nicht mehr standardmäßig ausgewählt.
|
||||
Die Buttons zum Auswählen bzw. Abwählen aller Mitarbeiter befinden sich direkt über der Mitarbeiterliste.
|
||||
|
||||
- **Update-Hinweis**: Bei einer neueren zentralen Version wird ein schwebender Hinweis oben rechts angezeigt,
|
||||
einschließlich Schließen- und Ordner-Schaltfläche.
|
||||
|
||||
---
|
||||
|
||||
## [1.2.1] – 2026-06-30
|
||||
|
||||
### Behoben
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
# Tätigkeitsnachweis-Generator
|
||||
|
||||
Streamlit-App zur automatisierten Erstellung von Tätigkeitsnachweisen aus Excel-Daten.
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Python 3.9+ installieren** (falls nicht vorhanden): https://python.org
|
||||
2. **Virtuelle Umgebung erstellen und Abhängigkeiten installieren:**
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate # Windows
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **App starten:**
|
||||
|
||||
```bash
|
||||
streamlit run app.py
|
||||
```
|
||||
|
||||
Oder einfach `start.bat` (Windows) bzw. `./start.sh` (macOS/Linux) ausführen.
|
||||
|
||||
## Cold Run – Headless-Generierung
|
||||
|
||||
`coldrun.py` erzeugt ohne GUI einen Tätigkeitsnachweis für die erste Person –
|
||||
automatisch mit der ersten Input- und Template-Datei. Ergebnis wird auf macOS
|
||||
im Finder geöffnet.
|
||||
|
||||
```bash
|
||||
# Kommandozeile
|
||||
.venv/bin/python coldrun.py
|
||||
|
||||
# oder per Doppelklick (macOS)
|
||||
coldrun.command
|
||||
```
|
||||
|
||||
## Bedienung
|
||||
|
||||
1. **Input-Datei wählen** – Dropdown zeigt alle `.xlsx`-Dateien aus dem `input/`-Ordner
|
||||
- Nach Auswahl wird die Datei sofort validiert
|
||||
- Angezeigt werden: Anzahl Mitarbeiter, Antragsnummern, Einträge pro Monat
|
||||
2. **Template wählen** – Dropdown zeigt alle `.xlsx`-Dateien aus dem `template/`-Ordner
|
||||
- Nach Auswahl wird das Template sofort validiert
|
||||
- Angezeigt wird: Anzahl gefundener Zielfelder
|
||||
3. **Mitarbeiter auswählen** – Multiselect-Liste aller gefundenen Mitarbeiter
|
||||
- Standardmäßig sind alle ausgewählt
|
||||
- Nicht selektierte Mitarbeiter werden bei der Generierung übersprungen
|
||||
4. **"Tätigkeitsnachweise generieren"** klicken
|
||||
- Die generierten Dateien werden im `output/`-Ordner abgelegt
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Alle Einstellungen liegen im `config/`-Ordner:
|
||||
|
||||
### `settings.ini`
|
||||
- `[paths]` – Ordner für Input, Template, Output
|
||||
- `[output]` – Dateinamen-Pattern (`{first_name}`, `{last_name}`)
|
||||
- `[parsing]` – Monats-Sheet-Erkennung, Leerzeilen-Verhalten
|
||||
|
||||
### `input-fields.json`
|
||||
Definiert die Spalten der Input-Tabelle:
|
||||
- `column`: Spaltenbuchstabe (A, B, C…)
|
||||
- `field_name`: Exakte Spaltenüberschrift aus der Excel-Datei
|
||||
- `type`: `string` oder `number`
|
||||
- `required`: `true` oder `false`
|
||||
|
||||
### `output-fields.json`
|
||||
Mapping von Input-Feldern auf Template-Zellen:
|
||||
- `source_field`: Exakte Spaltenüberschrift aus `input-fields.json`
|
||||
- `target_cell`: Zelladresse im Template (z. B. `"C16"`)
|
||||
- `format`: Optionaler Format-String (z. B. `"%.2f"` für Prozentwerte)
|
||||
|
||||
## Erwartetes Input-Format
|
||||
|
||||
- Eine `.xlsx`-Datei mit einem Sheet pro Monat (z. B. "Januar 2025", "Februar 2025", …)
|
||||
- Jedes Sheet hat in Zeile 1 die Spaltenüberschriften (laut `input-fields.json`)
|
||||
- Daten beginnen ab Zeile 2
|
||||
- Jede Zeile entspricht einer Mitarbeiter-Funktion-Kombination für diesen Monat
|
||||
|
||||
## Output
|
||||
|
||||
- Pro selektiertem Mitarbeiter wird eine `.xlsx`-Datei erstellt
|
||||
- Jeder Monat wird als eigenes Sheet (Kopie des Templates) eingefügt
|
||||
- Dateiname: `{Nachname}_{Vorname}_Tätigkeitsnachweis.xlsx`
|
||||
|
||||
## Tests
|
||||
|
||||
End-to-End-Tests parsen die reale Input-Datei (`input/2025 JM.xlsx`) und das reale
|
||||
Template (`template/Tätigkeitsnachweise Stellenförderung.xlsx`) und validieren
|
||||
den gesamten Generierungs-Workflow.
|
||||
|
||||
```bash
|
||||
# Test-Abhängigkeiten installieren
|
||||
.venv/bin/pip install pytest
|
||||
|
||||
# Tests ausführen
|
||||
.venv/bin/python -m pytest tests/test_e2e.py -v
|
||||
```
|
||||
|
||||
Die Tests prüfen:
|
||||
- Vorhandensein der Input- und Template-Dateien
|
||||
- Korrekte Parsierung (168 Datensätze, 19 Personen)
|
||||
- Vollständige Pipeline für die erste Person („Draßdo, Cornelia"): 12 Monats-Sheets
|
||||
mit allen Zellinhalten (Antragsnummer, Name, Stellenanteil, Qualifikation, Monat,
|
||||
Funktion, Tätigkeit, Ort, Datum)
|
||||
+7
-57
@@ -1,40 +1,10 @@
|
||||
|
||||
# Tätigkeitsnachweis-Generator
|
||||
|
||||
Streamlit-App zur automatisierten Erstellung von Tätigkeitsnachweisen aus Excel-Daten.
|
||||
Programm zur automatisierten Erstellung von Tätigkeitsnachweisen aus Excel-Daten.
|
||||
Die aktuellesten Programmversion find sich immer hier:
|
||||
"j:\Fachbereiche\BO\Produktionsschule\Maßnahmeunterlagen\20_Personal\05_Tätigkeitsnachweise ESF\Nachweise-automatisiert-generieren\"
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Python 3.9+ installieren** (falls nicht vorhanden): https://python.org
|
||||
2. **Virtuelle Umgebung erstellen und Abhängigkeiten installieren:**
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate # Windows
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **App starten:**
|
||||
|
||||
```bash
|
||||
streamlit run app.py
|
||||
```
|
||||
|
||||
Oder einfach `start.bat` (Windows) bzw. `./start.sh` (macOS/Linux) ausführen.
|
||||
|
||||
## Cold Run – Headless-Generierung
|
||||
|
||||
`coldrun.py` erzeugt ohne GUI einen Tätigkeitsnachweis für die erste Person –
|
||||
automatisch mit der ersten Input- und Template-Datei. Ergebnis wird auf macOS
|
||||
im Finder geöffnet.
|
||||
|
||||
```bash
|
||||
# Kommandozeile
|
||||
.venv/bin/python coldrun.py
|
||||
|
||||
# oder per Doppelklick (macOS)
|
||||
coldrun.command
|
||||
```
|
||||
Um das Programm zu starten: Doppelklick auf `Tätigkeitsnachweis-Generator.exe`
|
||||
|
||||
## Bedienung
|
||||
|
||||
@@ -45,9 +15,10 @@ coldrun.command
|
||||
- Nach Auswahl wird das Template sofort validiert
|
||||
- Angezeigt wird: Anzahl gefundener Zielfelder
|
||||
3. **Mitarbeiter auswählen** – Multiselect-Liste aller gefundenen Mitarbeiter
|
||||
- Standardmäßig sind alle ausgewählt
|
||||
- Standardmäßig ist keiner ausgewählt
|
||||
- Nicht selektierte Mitarbeiter werden bei der Generierung übersprungen
|
||||
4. **"Tätigkeitsnachweise generieren"** klicken
|
||||
4. **Mitarbeitermonate auswählen** – Multiselect-Liste aller verfügbaren Monate im Dokument. Vorselektiert sind die aktuell letzten beiden Monate.
|
||||
5. **"Tätigkeitsnachweise generieren"** klicken
|
||||
- Die generierten Dateien werden im `output/`-Ordner abgelegt
|
||||
|
||||
## Konfiguration
|
||||
@@ -84,24 +55,3 @@ Mapping von Input-Feldern auf Template-Zellen:
|
||||
- Pro selektiertem Mitarbeiter wird eine `.xlsx`-Datei erstellt
|
||||
- Jeder Monat wird als eigenes Sheet (Kopie des Templates) eingefügt
|
||||
- Dateiname: `{Nachname}_{Vorname}_Tätigkeitsnachweis.xlsx`
|
||||
|
||||
## Tests
|
||||
|
||||
End-to-End-Tests parsen die reale Input-Datei (`input/2025 JM.xlsx`) und das reale
|
||||
Template (`template/Tätigkeitsnachweise Stellenförderung.xlsx`) und validieren
|
||||
den gesamten Generierungs-Workflow.
|
||||
|
||||
```bash
|
||||
# Test-Abhängigkeiten installieren
|
||||
.venv/bin/pip install pytest
|
||||
|
||||
# Tests ausführen
|
||||
.venv/bin/python -m pytest tests/test_e2e.py -v
|
||||
```
|
||||
|
||||
Die Tests prüfen:
|
||||
- Vorhandensein der Input- und Template-Dateien
|
||||
- Korrekte Parsierung (168 Datensätze, 19 Personen)
|
||||
- Vollständige Pipeline für die erste Person („Draßdo, Cornelia"): 12 Monats-Sheets
|
||||
mit allen Zellinhalten (Antragsnummer, Name, Stellenanteil, Qualifikation, Monat,
|
||||
Funktion, Tätigkeit, Ort, Datum)
|
||||
+185
-19
@@ -29,6 +29,27 @@ from src.models import AppConfig
|
||||
from src.parser import parse_input_file, group_by_employee, get_unique_antragsnummern, get_unique_employees
|
||||
from src.validator import validate_input_file, validate_template
|
||||
from src.writer import generate_activity_reports
|
||||
from version_check import UPDATE_DIR, get_latest_version
|
||||
|
||||
|
||||
def set_active_employee(employee_name: str):
|
||||
"""Merkt die zuletzt angeklickte Person für die Monatsliste."""
|
||||
st.session_state.active_month_employee = employee_name
|
||||
|
||||
|
||||
def sync_employee_months(employee_name: str, months: list[str]):
|
||||
"""Übernimmt Monats-Checkboxen vor dem nächsten UI-Rendern."""
|
||||
st.session_state.employee_months[employee_name] = {
|
||||
month for month in months
|
||||
if st.session_state.get(f"month_{employee_name}_{month}", False)
|
||||
}
|
||||
|
||||
|
||||
def set_all_employee_months(employee_name: str, months: list[str], selected: bool):
|
||||
"""Setzt alle Monats-Checkboxen einer Person gesammelt."""
|
||||
st.session_state.employee_months[employee_name] = set(months) if selected else set()
|
||||
for month in months:
|
||||
st.session_state[f"month_{employee_name}_{month}"] = selected
|
||||
|
||||
# --- Seite konfigurieren ---
|
||||
st.set_page_config(
|
||||
@@ -56,6 +77,60 @@ except Exception as e:
|
||||
st.error(f"Fehler beim Laden der Konfiguration: {e}")
|
||||
st.stop()
|
||||
|
||||
# --- Versionsinformation für die dezente Anzeige am Seitenende ---
|
||||
current_version_text = ""
|
||||
try:
|
||||
local_version, latest_version = get_latest_version()
|
||||
current_version_text = f"v{'.'.join(map(str, local_version))}"
|
||||
if latest_version:
|
||||
available_version, _ = latest_version
|
||||
version_text = ".".join(map(str, available_version))
|
||||
st.html(
|
||||
f'''<style>
|
||||
.update-notice {{
|
||||
position: fixed; top: 4rem; right: 1rem; z-index: 10000;
|
||||
width: 290px; padding: 8px 12px; color: #5f4700;
|
||||
background: #fff8e1; border: 1px solid #e0b84c; border-radius: 8px;
|
||||
box-shadow: 0 3px 12px rgba(0, 0, 0, .18); font-size: .85rem;
|
||||
}}
|
||||
#update-notice-dismiss {{ display:none; }}
|
||||
#update-notice-dismiss:checked + .update-notice {{ display:none; }}
|
||||
body:has(#update-notice-dismiss:checked) div.st-key-update_notice_open {{ display:none!important; }}
|
||||
.update-notice__actions {{ display:flex; justify-content:flex-end; gap:8px; margin-top:8px; }}
|
||||
.update-notice__actions--footer {{ min-height:26px; }}
|
||||
.update-notice__button {{
|
||||
display:inline-flex; align-items:center; justify-content:center;
|
||||
width:26px; height:26px; color:#5f4700; background:#fffdf3;
|
||||
border:1px solid #d9bd66; border-radius:5px; text-decoration:none;
|
||||
font-size:1rem; line-height:1;
|
||||
}}
|
||||
.update-notice__button:hover {{ background:#f6e7b2; }}
|
||||
div.st-key-update_notice_open {{
|
||||
position:fixed; top:8.5rem; right:3.75rem; z-index:10001; width:26px;
|
||||
}}
|
||||
div.st-key-update_notice_open div[data-testid="stButton"] {{ margin:0; }}
|
||||
div.st-key-update_notice_open div[data-testid="stButton"] button {{
|
||||
min-width:200px; min-height:26px; height:26px; padding:0; color:#5f4700;
|
||||
background:#fffdf3; border:1px solid #d9bd66; border-radius:5px;
|
||||
}}
|
||||
</style>
|
||||
<input id="update-notice-dismiss" type="checkbox">
|
||||
<aside class="update-notice" role="status">
|
||||
<div class="update-notice__actions">
|
||||
<label class="update-notice__button" for="update-notice-dismiss" title="Hinweis schließen">×</label>
|
||||
</div>
|
||||
<strong>Neue Version v{version_text} verfügbar</strong>
|
||||
<div class="update-notice__actions update-notice__actions--footer">
|
||||
</div>
|
||||
</aside>'''
|
||||
)
|
||||
with st.container(key="update_notice_open"):
|
||||
if st.button("Update Ordner öffnen 📁", key="open_update_directory", help="Update-Ordner öffnen"):
|
||||
if sys.platform == "win32":
|
||||
os.startfile(str(UPDATE_DIR))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# --- Session State initialisieren ---
|
||||
if "parsed_records" not in st.session_state:
|
||||
st.session_state.parsed_records = []
|
||||
@@ -72,7 +147,8 @@ if "cached_val_result" not in st.session_state:
|
||||
st.session_state.cached_val_result = None
|
||||
if "cached_tpl_result" not in st.session_state:
|
||||
st.session_state.cached_tpl_result = None
|
||||
|
||||
if "employee_months" not in st.session_state:
|
||||
st.session_state.employee_months = {}
|
||||
# --- Layout: zwei Spalten ---
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
@@ -230,24 +306,94 @@ if st.session_state.input_validated and st.session_state.parsed_records:
|
||||
employees = get_unique_employees(st.session_state.parsed_records)
|
||||
st.caption(f"{len(employees)} Mitarbeiter gefunden – wähle aus, für wen Tätigkeitsnachweise erstellt werden sollen:")
|
||||
|
||||
# "Alle" / "Keine" Buttons
|
||||
btn_col1, btn_col2 = st.columns(2)
|
||||
if btn_col1.button("☑ Alle auswählen", use_container_width=True):
|
||||
for emp in employees:
|
||||
st.session_state[f"sel_{emp}"] = True
|
||||
if btn_col2.button("☐ Keine auswählen", use_container_width=True):
|
||||
for emp in employees:
|
||||
st.session_state[f"sel_{emp}"] = False
|
||||
# Monatsbezeichnungen für die kompakte Anzeige hinter dem Namen
|
||||
month_labels = {
|
||||
"01": "Jan", "02": "Feb", "03": "Mär", "04": "Apr",
|
||||
"05": "Mai", "06": "Jun", "07": "Jul", "08": "Aug",
|
||||
"09": "Sep", "10": "Okt", "11": "Nov", "12": "Dez",
|
||||
}
|
||||
|
||||
# Mitarbeiter-Liste mit Rahmen
|
||||
with st.container(border=True):
|
||||
selected_employees = []
|
||||
for emp in employees:
|
||||
key = f"sel_{emp}"
|
||||
if key not in st.session_state:
|
||||
st.session_state[key] = True
|
||||
if st.checkbox(emp, key=key):
|
||||
selected_employees.append(emp)
|
||||
all_groups = group_by_employee(st.session_state.parsed_records)
|
||||
available_months = {}
|
||||
for emp in employees:
|
||||
months = sorted({
|
||||
record.monat.split(".", 1)[0].zfill(2)
|
||||
for record in all_groups.get(emp, [])
|
||||
if record.monat and record.monat.split(".", 1)[0].isdigit()
|
||||
}, key=lambda value: int(value))
|
||||
available_months[emp] = months
|
||||
if emp not in st.session_state.employee_months:
|
||||
st.session_state.employee_months[emp] = set(months[-2:])
|
||||
|
||||
# Mitarbeiter- und Monatsliste teilen sich die verfügbare Breite.
|
||||
employee_col, month_col = st.columns(2)
|
||||
with employee_col:
|
||||
employee_btn_col1, employee_btn_col2 = st.columns(2)
|
||||
if employee_btn_col1.button("☑ Alle auswählen", use_container_width=True):
|
||||
for emp in employees:
|
||||
st.session_state[f"sel_{emp}"] = True
|
||||
if employee_btn_col2.button("☐ Keine auswählen", use_container_width=True):
|
||||
for emp in employees:
|
||||
st.session_state[f"sel_{emp}"] = False
|
||||
|
||||
with st.container(border=True):
|
||||
selected_employees = []
|
||||
for emp in employees:
|
||||
key = f"sel_{emp}"
|
||||
if key not in st.session_state:
|
||||
st.session_state[key] = False
|
||||
selected_months = st.session_state.employee_months.get(emp, set())
|
||||
short_months = ", ".join(month_labels[m] for m in available_months[emp] if m in selected_months)
|
||||
label = f"{emp} ({short_months})" if short_months else f"{emp} (**keine Monate**)"
|
||||
if st.checkbox(
|
||||
label,
|
||||
key=key,
|
||||
on_change=set_active_employee,
|
||||
args=(emp,),
|
||||
):
|
||||
selected_employees.append(emp)
|
||||
|
||||
with month_col:
|
||||
selected_for_months = st.session_state.get("active_month_employee")
|
||||
if selected_for_months not in employees:
|
||||
selected_for_months = None
|
||||
|
||||
month_btn_col1, month_btn_col2 = st.columns(2)
|
||||
month_btn_col1.button(
|
||||
"☑ Alle Monate",
|
||||
use_container_width=True,
|
||||
disabled=not selected_for_months,
|
||||
on_click=set_all_employee_months,
|
||||
args=(selected_for_months, available_months[selected_for_months], True) if selected_for_months else ("", [], True),
|
||||
)
|
||||
month_btn_col2.button(
|
||||
"☐ Keine Monate",
|
||||
use_container_width=True,
|
||||
disabled=not selected_for_months,
|
||||
on_click=set_all_employee_months,
|
||||
args=(selected_for_months, available_months[selected_for_months], False) if selected_for_months else ("", [], False),
|
||||
)
|
||||
|
||||
with st.container(border=True):
|
||||
st.caption("Monate für den angeklickten Mitarbeiter")
|
||||
if selected_for_months:
|
||||
st.write(selected_for_months)
|
||||
month_values = {}
|
||||
for month in available_months[selected_for_months]:
|
||||
month_key = f"month_{selected_for_months}_{month}"
|
||||
if month_key not in st.session_state:
|
||||
st.session_state[month_key] = month in st.session_state.employee_months[selected_for_months]
|
||||
month_values[month] = st.checkbox(
|
||||
f"{month_labels[month]} ({month})",
|
||||
key=month_key,
|
||||
on_change=sync_employee_months,
|
||||
args=(selected_for_months, available_months[selected_for_months]),
|
||||
)
|
||||
st.session_state.employee_months[selected_for_months] = {
|
||||
month for month, checked in month_values.items() if checked
|
||||
}
|
||||
else:
|
||||
st.info("Einen Mitarbeiter links auswählen, um seine Monate festzulegen.")
|
||||
|
||||
st.caption(f"{len(selected_employees)} von {len(employees)} Mitarbeitern ausgewählt")
|
||||
else:
|
||||
@@ -283,7 +429,20 @@ if generate_clicked:
|
||||
else:
|
||||
# Filtere Records auf selektierte Mitarbeiter
|
||||
all_groups = group_by_employee(st.session_state.parsed_records)
|
||||
selected_groups = {name: all_groups[name] for name in selected_employees if name in all_groups}
|
||||
selected_groups = {
|
||||
name: [
|
||||
record for record in all_groups[name]
|
||||
if record.monat.split(".", 1)[0].zfill(2)
|
||||
in st.session_state.employee_months.get(name, set())
|
||||
]
|
||||
for name in selected_employees
|
||||
if name in all_groups
|
||||
}
|
||||
selected_groups = {name: records for name, records in selected_groups.items() if records}
|
||||
|
||||
if not selected_groups:
|
||||
st.error("Für die ausgewählten Mitarbeiter sind keine Monate ausgewählt.")
|
||||
st.stop()
|
||||
|
||||
template_path = Path(app_config.template_dir) / selected_template
|
||||
output_dir = Path(app_config.output_dir)
|
||||
@@ -328,3 +487,10 @@ if "generated_files" in st.session_state and st.session_state.generated_files:
|
||||
help="Ordner im Finder öffnen",
|
||||
use_container_width=True,
|
||||
)
|
||||
|
||||
if current_version_text:
|
||||
st.markdown(
|
||||
f'<div style="text-align:right; color:#888; font-size:0.8em; margin-top:2em;">'
|
||||
f'Programmversion {current_version_text}</div>',
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Prüft auf neuere Programmversionen im zentralen SMB-Verzeichnis."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
UPDATE_DIR = Path(r"j:\Fachbereiche\BO\Produktionsschule\Maßnahmeunterlagen\20_Personal\05_Tätigkeitsnachweise ESF\Nachweise-automatisiert-generieren")
|
||||
VERSION_PATTERN = re.compile(r"Tätigkeitsnachweis-Generator v(\d+)\.(\d+)\.(\d+)(?:\.\d+)?\.(zip|exe)$", re.IGNORECASE)
|
||||
CHANGELOG_PATTERN = re.compile(r"^## \[(\d+)\.(\d+)(?:\.(\d+))?\]", re.MULTILINE)
|
||||
|
||||
|
||||
def get_latest_version():
|
||||
match = CHANGELOG_PATTERN.search(Path(__file__).with_name("CHANGELOG.md").read_text(encoding="utf-8"))
|
||||
current = tuple(int(part or 0) for part in match.groups()) if match else (0, 0, 0)
|
||||
try:
|
||||
available = UPDATE_DIR.exists()
|
||||
except OSError:
|
||||
available = False
|
||||
if not available:
|
||||
return current, None
|
||||
newer = []
|
||||
try:
|
||||
paths = UPDATE_DIR.iterdir()
|
||||
for path in paths:
|
||||
found = VERSION_PATTERN.fullmatch(path.name)
|
||||
if found:
|
||||
version = tuple(int(found.group(i)) for i in range(1, 4))
|
||||
if version > current:
|
||||
newer.append((version, path))
|
||||
except OSError:
|
||||
return current, None
|
||||
if newer:
|
||||
return current, max(newer)
|
||||
return current, None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
current, latest = get_latest_version()
|
||||
print(f"[INFO] Lokale Version: v{'.'.join(map(str, current))}")
|
||||
if latest:
|
||||
version, path = latest
|
||||
print(f"[HINWEIS] Neuere Version verfügbar: v{'.'.join(map(str, version))}")
|
||||
print(f"[HINWEIS] Datei: {path}")
|
||||
else:
|
||||
print("[INFO] Keine neuere Version gefunden oder Update-Verzeichnis nicht erreichbar.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1 +1 @@
|
||||
1.2.2
|
||||
1.3
|
||||
@@ -222,6 +222,12 @@ def _prepare_build_dir() -> None:
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# README.md aus 0_application/ kopieren
|
||||
readme_src = APP_DIR / "README.md"
|
||||
if readme_src.exists():
|
||||
shutil.copy2(readme_src, BUILD_OUTPUT_DIR / "README.md")
|
||||
print(" ✓ README.md nach 2_build/ kopiert")
|
||||
|
||||
print(f" ✓ 2_build/ vorbereitet")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user