diff --git a/.github/copilot-tasks.md b/.github/copilot-tasks.md index 40f5652..b44be84 100644 --- a/.github/copilot-tasks.md +++ b/.github/copilot-tasks.md @@ -77,10 +77,10 @@ Projekt vollstaendig umsetzen: aus PS_PW.xlsx und Stellenbeschreibungen.xlsx mon - [x] Bei mehreren Zuordnungen: mehrere Sheets in derselben Datei ## Phase 7 - CLI und Benutzerfluss -- [ ] T13: CLI in `generate.py` implementieren - - [ ] Parameter `--month` und `--year` - - [ ] Pipeline orchestrieren: parse -> match -> validate -> preview -> write - - [ ] Kontrollierter Exit-Code bei Fehlern +- [x] T13: CLI in `generate.py` implementieren + - [x] Parameter `--month` und `--year` + - [x] Pipeline orchestrieren: parse -> match -> validate -> preview -> write + - [x] Kontrollierter Exit-Code bei Fehlern - [ ] T14: Vorschau + Bestaetigung vor Schreiben - [ ] Uebersicht zu Berichtsmonat, aktiven Faellen, Fehlern - [ ] Liste der geplanten Ausgabedateien @@ -111,4 +111,4 @@ Projekt vollstaendig umsetzen: aus PS_PW.xlsx und Stellenbeschreibungen.xlsx mon 8. T15 -> T16 -> T17 ## Aktueller Startpunkt -- [ ] Naechste aktive Aufgabe: T13 (CLI in generate.py implementieren) +- [ ] Naechste aktive Aufgabe: T14 (Vorschau + Bestaetigung vor Schreiben) diff --git a/generate.py b/generate.py new file mode 100644 index 0000000..0405bc4 --- /dev/null +++ b/generate.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import click + +from src.taetigkeitsnachweis.errors import TaetigkeitsnachweisError +from src.taetigkeitsnachweis.logging_utils import configure_logging +from src.taetigkeitsnachweis.matcher import match_assignments +from src.taetigkeitsnachweis.parser import parse_allocation_records, parse_masterdata_records +from src.taetigkeitsnachweis.validator import validate_business_rules, validate_sources +from src.taetigkeitsnachweis.writer import build_employee_workbooks, save_employee_workbooks + + +def _load_config(project_root: Path) -> dict: + config_path = project_root / "config.json" + with config_path.open("r", encoding="utf-8") as config_file: + return json.load(config_file) + + +def _print_issues(issues: list, title: str) -> None: + if not issues: + return + click.echo(title) + for issue in issues: + click.echo(f"- [{issue.severity}] {issue.code}: {issue.message}") + + +def _print_preview(month: int, year: int, matched_assignments: list) -> None: + click.echo("=== Vorschau der ermittelten Werte ===") + click.echo(f"Berichtsmonat : {month:02d}/{year}") + click.echo(f"Aktive Faelle : {len(matched_assignments)}") + click.echo("--- Zu erzeugende Taetigkeitsnachweise ---") + for item in matched_assignments: + click.echo( + "- " + f"{item.allocation.employee_name} | " + f"{item.masterdata.funktion} | " + f"{item.foerderquote_percent:.2f}%" + ) + + +@click.command() +@click.option("--month", type=click.IntRange(1, 12), required=True) +@click.option("--year", type=click.IntRange(2000, 2100), required=True) +def main(month: int, year: int) -> None: + configure_logging() + project_root = Path(__file__).resolve().parent + + try: + config = _load_config(project_root) + + source_issues = validate_sources( + config=config, + report_month=month, + report_year=year, + project_root=project_root, + ) + _print_issues(source_issues, "=== Source validation ===") + if any(issue.severity == "error" for issue in source_issues): + raise click.ClickException("Source validation failed.") + + allocation_records = parse_allocation_records( + config=config, + report_month=month, + report_year=year, + project_root=project_root, + ) + masterdata_records = parse_masterdata_records( + config=config, + project_root=project_root, + ) + + matched_assignments, match_issues = match_assignments( + allocation_records=allocation_records, + masterdata_records=masterdata_records, + config=config, + ) + _print_issues(match_issues, "=== Matching issues ===") + + business_issues = validate_business_rules( + config=config, + report_month=month, + report_year=year, + allocation_records=allocation_records, + matched_assignments=matched_assignments, + project_root=project_root, + ) + _print_issues(business_issues, "=== Business validation ===") + + all_issues = [*match_issues, *business_issues] + if any(issue.severity == "error" for issue in all_issues): + raise click.ClickException("Validation failed after matching.") + + _print_preview(month=month, year=year, matched_assignments=matched_assignments) + + employee_workbooks = build_employee_workbooks( + config=config, + matched_assignments=matched_assignments, + report_month=month, + report_year=year, + project_root=project_root, + ) + output_paths = save_employee_workbooks( + config=config, + employee_workbooks=employee_workbooks, + report_month=month, + report_year=year, + project_root=project_root, + ) + + click.echo("=== Generated files ===") + for output_path in output_paths: + click.echo(f"- {output_path}") + + except TaetigkeitsnachweisError as exc: + raise click.ClickException(str(exc)) from exc + + +if __name__ == "__main__": + main() diff --git a/src/taetigkeitsnachweis/validator.py b/src/taetigkeitsnachweis/validator.py index 8918c91..396ef4c 100644 --- a/src/taetigkeitsnachweis/validator.py +++ b/src/taetigkeitsnachweis/validator.py @@ -266,6 +266,7 @@ def validate_business_rules( report_year: int, allocation_records: list[AllocationRecord], matched_assignments: list[MatchedAssignment], + project_root: Path, ) -> list[ValidationIssue]: issues: list[ValidationIssue] = [] @@ -301,7 +302,6 @@ def validate_business_rules( paths_cfg = config["paths"] files_cfg = config["files"] - project_root = Path.cwd() template_path = ( project_root / paths_cfg["template_dir"]