Mini-API zwischen beiden Programmen, Proforma-Rechnung
MINI-API (seine Ansage: "die muessen ja sehen das die beiden auf sind"): Jedes Programm macht beim Start einen winzigen Server auf 127.0.0.1 mit einem Port, den das Betriebssystem aussucht, und traegt Port und PID in die gemeinsame SQLite ein - die ist das Telefonbuch. Zwei Nachrichten reichen: "ping" wird mit "hallo, bin noch da" beantwortet, "schau_nach" ist der Push. Der Push traegt KEINE Daten. Er sagt nur "schau in die sqlite" - dort steht die Meldung ohnehin. Eine Wahrheit, und wenn der Anruf nicht durchgeht, findet das andere Programm die Meldung beim naechsten Nachsehen. Gebunden wird nur auf Loopback: von aussen ist nichts erreichbar, Windows fragt bei 127.0.0.1 nicht nach der Firewall, und die API fuehrt keine Befehle aus. Kein Netzwerk-Server mit MySQL - dafuer braeuchte er Infrastruktur, die es nicht gibt; auf verschiedenen Rechnern waere ohnehin ein anderer Bau noetig. PROFORMA-RECHNUNG: ein Knopf, gewaehlt wird im Popup - die fruehere Sicherheitsfrage IST diese Auswahl geworden (eine Frage statt zwei). Seine Bedingung: "musst nur auf passen das der rechner dann die rechnungs nummer nicht aendert". Die Proforma laeuft in einer EIGENEN Reihe (2026-P001); Nummernfeld, Rechnungszaehler, Nummernbuch und die Meldung ans Journal bleiben unberuehrt. Genau das prueft pruef_proforma.py zuerst. An den Quellen geprueft, nicht an Ratgeberseiten: * § 14 Abs. 1 S. 1 UStG / UStAE 14.1 - Rechnung ist jedes Dokument, mit dem abgerechnet wird, "gleichgueltig, wie dieses Dokument im Geschaeftsverkehr bezeichnet wird". Das Wort "Proforma" allein schuetzt also nicht. * § 14c Abs. 2 UStG - wer Steuer gesondert ausweist, ohne berechtigt zu sein, schuldet den Betrag; Korrektur nur mit Antrag und Zustimmung des Finanzamts. * § 14 Abs. 4 Nr. 4 UStG - die fortlaufende, einmalig vergebene Nummer gilt fuer Rechnungen. Deshalb: Ueberschrift "Proforma-Rechnung", der Satz "keine Rechnung im Sinne des § 14 UStG ... nicht zu bezahlen", "Proforma an:" statt "Rechnung an:", die Nummer ausdruecklich als KEINE Rechnungsnummer, KEIN Steuersatz und KEIN Steuerbetrag (auch die Beherbergungssteuer nicht - sonst waere das Blatt halb ausgewiesen, halb nicht), dafuer "Voraussichtlicher Gesamtbetrag" mit dem Hinweis, dass alles enthalten ist. Kein Blatt fuers Amt, kein Zahlungstext. TESTLAUF: _testwahl sagt, was im Auswahlfenster geklickt worden waere; pruef_proforma setzt _dialog_zeigen und drueckt die echten Knoepfe. Pruefstaende: pruef_proforma.py (neu, mit dem echten Popup), pruef_montage, pruef_storno, pruef_kette, pruef_kernregeln, pruef_nummernbuch, pruef_nummern, pruef_dialoge - alle gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
This commit is contained in:
parent
8163213960
commit
94c2c7881a
7 changed files with 703 additions and 46 deletions
48
gemeinsam.py
48
gemeinsam.py
|
|
@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS laeuft (
|
|||
programm TEXT PRIMARY KEY, -- 'journal' | 'rechnungstool'
|
||||
pid INTEGER NOT NULL DEFAULT 0,
|
||||
exe TEXT NOT NULL DEFAULT '',
|
||||
port INTEGER NOT NULL DEFAULT 0, -- wo seine Mini-API lauscht (127.0.0.1)
|
||||
seit TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
|
|
@ -191,6 +192,11 @@ class Nummernbuch:
|
|||
con = sqlite3.connect(self.pfad, timeout=10)
|
||||
con.row_factory = sqlite3.Row
|
||||
con.executescript(SCHEMA)
|
||||
# Spalte nachziehen: CREATE TABLE IF NOT EXISTS aendert eine bestehende
|
||||
# Tabelle nicht, und ein Buch aus der Fassung ohne API hat sie noch nicht.
|
||||
spalten = {z["name"] for z in con.execute("PRAGMA table_info('laeuft')")}
|
||||
if spalten and "port" not in spalten:
|
||||
con.execute("ALTER TABLE laeuft ADD COLUMN port INTEGER NOT NULL DEFAULT 0")
|
||||
return con
|
||||
|
||||
# ---- Lesen -------------------------------------------------------------
|
||||
|
|
@ -248,17 +254,37 @@ class Nummernbuch:
|
|||
con.execute("SELECT storno_zu FROM nummern WHERE storno_zu <> ''")}
|
||||
|
||||
# ---- Wer laeuft, und der Briefkasten -----------------------------------
|
||||
def anmelden(self, programm, pid, exe):
|
||||
"""Eintragen: dieses Programm ist offen."""
|
||||
def anmelden(self, programm, pid, exe, port=0):
|
||||
"""Eintragen: dieses Programm ist offen - und wo seine API lauscht.
|
||||
|
||||
Die Tabelle ist das Telefonbuch der beiden Programme: PID (laeuft es
|
||||
ueberhaupt?) und Port (wo erreiche ich es?).
|
||||
"""
|
||||
import datetime
|
||||
with self._verbindung() as con:
|
||||
con.execute(
|
||||
"INSERT INTO laeuft (programm, pid, exe, seit) VALUES (?,?,?,?) "
|
||||
"INSERT INTO laeuft (programm, pid, exe, port, seit) VALUES (?,?,?,?,?) "
|
||||
"ON CONFLICT(programm) DO UPDATE SET pid=excluded.pid, exe=excluded.exe, "
|
||||
"seit=excluded.seit",
|
||||
(str(programm), int(pid), str(exe),
|
||||
"port=excluded.port, seit=excluded.seit",
|
||||
(str(programm), int(pid), str(exe), int(port or 0),
|
||||
datetime.datetime.now().isoformat(timespec="seconds")))
|
||||
log.info("angemeldet: %s (PID %s)", programm, pid)
|
||||
log.info("angemeldet: %s (PID %s, Port %s)", programm, pid, port)
|
||||
|
||||
def wer_laeuft(self, programm):
|
||||
"""Telefonbuch-Eintrag eines laufenden Programms - oder None.
|
||||
|
||||
Geprueft wird die PID (Windows antwortet), nicht ein Zeitstempel. Eine
|
||||
Karteileiche wird gleich weggeraeumt.
|
||||
"""
|
||||
with self._verbindung() as con:
|
||||
z = con.execute("SELECT programm, pid, exe, port FROM laeuft WHERE programm=?",
|
||||
(str(programm),)).fetchone()
|
||||
if not z:
|
||||
return None
|
||||
if not prozess_laeuft(z["pid"], z["exe"]):
|
||||
self.abmelden(programm)
|
||||
return None
|
||||
return dict(z)
|
||||
|
||||
def abmelden(self, programm):
|
||||
with self._verbindung() as con:
|
||||
|
|
@ -266,15 +292,7 @@ class Nummernbuch:
|
|||
|
||||
def laeuft(self, programm):
|
||||
"""Laeuft dieses Programm noch? Karteileichen werden weggeraeumt."""
|
||||
with self._verbindung() as con:
|
||||
z = con.execute("SELECT pid, exe FROM laeuft WHERE programm=?",
|
||||
(str(programm),)).fetchone()
|
||||
if not z:
|
||||
return False
|
||||
if prozess_laeuft(z["pid"], z["exe"]):
|
||||
return True
|
||||
self.abmelden(programm)
|
||||
return False
|
||||
return self.wer_laeuft(programm) is not None
|
||||
|
||||
def melden(self, absender, text):
|
||||
"""Eine Zeile in den Briefkasten legen."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue