feat: install user console commands and document Office independence
This commit is contained in:
1 parent
6438558ea3
commit
f447603af4
6 files changed
+179
-10
No files matched your search
@@ -291,3 +291,59 @@ Checkout-Updater nicht.
|
||||
Technische Grundlagen: [pip Versionsauflösung und Installationsbericht](https://pip.pypa.io/en/stable/cli/pip_install/),
|
||||
[Python venv und feste Umgebungspfade](https://docs.python.org/3/library/venv.html).
|
||||
Umgebungen bleiben deshalb an ihrem Erstellungsort; nur der Zeiger wird gewechselt.
|
||||
|
||||
|
||||
## Konsolenbefehle installieren
|
||||
|
||||
Einmal im Checkout `python3 scripts/install_commands.py` ausführen. Der Installer
|
||||
benötigt kein sudo und verlinkt `einvoice` sowie `jr-einvoice` nach `~/.local/bin`.
|
||||
Die verlinkten Starter finden den Checkout auch beim Aufruf aus einem anderen
|
||||
Verzeichnis und verwenden den aktiven Update-/Rollback-Stand. Beide Befehle werden
|
||||
mit `--help` geprüft. Bei Fehlern setzt der Installer seine Änderungen zurück.
|
||||
Vorhandene andere Dateien/Symlinks werden unter `*.before-einvoice-<Zeit-ID>`
|
||||
gesichert; bereits korrekt eingerichtete Links bleiben erhalten.
|
||||
|
||||
```bash
|
||||
# Einmalig:
|
||||
cd /home/johannes/localdev/einvoice
|
||||
python3 scripts/install_commands.py
|
||||
|
||||
# Danach auch außerhalb des Checkouts:
|
||||
einvoice --help
|
||||
einvoice --customers
|
||||
einvoice --update --status
|
||||
einvoice --update --apply
|
||||
einvoice --update --rollback
|
||||
jr-einvoice --help
|
||||
```
|
||||
|
||||
Abweichender Zielordner: `--bin-dir /absoluter/pfad`. Falls dieser Ordner nicht im
|
||||
PATH steht, zeigt der Installer die passende Export-Zeile an; dauerhaft in die eigene
|
||||
Shell-Konfiguration übernehmen. Auf Johannes' Rechner ist `~/.local/bin` bereits im
|
||||
PATH, daher ist keine Änderung der Shell-Konfiguration nötig. Bei einem alten
|
||||
Shell-Cache `rehash` (zsh) oder `hash -r` (bash) verwenden. Eine aktivierte `.venv`
|
||||
kann den alten Einstieg vorziehen; mit `deactivate` die verwalteten Befehle nutzen.
|
||||
Der Checkout muss am Installationsort erhalten bleiben. Nach Verschieben den
|
||||
Installer am neuen Ort erneut ausführen. Die Starter benötigen bash, readlink und
|
||||
Python 3; diese sind auf dem vorgesehenen Linux-System vorhanden.
|
||||
|
||||
## Office-Unabhängigkeit und LibreOffice
|
||||
|
||||
Die Quell-, Vorlagen- und Datenformate der Anwendung sind YAML, HTML/Jinja2, SVG
|
||||
und Markdown. Erzeugt werden PDF/A-3, strukturiertes XML, Prüfberichte, JSON-Manifeste
|
||||
und optional EML. Weder DOC/DOCX, XLS/XLSX, PPT/PPTX noch COM-/Microsoft-Office-APIs
|
||||
werden verwendet. Es gibt keine Abhängigkeit von Word, Excel, Outlook oder einer
|
||||
Microsoft-365-Lizenz. Die Python-Abhängigkeiten und die externen Werkzeuge
|
||||
WeasyPrint/Mustang/Java benötigen keine Office-Installation.
|
||||
|
||||
Für diese Rechnungslösung muss deshalb beim Wechsel zu LibreOffice nichts nach
|
||||
ODT/ODS konvertiert werden. ODF-Import/-Export ist keine Funktion des Tools;
|
||||
Dokumentvorlagen werden weiterhin als HTML/Jinja2 bearbeitet, Daten als YAML.
|
||||
Die versandfertige E-Rechnung ist das validierte PDF mit eingebettetem XML.
|
||||
Dieses Original nicht in Office/LibreOffice bearbeiten oder als neues PDF exportieren,
|
||||
da XML-Einbettung und PDF/A-Eigenschaften dabei verloren gehen können.
|
||||
|
||||
Der optionale EML-Entwurf ist eine Datei ohne Outlook-Automation. Mailprogramm,
|
||||
Mailkonto, Zugang und tatsächlicher Versand liegen außerhalb der Toolchain.
|
||||
Die Prüfung betrifft dieses eInvoice-Projekt; andere persönliche oder betriebliche
|
||||
Office-Dateien und Microsoft-365-Dienste wurden damit nicht geprüft oder migriert.
|
||||
@@ -29,3 +29,7 @@ rollback:
|
||||
python3 scripts/update.py --rollback
|
||||
update-status:
|
||||
python3 scripts/update.py --status
|
||||
|
||||
.PHONY: install-commands
|
||||
install-commands:
|
||||
python3 scripts/install_commands.py
|
||||
@@ -11,7 +11,7 @@ Auf diesem Rechner sind Python-Umgebung, Absenderdaten und Mustang eingerichtet.
|
||||
|
||||
```bash
|
||||
cd /home/johannes/localdev/einvoice
|
||||
export PATH="$PWD/bin:$PATH"
|
||||
python3 scripts/install_commands.py # einmalig; auf diesem Rechner bereits eingerichtet
|
||||
|
||||
einvoice --create \
|
||||
--Customer "Meine Kundenfirma GmbH" \
|
||||
@@ -103,21 +103,50 @@ Die Konsole zeigt Schritte, Versionsänderungen und Fehler; vollständige Logs l
|
||||
in `.runtime/logs/`. Netzwerk ist nur für neue Kandidaten nötig. Ohne `--apply`
|
||||
werden Downloads, Installation und Tests ausgeführt, aber keine Umschaltung.
|
||||
|
||||
**Für die verwaltete Laufzeit die Starter aus `bin/` verwenden:**
|
||||
**Konsolenbefehle einmalig im Benutzer-Suchpfad einrichten:**
|
||||
|
||||
```bash
|
||||
export PATH="/home/johannes/localdev/einvoice/bin:$PATH" # bei Bedarf in ~/.zshrc aufnehmen
|
||||
# alternativ ohne PATH-Einrichtung:
|
||||
./bin/einvoice --customers
|
||||
./bin/einvoice --update --apply
|
||||
python3 scripts/install_commands.py
|
||||
# Danach aus jedem Verzeichnis, ohne venv-Aktivierung:
|
||||
einvoice --customers
|
||||
einvoice --update --apply
|
||||
einvoice --update --rollback
|
||||
jr-einvoice --help
|
||||
```
|
||||
|
||||
Diese PATH-Zeile nach einer eventuellen venv-Aktivierung ausführen. Direkte Aufrufe
|
||||
von `.venv/bin/einvoice` verwenden weiterhin die ursprüngliche Entwicklerumgebung.
|
||||
Der Installer verlinkt beide Starter nach `~/.local/bin` und prüft ihre Funktion.
|
||||
Dort vorhandene andere Befehle werden vor dem Ersetzen gesichert. Wiederholtes
|
||||
Ausführen ist möglich. Auf diesem Rechner ist das Verzeichnis bereits im PATH.
|
||||
Bei einem gespeicherten alten Shell-Pfad einmal `rehash` (zsh) bzw. `hash -r`
|
||||
(bash) ausführen. Eine aktivierte `.venv` kann die Befehle überdecken: dann
|
||||
`deactivate` ausführen. Direkte Aufrufe von `.venv/bin/einvoice` verwenden weiterhin
|
||||
die ursprüngliche Entwicklerumgebung.
|
||||
Java und native Bibliotheken werden mitgetestet, aber über den Systempaketmanager
|
||||
aktualisiert; dessen Änderungen kann dieser Updater nicht zurückrollen.
|
||||
Details und Grenzen: [Update-Betrieb](Documentation.md#updates-und-rollback).
|
||||
|
||||
## Ohne Microsoft Office / Microsoft 365
|
||||
|
||||
Die Rechnungslösung benötigt weder Word, Excel, Outlook noch Microsoft 365.
|
||||
Es gibt keine DOC/DOCX-, XLS/XLSX- oder PPT/PPTX-Vorlagen und keine Office-Automation.
|
||||
Auch LibreOffice muss zur Rechnungserstellung nicht installiert sein.
|
||||
|
||||
| Zweck | Verwendetes Format |
|
||||
| --- | --- |
|
||||
| Rechnungsdaten und Stammdaten | YAML (`.yaml`) |
|
||||
| Layout | HTML/Jinja2 (`.html.j2`), Logo als SVG |
|
||||
| Fertige E-Rechnung | PDF/A-3 mit eingebettetem EN16931-XML |
|
||||
| Optionaler Mailentwurf | EML (`.eml`), ohne Outlook-Anbindung |
|
||||
| Dokumentation | Markdown (`.md`) |
|
||||
|
||||
Ein Wechsel auf LibreOffice erfordert für dieses Projekt keine Dateikonvertierung.
|
||||
ODT-/ODS-Dateien werden aktuell weder eingelesen noch erzeugt. Layoutänderungen
|
||||
finden in der HTML-Vorlage statt. Das fertige E-Rechnungs-PDF direkt versenden;
|
||||
nicht zum Bearbeiten konvertieren oder neu als PDF exportieren, da eingebettete
|
||||
Rechnungsdaten oder die geprüfte PDF/A-Struktur verloren gehen können.
|
||||
Die tatsächliche E-Mail-Zustellung erfolgt weiterhin über dein separat eingerichtetes
|
||||
Mailprogramm bzw. Konto; sie wird von diesem Tool nicht bereitgestellt.
|
||||
|
||||
## Daten und Einrichtung
|
||||
|
||||
- Absender: **johannes@jr-it-services.de**, lokale Vorlage `private/presets/seller.default.yaml`.
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
launcher="$(readlink -f -- "${BASH_SOURCE[0]}")"
|
||||
root="$(cd -- "$(dirname -- "$launcher")/.." && pwd)"
|
||||
if [[ "${1:-}" == --update ]]; then
|
||||
shift
|
||||
exec python3 "$root/scripts/update.py" "$@"
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
launcher="$(readlink -f -- "${BASH_SOURCE[0]}")"
|
||||
root="$(cd -- "$(dirname -- "$launcher")/.." && pwd)"
|
||||
if [[ "${1:-}" == --update ]]; then
|
||||
shift
|
||||
exec python3 "$root/scripts/update.py" "$@"
|
||||
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install per-user launchers without sudo or changing shell configuration."""
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
|
||||
def install(bin_dir: Path) -> None:
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
bin_dir = bin_dir.expanduser().absolute()
|
||||
bin_dir.mkdir(parents=True, exist_ok=True)
|
||||
changed = []
|
||||
try:
|
||||
for name in ('einvoice', 'jr-einvoice'):
|
||||
source = root / 'bin' / name
|
||||
target = bin_dir / name
|
||||
if target.is_symlink() and target.resolve() == source:
|
||||
print(f'Bereits eingerichtet: {target}', flush=True)
|
||||
continue
|
||||
backup = None
|
||||
if target.exists() or target.is_symlink():
|
||||
if target.is_dir():
|
||||
raise RuntimeError(f'Ziel ist ein Verzeichnis: {target}')
|
||||
stamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')
|
||||
backup = target.with_name(f'{name}.before-einvoice-{stamp}-{uuid.uuid4().hex[:8]}')
|
||||
if target.is_symlink():
|
||||
backup.symlink_to(os.readlink(target))
|
||||
else:
|
||||
import shutil
|
||||
shutil.copy2(target, backup)
|
||||
print(f'Bisheriger Befehl gesichert: {backup}', flush=True)
|
||||
temporary = target.with_name(f'.{name}-{uuid.uuid4().hex}.next')
|
||||
try:
|
||||
temporary.symlink_to(source)
|
||||
os.replace(temporary, target)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
changed.append((target, backup))
|
||||
print(f'Eingerichtet: {target} -> {source}', flush=True)
|
||||
# A different cwd verifies that symlinks resolve to the checkout correctly.
|
||||
for name in ('einvoice', 'jr-einvoice'):
|
||||
subprocess.run([str(bin_dir / name), '--help'], cwd=bin_dir,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
|
||||
print('OK: Beide Konsolenbefehle geprüft.', flush=True)
|
||||
except BaseException:
|
||||
for target, backup in reversed(changed):
|
||||
if backup is None:
|
||||
target.unlink(missing_ok=True)
|
||||
else:
|
||||
os.replace(backup, target)
|
||||
raise
|
||||
if str(bin_dir) not in os.environ.get('PATH', '').split(os.pathsep):
|
||||
import shlex
|
||||
print(f'Für diese Shell ausführen: export PATH={shlex.quote(str(bin_dir))}:"$PATH"')
|
||||
print('Ab jetzt: einvoice --help | einvoice --update --status | jr-einvoice --help')
|
||||
print('Falls die Shell noch den alten Pfad merkt: rehash (zsh) bzw. hash -r (bash).')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='einvoice und jr-einvoice im Benutzer-Suchpfad einrichten.')
|
||||
parser.add_argument('--bin-dir', type=Path, default=Path.home() / '.local/bin')
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
install(args.bin_dir)
|
||||
except (Exception, KeyboardInterrupt) as exc:
|
||||
print(f'FEHLER: {exc}; geänderte Befehle wurden zurückgesetzt.', file=sys.stderr)
|
||||
if isinstance(exc, subprocess.CalledProcessError) and exc.stderr:
|
||||
print(exc.stderr.strip(), file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user