from __future__ import annotations from pathlib import Path import shutil from datetime import date, timedelta from .presets import load_yaml, deep_merge, load_customer_preset from .invoice_number import suggest_next_invoice_number from .folder_naming import default_invoice_folder_name import yaml def _dump_yaml(data: dict, path: Path) -> None: path.write_text(yaml.safe_dump(data, sort_keys=False, allow_unicode=True), encoding="utf-8") def init_invoice_folder( target_dir: Path, repo_root: Path, logger, force: bool = False, customer: str | None = None, invoice_number: str | None = None, due_days: int = 14, invoices_root: Path | None = None, project: str | None = None, ) -> Path: target_dir = target_dir.resolve() target_dir.mkdir(parents=True, exist_ok=True) # Create standard subfolders (target_dir / "assets").mkdir(parents=True, exist_ok=True) for p in [target_dir / "out", target_dir / "out" / "logs", target_dir / "out" / "validation"]: p.mkdir(parents=True, exist_ok=True) (p / ".gitkeep").write_text("", encoding="utf-8") # Copy default logo if not present (or force) src_logo = repo_root / "assets" / "logo.svg" dst_logo = target_dir / "assets" / "logo.svg" if src_logo.exists() and (force or not dst_logo.exists()): shutil.copy2(src_logo, dst_logo) logger.info("Copied default logo to %s", dst_logo) # Base invoice template base_tpl = load_yaml(repo_root / "examples" / "invoice.init.yaml") # Apply seller defaults seller_defaults = load_yaml(repo_root / "presets" / "seller.default.yaml") merged = deep_merge(base_tpl, seller_defaults) # Apply customer preset if customer: preset = load_customer_preset(repo_root, customer) if preset: merged = deep_merge(merged, preset) logger.info("Applied customer preset: %s", customer) else: logger.warning("Customer preset not found for: %s (continuing with generic buyer)", customer) # Smart defaults for meta/payment today = date.today() merged.setdefault("meta", {}) merged["meta"]["invoice_date"] = str(today) merged["meta"]["service_date"] = str(today) # invoice number if not invoice_number and invoices_root: try: invoice_number = suggest_next_invoice_number(invoices_root) logger.info("Suggested next invoice number: %s (root=%s)", invoice_number, invoices_root) except Exception as e: logger.warning("Failed to suggest invoice number from %s: %s", invoices_root, e) if invoice_number: merged["meta"]["invoice_number"] = invoice_number # project -> default first item name if project: merged.setdefault("items", []) if len(merged["items"]) == 0: merged["items"] = [{ "name": project, "description": "Leistung/Beschreibung", "quantity": 1, "unit": "C62", "unit_price_net": 100.00, "tax_rate": 19.0 }] else: if merged["items"][0].get("name") in ("Leistung", "Service", None, ""): merged["items"][0]["name"] = project logger.info("Applied project default: %s", project) merged.setdefault("payment", {}) merged["payment"]["due_date"] = str(today + timedelta(days=due_days)) inv_no_for_ref = merged["meta"].get("invoice_number", "") merged["payment"]["reference"] = f"Rechnung {inv_no_for_ref}".strip() dst_yaml = target_dir / "invoice.yaml" if dst_yaml.exists() and not force: raise FileExistsError(f"{dst_yaml} already exists (use --force to overwrite)") _dump_yaml(merged, dst_yaml) logger.info("Initialized invoice folder: %s", target_dir) return target_dir def init_invoice_under_root( invoices_root: Path, repo_root: Path, logger, customer: str | None, project: str | None, invoice_number: str | None, due_days: int, force: bool, ) -> Path: invoices_root = invoices_root.expanduser().resolve() invoices_root.mkdir(parents=True, exist_ok=True) # suggest number if missing if not invoice_number: invoice_number = suggest_next_invoice_number(invoices_root) logger.info("Auto invoice number: %s", invoice_number) folder_name = default_invoice_folder_name(invoice_number, customer) target_dir = invoices_root / folder_name logger.info("Auto folder name: %s", target_dir) return init_invoice_folder( target_dir=target_dir, repo_root=repo_root, logger=logger, force=force, customer=customer, invoice_number=invoice_number, due_days=due_days, invoices_root=invoices_root, project=project, )