Gleiche Umstellung wie im Rechnungstool, gleiche gemeinsame Dateien
(gemeinsam.py, ordnerwahl.py, wasserzeichen.py, bauen.py).
%LOCALAPPDATA%/ravokk/
journal.sqlite3, berichte/, Nummernbuch, config.json <- Daten
Rechnungstool/ <- Programm
Steuerrechnungstool/ <- Programm
⚠ Hier gefunden und behoben: das Journal der INSTALLIERTEN Fassung lag im
Temp-Ordner. Der Merkzettel (ablage.py) zeigte auf
%TEMP%/abnahme_bst/Daten - haengengeblieben aus einem Abnahmelauf vom
03.09.2026. Windows raeumt den Temp-Ordner von selbst auf; das Journal
waere ohne Vorwarnung verschwunden. Ein Merkzettel, der dorthin zeigt,
wird jetzt verworfen statt befolgt.
Sein Echtbestand (238 Buchungen) lag davon unberuehrt in der Quelle und
ist gesichert.
Kein Ordner-Dialog mehr beim ersten Start - der gemeinsame Ordner steht
fest. Der Knopf hiess dort "Install-Ordner auswaehlen...", was schon immer
falsch war: es geht um das gemeinsame Nummernbuch, nicht um den
Installationsort.
Uninstaller raeumt jetzt auch die vom Updater geladenen Fassungen weg.
pruef_installation.py prueft beides zusammen: Programmordner leer, Daten
unangetastet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
185 lines
6.7 KiB
Python
185 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: installieren, deinstallieren - bleibt der Ordner sauber?
|
|
|
|
Seine Ansage vom 07.09.2026:
|
|
|
|
"du solltest die auch mal deinstallieren und neu installieren von windows
|
|
lassen, dass du auch pruefen kannst, dass der ordner dann leer ist und
|
|
sauber - will keine tools die unsauber sind"
|
|
|
|
Er hatte recht. Gemessen blieb nach dem Deinstallieren liegen:
|
|
|
|
Rechnungstool-1.7.1.exe, Rechnungstool-1.8.0.exe <- vom UPDATER geladen
|
|
Daten/config.json, Daten/rechnungstool.pid
|
|
|
|
Die EXEs kennt der Installer nicht - die hat der Updater abgelegt. Ohne eine
|
|
eigene Regel bleiben sie stehen, und mit jeder Fassung wird es eine mehr.
|
|
|
|
ZWEI DINGE MUESSEN GLEICHZEITIG STIMMEN
|
|
---------------------------------------
|
|
1. Der PROGRAMMordner ist danach restlos weg.
|
|
2. Die DATEN sind unangetastet - Rechnungen, Journal, Nummernbuch. Die sind
|
|
zehn Jahre aufbewahrungspflichtig (§ 147 AO); ein Deinstallieren darf sie
|
|
nie mitnehmen.
|
|
|
|
Beides zusammen geht nur, weil die Daten NEBEN dem Programm liegen:
|
|
|
|
%LOCALAPPDATA%/ravokk/ <- Daten, gemeinsames Nummernbuch
|
|
Rechnungstool/ <- Programm, wird geleert
|
|
Steuerrechnungstool/ <- Programm, wird geleert
|
|
|
|
Genau so hat er es beschrieben: *"es gibt ravokk als main verzeichnis, da liegt
|
|
config und sqlite drin, und dann gibt es den ordner rechnungstool und
|
|
steuertool"* - und dazu, warum es so heisst: *"ravokk ist meine firma und die
|
|
programme gehoeren zu der firma"*.
|
|
|
|
⚠ DIESER PRUEFSTAND INSTALLIERT WIRKLICH. Er laeuft deshalb nur, wenn das
|
|
Setup gebaut ist, und er fasst NUR die Programmordner an - vorhandene Daten
|
|
werden vorher gesichert und danach zurueckgelegt.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
HIER = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HIER)
|
|
|
|
import version # noqa: E402
|
|
|
|
LOKAL = os.environ.get("LOCALAPPDATA", "")
|
|
RAVOKK = os.path.join(LOKAL, "ravokk")
|
|
NAME = os.path.splitext(version.EXE_NAME)[0]
|
|
PROGRAMMORDNER = os.path.join(RAVOKK, NAME)
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
|
kod = sys.stdout.encoding or "utf-8"
|
|
print(zeile.encode(kod, "replace").decode(kod))
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
def setup_datei():
|
|
ordner = os.path.join(HIER, "release")
|
|
if not os.path.isdir(ordner):
|
|
return ""
|
|
for d in sorted(os.listdir(ordner)):
|
|
if d.startswith(NAME + "-Setup") and d.endswith(".exe"):
|
|
return os.path.join(ordner, d)
|
|
return ""
|
|
|
|
|
|
def beenden():
|
|
subprocess.run(["powershell", "-NoProfile", "-Command",
|
|
"Get-Process | Where-Object { $_.ProcessName -match "
|
|
f"'{NAME}|Updater' " + "} | Stop-Process -Force "
|
|
"-ErrorAction SilentlyContinue"], capture_output=True)
|
|
time.sleep(1.5)
|
|
|
|
|
|
def inhalt(ordner):
|
|
if not os.path.isdir(ordner):
|
|
return []
|
|
gefunden = []
|
|
for wurzel, _, dateien in os.walk(ordner):
|
|
for d in dateien:
|
|
gefunden.append(os.path.relpath(os.path.join(wurzel, d), ordner))
|
|
return sorted(gefunden)
|
|
|
|
|
|
setup = setup_datei()
|
|
if not setup:
|
|
print(f"UEBERSPRUNGEN | kein Setup in release/ (vorher: python bauen.py)")
|
|
sys.exit(0)
|
|
if not LOKAL:
|
|
print("UEBERSPRUNGEN | kein LOCALAPPDATA")
|
|
sys.exit(0)
|
|
|
|
# ---- Vorhandene Daten in Sicherheit bringen -----------------------------
|
|
# Hier laeuft echte Software auf einem echten Rechner. Was schon da ist, wird
|
|
# beiseitegelegt und hinterher zurueckgestellt - ein Pruefstand darf niemandem
|
|
# seine Buchhaltung anfassen.
|
|
sicher = tempfile.mkdtemp(prefix="installpruef_")
|
|
gesichert = []
|
|
if os.path.isdir(RAVOKK):
|
|
for d in os.listdir(RAVOKK):
|
|
quelle = os.path.join(RAVOKK, d)
|
|
if os.path.isfile(quelle):
|
|
shutil.copy2(quelle, os.path.join(sicher, d))
|
|
gesichert.append(d)
|
|
print(f" ({len(gesichert)} vorhandene Datei(en) gesichert)")
|
|
|
|
beenden()
|
|
alter_uninstaller = os.path.join(PROGRAMMORDNER, "unins000.exe")
|
|
if os.path.exists(alter_uninstaller):
|
|
subprocess.run([alter_uninstaller, "/VERYSILENT", "/SUPPRESSMSGBOXES",
|
|
"/NORESTART"], capture_output=True)
|
|
time.sleep(6)
|
|
|
|
# ---- 1. Installieren ----------------------------------------------------
|
|
e = subprocess.run([setup, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"],
|
|
capture_output=True)
|
|
time.sleep(5)
|
|
pruefe(e.returncode == 0, f"das Setup laeuft durch (Rueckgabe {e.returncode})")
|
|
pruefe(os.path.isdir(PROGRAMMORDNER),
|
|
f"das Programm landet unter ravokk ({PROGRAMMORDNER})")
|
|
dabei = inhalt(PROGRAMMORDNER)
|
|
pruefe(any(d.startswith(NAME + "-") for d in dabei),
|
|
f"die Programmdatei liegt dort ({dabei})")
|
|
pruefe("Updater.exe" in dabei, "und der Updater daneben")
|
|
|
|
# ---- 2. Eine Datei anlegen, wie der Updater es taete ---------------------
|
|
# So sieht der Ordner aus, nachdem das Programm ein paar Fassungen lang
|
|
# gelaufen ist. Genau diese Dateien blieben frueher liegen.
|
|
geerbt = os.path.join(PROGRAMMORDNER, f"{NAME}-9.9.9.exe")
|
|
with open(geerbt, "wb") as f:
|
|
f.write(b"als haette der Updater sie geladen")
|
|
with open(os.path.join(PROGRAMMORDNER, "installiert.json"), "w",
|
|
encoding="utf-8") as f:
|
|
f.write("{}")
|
|
|
|
# ---- 3. Eine Datendatei daneben, die ueberleben MUSS ---------------------
|
|
probe = os.path.join(RAVOKK, "pruefstand-beleg.txt")
|
|
with open(probe, "w", encoding="utf-8") as f:
|
|
f.write("steht fuer Rechnungen, Journal und Nummernbuch")
|
|
|
|
# ---- 4. Deinstallieren --------------------------------------------------
|
|
beenden()
|
|
u = os.path.join(PROGRAMMORDNER, "unins000.exe")
|
|
pruefe(os.path.exists(u), "es gibt einen Uninstaller")
|
|
if os.path.exists(u):
|
|
subprocess.run([u, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"],
|
|
capture_output=True)
|
|
time.sleep(7)
|
|
|
|
uebrig = inhalt(PROGRAMMORDNER)
|
|
pruefe(not os.path.isdir(PROGRAMMORDNER),
|
|
f"der Programmordner ist danach weg ({uebrig})")
|
|
pruefe(not uebrig,
|
|
f"und darin liegt nichts mehr - auch keine geladene Fassung ({uebrig})")
|
|
|
|
pruefe(os.path.exists(probe),
|
|
"die Daten daneben sind unangetastet (Rechnungen, Journal, Nummernbuch)")
|
|
pruefe(os.path.isdir(RAVOKK),
|
|
"und der Ordner der Firma steht noch")
|
|
|
|
# ---- Aufraeumen ---------------------------------------------------------
|
|
if os.path.exists(probe):
|
|
os.remove(probe)
|
|
for d in gesichert:
|
|
ziel = os.path.join(RAVOKK, d)
|
|
if not os.path.exists(ziel):
|
|
os.makedirs(RAVOKK, exist_ok=True)
|
|
shutil.copy2(os.path.join(sicher, d), ziel)
|
|
shutil.rmtree(sicher, ignore_errors=True)
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Deinstallieren laesst den Programmordner leer - und die Daten stehen")
|