""" create_examples.py - Liest Stuecklisten-Daten aus Excel-Dateien im Examples-Ordner. Verwendung: python create_examples.py [--example-dir ] """ from __future__ import annotations import argparse import logging import sys from dataclasses import dataclass from pathlib import Path from typing import Any log = logging.getLogger(__name__) @dataclass class StkItem: position: str teilenummer: str menge: Any def _cell_str(value: Any) -> str: return str(value).strip() if value is not None else "" def _find_col_index(header_row: tuple[Any, ...], candidates: list[str]) -> int | None: normalized = [_cell_str(v).lower() for v in header_row] for candidate in candidates: candidate_norm = candidate.lower() if candidate_norm in normalized: return normalized.index(candidate_norm) return None def _extract_stk_liste_ist(xlsx_path: Path) -> list[StkItem]: import openpyxl wb = openpyxl.load_workbook(str(xlsx_path), read_only=True, data_only=True) if "Stk.-Liste IST" not in wb.sheetnames: wb.close() raise ValueError("Reiter 'Stk.-Liste IST' nicht gefunden") ws = wb["Stk.-Liste IST"] header_idx: tuple[int | None, int | None, int | None] | None = None items: list[StkItem] = [] for row in ws.iter_rows(values_only=True): if header_idx is None: pos_idx = _find_col_index(list(row), ["position", "pos", "pos."]) part_idx = _find_col_index( list(row), ["teilenummer", "teilnummer", "article", "artikelnummer"], ) qty_idx = _find_col_index(list(row), ["menge", "anzahl", "qty", "quantity"]) if pos_idx is not None and part_idx is not None and qty_idx is not None: header_idx = (pos_idx, part_idx, qty_idx) continue pos_idx, part_idx, qty_idx = header_idx assert pos_idx is not None and part_idx is not None and qty_idx is not None position = _cell_str(row[pos_idx]) if pos_idx < len(row) else "" teilenummer = _cell_str(row[part_idx]) if part_idx < len(row) else "" menge = row[qty_idx] if qty_idx < len(row) else None # Leere Zeilen ueberspringen if not position and not teilenummer and menge in ("", None): continue if not position or not teilenummer: continue items.append(StkItem(position=position, teilenummer=teilenummer, menge=menge)) wb.close() return items def _process_single_folder(folder: Path, example_dir: Path, result: dict, log) -> None: xlsx_files = sorted(folder.glob("*.xlsx")) if not xlsx_files: return xlsx_path = xlsx_files[0] rel_folder = str(folder.relative_to(example_dir)) try: items = _extract_stk_liste_ist(xlsx_path) except ValueError as exc: log.warning("%s/%s uebersprungen: %s", rel_folder, xlsx_path.name, exc) return result[rel_folder] = [ { "position": item.position, "teilenummer": item.teilenummer, "menge": item.menge, } for item in items ] def process_all(example_dir: Path) -> dict[str, list[dict[str, Any]]]: """ Durchsucht rekursiv alle Unterverzeichnisse in example_dir. Pro Unterverzeichnis wird die erste gefundene .xlsx-Datei gelesen und aus dem Reiter "Stk.-Liste IST" werden Position, Teilenummer, Menge extrahiert. """ if not example_dir.exists(): log.error("Beispiel-Verzeichnis existiert nicht: %s", example_dir) sys.exit(1) subdirs = sorted([p for p in example_dir.rglob("*") if p.is_dir()]) if not subdirs: log.error("Keine Unterverzeichnisse in %s gefunden", example_dir) sys.exit(1) log.info("Gefunden: %d Unterverzeichnisse in %s", len(subdirs), example_dir) result: dict[str, list[dict[str, Any]]] = {} for folder in subdirs: log.info("Verarbeite %s", folder) _process_single_folder(folder, example_dir, result, log) log.info("Fertig. Gelesene Verzeichnisse mit Daten: %d", len(result)) return result def main() -> None: parser = argparse.ArgumentParser( description="Liest Position/Teilenummer/Menge aus Example-Exceldateien" ) parser.add_argument( "--example-dir", type=Path, default=None, help=( "Unterordnername in PV_EXAMPLES oder direkter Verzeichnispfad " "(default: alle Unterordner in PV_EXAMPLES)" ), ) parser.add_argument("--verbose", "-v", action="store_true", help="Ausfuehrliche Ausgabe") args = parser.parse_args() logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, format="%(levelname)-7s %(message)s", ) import os pv_examples = os.environ.get("PV_EXAMPLES") if not pv_examples: log.error("PV_EXAMPLES ist nicht gesetzt") sys.exit(1) examples_root = Path(pv_examples).resolve() if not examples_root.exists() or not examples_root.is_dir(): log.error("PV_EXAMPLES ist kein gueltiges Verzeichnis: %s", examples_root) sys.exit(1) if args.example_dir is None: log.info("Beispiel-Verzeichnis (alle Unterordner): %s", examples_root) data = process_all(examples_root) log.info("Objekt erzeugt mit %d Schluesseln", len(data)) return # --example-dir: entweder direkter Verzeichnispfad oder Unterordnername in PV_EXAMPLES candidate = args.example_dir if candidate.is_absolute(): target_dir = candidate.resolve() else: as_path = candidate.resolve() if as_path.exists() and as_path.is_dir(): target_dir = as_path else: target_dir = (examples_root / candidate).resolve() if not target_dir.exists() or not target_dir.is_dir(): log.error( "--example-dir muss ein gueltiger Verzeichnispfad oder Unterordner in PV_EXAMPLES sein: %s", args.example_dir, ) sys.exit(1) log.info("Beispiel-Verzeichnis (einzeln): %s", target_dir) result: dict[str, list[dict[str, Any]]] = {} _process_single_folder(target_dir, target_dir.parent, result, log) log.info("Objekt erzeugt mit %d Schluesseln", len(result)) if __name__ == "__main__": main()