Initial commit: v1.4 with documentation

This commit is contained in:
2026-08-15 17:04:43 +02:00
parent 7c976e8619
commit 9838a86660
8 changed files with 503 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
.DS_Store
+68
View File
@@ -0,0 +1,68 @@
Änderungshistorie
Version 1.4:
(1)
Bei mehreren Buchungsjahren wurden die Protokolle in die verkehrte /Versendet-Gruppe geschrieben.
Beispiel:
/Belege
├── /2026
├── /01-Januar
└── [Quittung-1]
└── [Beleg-x]
[...]
/Versendet
├── /2027
├── /01-Januar
└── [Quittung-1]
└── [Beleg-y]
[...]
/Provisionsabrechnungen
/Versendet
Die Protokolle landen jetzt in der /Versendet-Gruppe des Jahres, aus dem die versendeten Belege stammen.
(2)
Beleg-Versand über mehrere Jahre wird untersagt:
Die Protokolle liegen in einer einzigen /Versendet-Gruppe. Um eine Split-Funktion zu vermeiden (beleg-1 in 2026, beleg-2 in 2027), wird vor der Pack-/Sende-Routine sichergestellt, dass lediglich Dokumente aus einem einzigen Buchungsjahr in der Auswahl sind.
Version 1.3
Dry Run schreibt immer eine vollständige Vorschau-Datei
Dry Run zeigt zusätzlich eine Kurzliste direkt im Dialog
Vorschau-Datei kann direkt im Finder angezeigt werden
Echtlauf läuft nach Bestätigung ohne zweite Rückfrage weiter
Abschlussmeldung weist auf die Prüfung des automatisch eingefügten Mailtexts hin
menschenlesbares Markdown-Log wird in Sendelauf-Blöcken gegliedert
neuer Config-Key PATH_TO_DRYRUN_PREVIEW
README und Konfigurationsbeschreibung entsprechend erweitert
Version 1.2.1
interne Scripts in Unterordner Scripts
Mailbody in _config/mail_vorlagen/belegversand
Mail-Betreff ohne Batch-ID
konfigurierbares Dokument-Tag (SENT_TAG_FOR_DOCS)
Konfigurationsdatei klarer gegliedert
interne README und Archiv-README ergänzt
Version 1.2
Mailbody-Datei in DEVONthink eingeführt
README in DEVONthink eingeführt
Welcome-/Hinweisdialog über SHOW_README
Konfiguration in _config weiter ausgebaut
Version 1.1
Konfiguration in DEVONthink eingeführt
Setup-Script ergänzt
Startscript ergänzt
Root-relative Suche der Konfiguration
Version 1.0
erster funktionaler Versandablauf
ZIP-Erzeugung
Mail-Entwurf
Tagging
NDJSON-/Markdown-Logging
+79
View File
@@ -0,0 +1,79 @@
tell application id "DNtp"
set monthNames to {"01_Januar", "02_Februar", "03_MŠrz", "04_April", "05_Mai", "06_Juni", "07_Juli", "08_August", "09_September", "10_Oktober", "11_November", "12_Dezember"}
set monthVariants to {"januar", "jan", "january", Â
"februar", "feb", "february", Â
"mŠrz", "maerz", "mrz", "mŠr", "march", "mar", Â
"april", "apr", Â
"mai", "may", Â
"juni", "jun", "june", Â
"juli", "jul", "july", Â
"august", "aug", Â
"september", "sep", "sept", Â
"oktober", "okt", "october", "oct", Â
"november", "nov", Â
"dezember", "dez", "december", "dec"}
set selRecs to selection as list
if (count of selRecs) is 0 then
display dialog "Bitte einen Ordner in DEVONthink auswŠhlen." buttons {"OK"} default button "OK"
return
end if
set targetGroup to first item of selRecs
set targetName to name of targetGroup
set normalizedTargetName to my normalizeMonthFolderName(targetName)
if monthVariants contains normalizedTargetName then
set answerButton to button returned of (display dialog "Du stehst im Ordner " & targetName & ". Willst du hier trotzdem einen Jahreszyklus anlegen?" buttons {"Abbrechen", "Ja"} default button "Ja" with icon caution)
if answerButton is "Abbrechen" then return
end if
repeat with i from 1 to count of monthNames
set monthName to item i of monthNames
try
create record with {name:monthName, type:group} in targetGroup
end try
end repeat
try
create record with {name:"Versendet", type:group} in targetGroup
end try
display dialog "Fertig." buttons {"OK"} default button "OK"
end tell
on normalizeMonthFolderName(theName)
set t to (do shell script "printf %s " & quoted form of theName & " | tr '[:upper:]' '[:lower:]'")
set t to my replaceText(".", "", t)
set t to my replaceText("_", " ", t)
set t to my replaceText("-", " ", t)
set t to my trimText(t)
try
set t to do shell script "printf %s " & quoted form of t & " | sed -E 's/^[0-9]{1,3}[[:space:]]+//; s/^[0-9]{1,3}//'"
on error
-- falls sed zickt, einfach Original weiterverwenden
end try
set t to my trimText(t)
return t
end normalizeMonthFolderName
on replaceText(findText, replaceWith, sourceText)
set AppleScript's text item delimiters to findText
set textItems to every text item of sourceText
set AppleScript's text item delimiters to replaceWith
set newText to textItems as text
set AppleScript's text item delimiters to ""
return newText
end replaceText
on trimText(t)
set whitespaceChars to {space, tab, return, linefeed}
repeat while t is not "" and (character 1 of t is in whitespaceChars)
set t to text 2 thru -1 of t
end repeat
repeat while t is not "" and (character -1 of t is in whitespaceChars)
set t to text 1 thru -2 of t
end repeat
return t
end trimText
Binary file not shown.
Binary file not shown.
Binary file not shown.
+36
View File
@@ -0,0 +1,36 @@
(*
uuid_anzeigen.scpt
Zeigt Name, Typ und UUID des aktuell ausgewŠhlten DEVONthink-Objekts an.
Die UUID wird zusŠtzlich in die Zwischenablage kopiert.
Zweck:
- Diagnose
- manuelles Nachschlagen einer DEVONthink-UUID
- Vorbereitung fŸr das manuelle Aktualisieren von BELEGE_ROOT_UUID
*)
tell application id "DNtp"
set sel to selection
if sel is {} then error "Bitte zuerst ein Objekt in DEVONthink auswŠhlen."
set r to item 1 of sel
set recName to name of r as text
try
set recType to type of r as text
on error
set recType to "unbekannt"
end try
set recUUID to uuid of r as text
end tell
set the clipboard to recUUID
display dialog "AusgewŠhltes Objekt:" & return & return & Â
"Name: " & recName & return & Â
"Typ: " & recType & return & Â
"UUID: " & recUUID & return & return & Â
"Die UUID wurde in die Zwischenablage kopiert." buttons {"OK"} default button "OK"
+319
View File
@@ -0,0 +1,319 @@
(*
START.scpt - Version 1.4
Rolle dieses Scripts
--------------------
Dieses Script ist der sichtbare Einstiegspunkt fŸr Anwender:innen.
Es selbst verschickt keine Belege und fŸhrt auch keine Einrichtung durch,
sondern Ÿbernimmt nur zwei Aufgaben:
1. PrŸfen, ob relativ zur aktuellen DEVONthink-Auswahl eine Konfigurationsdatei
fŸr den Belegversand existiert.
2. Falls in dieser Konfiguration SHOW_README=true gesetzt ist,
einen kurzen Willkommen-/Hinweisdialog anzeigen.
3. Danach das Setup-Script _belegversand_setup.scpt starten.
Das Setup-Script entscheidet anschlie§end selbst:
- ob bereits eine gŸltige Einrichtung vorliegt und direkt das Hauptscript
gestartet werden kann,
- oder ob zunŠchst die Konfiguration eingerichtet werden muss.
Wichtig:
--------
START.scpt ist bewusst klein gehalten.
Die eigentliche Fachlogik liegt in:
- _belegversand_setup.scpt
- _belegversand.scpt
*)
-- Name des Setup-Scripts, das im Unterordner "Helpers/scripts" liegt.
-- Dieses Script wird von START.scpt immer am Ende aufgerufen.
property setupScriptName : "_belegversand_setup.scpt"
-- Name der DEVONthink-Gruppe, in der Konfigurationsdateien liegen.
property configGroupName : "_config"
-- Name der eigentlichen Konfigurationsdatei in DEVONthink.
property configRecordName : "belegversand.conf"
(*
runSiblingScript(scriptFileName)
--------------------------------
LŠdt und startet ein weiteres AppleScript aus dem Unterordner "Helpers/scripts".
Parameter:
- scriptFileName : Dateiname des aufzurufenden Scripts, z. B.
"_belegversand_setup.scpt"
Ablauf:
- absolute Basis-Path setzen (Home + fest Pfad zu Belegversand_1-4)
- daran den Unterordner "Helpers/scripts" und den Dateinamen anhŠngen
- prŸfen, ob die Scriptdatei existiert
- Script laden
- Script ausfŸhren
Fehlerbehandlung:
- wenn die Datei nicht gefunden wird -> Hinweisdialog
- wenn das Laden oder Starten fehlschlŠgt -> Hinweisdialog mit Fehlermeldung
Wichtig:
--------
Der absolute Pfad ist invariant und unabhŠngig vom Speicherort dieses Scripts.
*)
on runSiblingScript(scriptFileName)
-- Absolute Basis-Path (Home + fest Installation)
set basePath to (do shell script "echo ~") & "/Library/Application Scripts/com.devon-technologies.think/Menu/Belegversand_1-4"
set targetPath to basePath & "/Helpers/scripts/" & scriptFileName
try
set targetAlias to POSIX file targetPath as alias
on error
display dialog "Hinweis: Scriptdatei nicht gefunden:" & return & targetPath buttons {"OK"} default button "OK"
return
end try
try
set loadedScript to load script targetAlias
run loadedScript
on error errMsg number errNum
display dialog "Hinweis: Konnte Script nicht starten:" & return & scriptFileName & return & return & errMsg buttons {"OK"} default button "OK"
end try
end runSiblingScript
(*
recordText(theRecord)
---------------------
Liest den Textinhalt eines DEVONthink-Records.
Verwendung in START.scpt:
-------------------------
Hier wird dieser Handler genutzt, um den Inhalt der Datei
"_config/belegversand.conf" als Text einzulesen.
Ablauf:
- zunŠchst plain text versuchen
- wenn das fehlschlŠgt, auf content ausweichen
- wenn beides fehlschlŠgt, leeren String zurŸckgeben
Hinweis:
--------
Die Konvertierung erscheint hier als "rich text", weil AppleScript/Script Editor
solche Stellen beim Kompilieren teilweise selbststŠndig umschreiben kann.
Der funktionale Zweck ist hier aber: Textinhalt lesbar zurŸckgeben.
*)
on recordText(theRecord)
tell application id "DNtp"
try
set t to plain text of theRecord
if t is missing value then set t to ""
return t as rich text
on error
try
set t to content of theRecord
if t is missing value then set t to ""
return t as rich text
on error
return ""
end try
end try
end tell
end recordText
(*
findChildByName(parentRecord, childName)
----------------------------------------
Sucht innerhalb einer DEVONthink-Gruppe ein direktes Kind mit bestimmtem Namen.
Parameter:
- parentRecord : die Gruppe, in der gesucht werden soll
- childName : Name des gesuchten direkten Kinds
RŸckgabe:
- den gefundenen DEVONthink-Record
- oder missing value, wenn nichts passt
Verwendung in START.scpt:
-------------------------
- _config-Gruppe unter der DB-Wurzel suchen
- belegversand.conf innerhalb von _config suchen
*)
on findChildByName(parentRecord, childName)
tell application id "DNtp"
try
set kids to children of parentRecord
repeat with i from 1 to count of kids
set c to item i of kids
if (name of c as rich text) is childName then return c
end repeat
end try
end tell
return missing value
end findChildByName
(*
getTopParentGroup(theRecord)
----------------------------
Ermittelt die oberste Gruppe einer DEVONthink-Hierarchie ausgehend von einem Record.
Ablauf:
- starte mit dem aktuell Ÿbergebenen Record
- frage wiederholt nach dessen Eltern
- solange es noch einen Parent gibt, gehe eine Ebene nach oben
- sobald kein Parent mehr vorhanden ist, ist die Wurzel erreicht
Verwendung in START.scpt:
-------------------------
START.scpt kennt die gewŸnschte Datenbank nicht direkt.
Darum nimmt es irgendein ausgewŠhltes Objekt in DEVONthink
und geht davon Ÿber die Parent-Kette bis zur obersten Gruppe hoch.
Dort wird dann _config gesucht.
Vorteil:
--------
Das Script ist dadurch nicht an einen festen Datenbanknamen gebunden,
sondern arbeitet relativ zur aktuellen Auswahl.
*)
on getTopParentGroup(theRecord)
tell application id "DNtp"
set currentRecord to theRecord
repeat
try
set p to parents of currentRecord
if p is {} then return currentRecord
set currentRecord to item 1 of p
on error
return currentRecord
end try
end repeat
end tell
end getTopParentGroup
(*
configValue(configText, keyName)
--------------------------------
Liest aus einem einfachen key=value-Konfigurationstext den Wert zu einem bestimmten SchlŸssel.
Parameter:
- configText : kompletter Textinhalt der Konfigurationsdatei
- keyName : gesuchter SchlŸssel, z. B. "SHOW_README"
RŸckgabe:
- Textwert des gefundenen SchlŸssels
- oder missing value, wenn kein passender Eintrag existiert
Funktionsweise:
---------------
- zeilenweise durch die Konfiguration gehen
- prŸfen, ob eine Zeile mit "KEY=" beginnt
- den Teil rechts vom "=" zurŸckgeben
*)
on configValue(configText, keyName)
set keyPrefix to keyName & "="
repeat with oneLine in paragraphs of configText
set lineText to oneLine as text
if lineText starts with keyPrefix then
if (length of lineText) is equal to (length of keyPrefix) then
return ""
else
set val to text ((length of keyPrefix) + 1) thru -1 of lineText
return val as text
end if
end if
end repeat
return missing value
end configValue
(*
findConfigInCurrentDB()
-----------------------
Sucht die Konfigurationsdatei belegversand.conf relativ zur aktuellen DEVONthink-Auswahl.
Ablauf:
1. aktuelle Auswahl holen
2. falls nichts ausgewŠhlt ist -> missing value
3. von der Auswahl zur DB-Wurzel hochgehen
4. dort die Gruppe _config suchen
5. darin die Datei belegversand.conf suchen
RŸckgabe:
- den gefundenen Config-Record
- oder missing value, wenn keine passende Konfiguration existiert
Wichtig:
--------
Es wird absichtlich nicht global in allen Datenbanken gesucht,
sondern immer nur relativ zur aktuellen Auswahl.
So bleibt das Verhalten bei mehreren Datenbanken stabil und nachvollziehbar.
*)
on findConfigInCurrentDB()
tell application id "DNtp"
set sel to selection
if sel is {} then return missing value
set selectedRecord to item 1 of sel
end tell
set dbRootRecord to my getTopParentGroup(selectedRecord)
set configGroupRecord to my findChildByName(dbRootRecord, configGroupName)
if configGroupRecord is missing value then return missing value
set configRecord to my findChildByName(configGroupRecord, configRecordName)
return configRecord
end findConfigInCurrentDB
(*
showWelcomeDialogIfNeeded()
---------------------------
Zeigt den Start-/Willkommensdialog an, falls die Konfiguration dies wŸnscht.
Ablauf:
1. Config-Datei relativ zur aktuellen Auswahl suchen
2. Config-Datei lesen
3. Wert SHOW_README auslesen
4. falls SHOW_README=true -> Hinweisdialog anzeigen
5. falls User "Diesen Dialog nicht mehr anzeigen" wŠhlt:
SHOW_README auf false in der Config zurŸckschreiben
Hinweis:
--------
START.scpt fŸhrt selbst keine Einrichtung und keinen Versand durch.
Der Dialog ist nur eine Hilfe fŸr Anwender:innen.
Er verweist auf die interne README-Datei in _config.
Bewusst einfach:
----------------
Es gibt hier nur zwei Buttons:
- "Weiter"
- "Diesen Dialog nicht mehr anzeigen"
Die README wird nicht direkt aus dem Dialog gešffnet.
*)
on showWelcomeDialogIfNeeded()
set configRecord to my findConfigInCurrentDB()
if configRecord is missing value then return
set cfgText to my recordText(configRecord)
set showReadmeVal to my configValue(cfgText, "SHOW_README")
if showReadmeVal is missing value then return
set showReadmeLower to do shell script "printf %s " & quoted form of showReadmeVal & " | tr '[:upper:]' '[:lower:]'"
if showReadmeLower is not "true" then return
set msgText to "Willkommen beim Belegversand, Version 1.4." & return & return & Â
"Mit dieser Funktion kannst du Dokumente aus dieser DEVONthink-Datenbank auswŠhlen, in ein ZIP-Archiv packen und als Mail-Entwurf vorbereiten." & return & return & Â
"Anschlie§end kšnnen die verarbeiteten Dokumente mit dem Tag 'gesendet' markiert werden. Der Versand wird zusŠtzlich in Protokolldateien dokumentiert." & return & return & Â
"Hinweise zur Einrichtung und Benutzung findest du in der Datei:" & return & Â
"README-Belegversand" & return & Â
"im Ordner:" & return & Â
"_config"
set choice to button returned of (display dialog msgText buttons {"Diesen Dialog nicht mehr anzeigen", "Weiter"} default button "Weiter")
if choice is "Diesen Dialog nicht mehr anzeigen" then
set newCfgText to ""
repeat with oneLine in paragraphs of cfgText
set lineText to oneLine as text
if lineText starts with "SHOW_README=" then
set newCfgText to newCfgText & "SHOW_README=false" & return
else
set newCfgText to newCfgText & lineText & return
end if
end repeat
tell application id "DNtp"
try
set plain text of configRecord to newCfgText
on error
set content of configRecord to newCfgText
end try
end tell
end if
end showWelcomeDialogIfNeeded
(*
run
---
Haupteinstiegspunkt des START-Scripts.
Ablauf:
1. optionalen Startdialog anzeigen
2. Setup-Script starten
Wichtig:
--------
START.scpt delegiert die eigentliche Entscheidung an das Setup-Script.
Dort wird geprŸft:
- existiert schon eine gŸltige Konfiguration?
-> dann Hauptscript starten
- oder muss erst eingerichtet werden?
*)
on run
with timeout of 3600 seconds
my showWelcomeDialogIfNeeded()
my runSiblingScript(setupScriptName)
end timeout
end run