feat: Add Windows compatibility, PyInstaller build setup, and custom icon

- Add Windows LibreOffice detection fallback for soffice path
  - Check standard install locations (%PROGRAMFILES%, %PROGRAMFILES(X86)%)
  - Graceful fallback with clear error messages

- Add PyInstaller build infrastructure
  - build/ccma.spec: PyInstaller configuration with icon generation
  - build/build.ps1: Automated build script for standalone exe
  - main.py: Entry point for PyInstaller
  - Supports versioning and architecture tagging

- Create custom CCMA icon
  - Person + Gear symbol representing member administration
  - Cyan/White/Orange color scheme
  - Auto-converts PNG to ICO during build

- Update documentation
  - README: Windows PowerShell setup instructions
  - README: Linux/macOS bash setup instructions
  - README: Standalone executable build guide
  - pyproject.toml: Add 'build' extra with pyinstaller+pillow

- Add regression tests
  - Test office executable detection and Windows fallback
  - Verify all tests pass (80 passed)
This commit is contained in:
Marcel Peterkau
2026-06-23 11:08:18 +02:00
parent d859557c0f
commit 302170230a
5 changed files with 98 additions and 1 deletions
+19
View File
@@ -188,3 +188,22 @@ def test_generated_reminder_pdf_is_stored_audited_and_linked(tmp_path, monkeypat
sha256="invalid",
template="Mahnung.fodt",
)
def test_office_executable_prefers_path_lookup(monkeypatch) -> None:
monkeypatch.setattr(document_module.shutil, "which", lambda name: "C:/tools/soffice.exe")
assert document_module._office_executable() == "C:/tools/soffice.exe"
def test_office_executable_falls_back_to_windows_default_path(tmp_path, monkeypatch) -> None:
libreoffice = tmp_path / "LibreOffice" / "program"
libreoffice.mkdir(parents=True)
executable = libreoffice / "soffice.exe"
executable.write_text("", encoding="utf-8")
monkeypatch.setattr(document_module.shutil, "which", lambda name: None)
monkeypatch.setattr(document_module.os, "name", "nt")
monkeypatch.setenv("PROGRAMFILES", str(tmp_path))
assert document_module._office_executable() == str(executable)