Files
DEVONthink-Belegversand-Aut…/Helpers/scripts/_belegversand_setup.applescript
T
enchiriadis e88720b2ca Helpers/scripts/_belegversand_setup.applescript aktualisiert
on buildReadmeText()
Doku und Tutorial jetzt im MediaWiki.
URL angepasst.
2026-08-18 12:58:21 +00:00

606 lines
22 KiB
AppleScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(*
_belegversand_setup.scpt – Version 1.4
Rolle dieses Scripts
--------------------
Dieses Script hat eine Doppelfunktion:
1. Es prüft, ob relativ zur aktuellen DEVONthink-Datenbank bereits eine
gültige Konfiguration für den Belegversand existiert.
Falls ja, startet es direkt das Hauptscript _belegversand.scpt.
2. Falls noch keine gültige Konfiguration existiert, führt es die
Ersteinrichtung durch:
- Belege-Hauptgruppe bestimmen
- _config-Struktur anlegen
- Konfigurationsdatei schreiben
- README-Datei anlegen/aktualisieren
- Mailbody-Datei anlegen/aktualisieren
- globale /Versendet_Outsider-Gruppe anlegen
Zielstruktur in DEVONthink
--------------------------
DB-Wurzel
├── Versendet_Outsider
│ ├── !Ergebnis
│ ├── sentlog_outsiders
│ └── sentlog_outsiders.ndjson
└── _config
├── belegversand.conf
├── README-Belegversand
└── mail_vorlagen
└── belegversand
Wichtig:
--------
Dieses Script ist der eigentliche "Dispatcher" zwischen
- Einrichtung
- und normalem Versand.
Version 1.4:
-----------
- MAIL_CC_TO_DEFAULT in Config hinzugefügt
- Globale Versendet_Outsider-Gruppe wird automatisch angelegt
*)
-- Name der DEVONthink-Konfigurationsgruppe.
property configGroupName : "_config"
-- Name der Konfigurationsdatei.
property configRecordName : "belegversand.conf"
-- Name des Hilfetext-Records in DEVONthink.
property readmeRecordName : "README-Belegversand"
-- Name des Unterordners innerhalb von _config, in dem Mail-Vorlagen liegen.
property defaultMailBodyFolderName : "mail_vorlagen"
-- Name des Standard-Mailbody-Records.
property defaultMailBodyRecordName : "belegversand"
-- Schlüsselfeld, an dem eine Konfiguration als "gültig" erkannt wird.
-- Wenn BELEGE_ROOT_UUID fehlt, gilt die Config als unvollständig.
property requiredConfigKey : "BELEGE_ROOT_UUID="
-- Name des eigentlichen Hauptscripts.
property mainScriptName : "_belegversand.scpt"
-- Versionsinformation für README und Hinweise.
property appVersion : "1.4"
property basePath : "Library/Application Scripts/com.devon-technologies.think/Menu/Belegversand"
-- Default-Werte für die Erstkonfiguration.
property defaultMailTo : "steuer@name.de"
property defaultSubjectPrefix : "Unterlagen"
property defaultZipPassword : ""
property defaultLogSentGroupName : "Versendet"
property defaultSentTagForDocs : "gesendet"
property defaultWriteDebugLog : "false"
property defaultShowReadme : "true"
property defaultDryRunPreviewPath : "/tmp/DT-Belegversand-preview.txt"
(*
runSiblingScript(scriptFileName)
--------------------------------
Lädt und startet ein weiteres Script aus Helpers/scripts/.
Parameter:
- scriptFileName : Name der Scriptdatei, die gestartet werden soll
Verwendung:
- bei vorhandener Konfiguration wird das Hauptscript _belegversand.scpt gestartet
*)
on runSiblingScript(scriptFileName)
set expandedPath to (do shell script "echo ~") & "/" & basePath
set targetPath to expandedPath & "/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.
Versucht zuerst:
- plain text
und fällt bei Bedarf zurück auf:
- content
Dieser Handler wird genutzt, um den Inhalt bestehender Config-Dateien
zu prüfen bzw. zu lesen.
*)
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
(*
isGroupByRecord(theRecord)
--------------------------
Prüft, ob ein DEVONthink-Record eine Gruppe ist.
Wird in der Einrichtung verwendet, um sicherzustellen,
dass die Belege-Hauptgruppe wirklich eine Gruppe/Ordnerstruktur ist
und nicht versehentlich ein Einzelobjekt wie ein Bild oder PDF.
*)
on isGroupByRecord(theRecord)
tell application id "DNtp"
try
return ((type of theRecord) is group)
on error
return false
end try
end tell
end isGroupByRecord
(*
findChildByName(parentRecord, childName)
----------------------------------------
Sucht innerhalb einer DEVONthink-Gruppe ein direktes Kind
mit einem bestimmten Namen.
Rückgabe:
- den gefundenen Record
- oder missing value
Verwendung:
- _config finden
- belegversand.conf finden
- README finden
- mail_vorlagen finden
- Mailbody-Textrecord finden
*)
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
(*
getOrCreateChildGroup(parentRecord, groupName)
----------------------------------------------
Sucht unter parentRecord eine Gruppe groupName.
Falls sie nicht existiert, wird sie angelegt.
Beispiele:
- _config
- mail_vorlagen
- Versendet_Outsider
*)
on getOrCreateChildGroup(parentRecord, groupName)
set existingGroup to my findChildByName(parentRecord, groupName)
if existingGroup is not missing value then return existingGroup
tell application id "DNtp"
return create record with {name:groupName, type:group} in parentRecord
end tell
end getOrCreateChildGroup
(*
getOrCreateTextRecord(parentRecord, recordName, initialText)
------------------------------------------------------------
Sucht unter parentRecord einen Textrecord mit gegebenem Namen.
Falls er nicht existiert, wird er mit initialText angelegt.
Verwendung:
- belegversand.conf
- Mailbody-Datei
*)
on getOrCreateTextRecord(parentRecord, recordName, initialText)
set existingRecord to my findChildByName(parentRecord, recordName)
if existingRecord is not missing value then return existingRecord
tell application id "DNtp"
return create record with {name:recordName, type:txt, content:initialText} in parentRecord
end tell
end getOrCreateTextRecord
(*
getOrCreateMarkdownRecord(parentRecord, recordName, initialText)
----------------------------------------------------------------
Wie getOrCreateTextRecord, aber für Markdown-Records.
Verwendung:
- README-Belegversand
*)
on getOrCreateMarkdownRecord(parentRecord, recordName, initialText)
set existingRecord to my findChildByName(parentRecord, recordName)
if existingRecord is not missing value then return existingRecord
tell application id "DNtp"
return create record with {name:recordName, type:markdown, content:initialText} in parentRecord
end tell
end getOrCreateMarkdownRecord
(*
buildConfigText(...)
--------------------
Baut den vollständigen Inhalt der Konfigurationsdatei belegversand.conf.
Aufbau:
- zuerst die für User relativ ungefährlichen / vorgesehenen Einstellungen
- dann ein erklärender Hinweisblock
- darunter die technischen Werte, die vom Script geschrieben werden
und möglichst nicht manuell verändert werden sollten
Wichtig:
--------
Die Konfigurationsdatei ist bewusst als einfacher key=value-Text aufgebaut.
Die Parser im Hauptscript lesen nur Zeilen, die wirklich mit KEY= beginnen.
Freie Kommentar- oder Trennerzeilen sind daher unproblematisch.
*)
on buildConfigText(mailToDefault, mailCcToDefault, subjectPrefix, zipPasswordDefault, logSentGroupName, belegeRootUUID, writeDebugLogValue, showReadmeValue, readmeUUID, mailBodyFolderName, mailBodyRecordName, sentTagForDocs, dryRunPreviewPath)
return "## Benutzer-Einstellungen - bitte individuell anpassen." & return & ¬
"## Eine Kurz-Doku findest du in der Datei README-Belegversand.md" & return & return & ¬
"APP_VERSION=" & appVersion & return & ¬
"MAIL_TO_DEFAULT=" & mailToDefault & return & ¬
"MAIL_CC_TO_DEFAULT=" & mailCcToDefault & return & ¬
"MAIL_SUBJECT_PREFIX=" & subjectPrefix & return & ¬
"ZIP_PASSWORD_DEFAULT=" & zipPasswordDefault & return & ¬
"LOG_SENT_GROUP_NAME=" & logSentGroupName & return & ¬
"WRITE_DEBUG_LOG=" & writeDebugLogValue & return & ¬
"SHOW_README=" & showReadmeValue & return & ¬
"MAILBODY_FOLDER_NAME=" & mailBodyFolderName & return & ¬
"MAILBODY_RECORD_NAME=" & mailBodyRecordName & return & ¬
"SENT_TAG_FOR_DOCS=" & sentTagForDocs & return & ¬
"PATH_TO_DRYRUN_PREVIEW=" & dryRunPreviewPath & return & return & ¬
"## Die folgenden Werte werden von der Software geschrieben." & return & ¬
"## Bitte nur manuell editieren, wenn du weisst, was du tust!" & return & return & ¬
"BELEGE_ROOT_UUID=" & belegeRootUUID & return & ¬
"README_UUID=" & readmeUUID & return
end buildConfigText
(*
buildReadmeText()
-----------------
Erzeugt den Inhalt der internen README-Datei in DEVONthink.
Bewusst kurz & prägnant — Links verweisen auf ausführliche Doku.
*)
on buildReadmeText()
return "# Belegversand v" & appVersion & return & return & ¬
"Automatisiertes Dokumenten-Versand-System für DEVONthink-Datenbanken." & return & return & ¬
"## Installation" & return & return & ¬
"```bash" & return & ¬
"cd ~/Library/Application\\ Scripts/com.devon-technologies.think/Menu/" & return & ¬
"git clone https://gitea.tuxi.ddnss.de/enchiriadis/DEVONthink-Belegversand-Automatisation.git Belegversand" & return & ¬
"cd Belegversand && ./compile.sh" & return & ¬
"```" & return & return & ¬
"## Dokumentation" & return & return & ¬
"- **Ausführliche Anleitung & Setup-Guide:** " & return & ¬
" https://wiki.tuxi.ddnss.de/wiki/Belegversand" & return & ¬
"- **Konfiguration:** _config/belegversand.conf" & return & ¬
"- **Mail-Templates:** _config/mail_vorlagen/" & return & return & ¬
"## Erste Schritte" & return & return & ¬
"1. Belege in DEVONthink auswählen" & return & ¬
"2. Scripts → START aufrufen" & return & ¬
"3. Dialog folgen → Versand vorbereiten" & return & return & ¬
"Hinweis: Detaillierte Dokumentation immer im Repository (siehe oben)."
end buildReadmeText
(*
buildDefaultMailBodyText()
--------------------------
Erzeugt den Standardtext für die Mailbody-Datei.
*)
on buildDefaultMailBodyText()
return "Sehr geehrte/r Frau/Herr <Name>," & return & return & ¬
"beiliegend erhalten Sie die aktuellen Belege und Quittungen." & return & return & ¬
"Mit freundlichen Grüßen" & return & return & ¬
"Unterschrift" & return
end buildDefaultMailBodyText
(*
getTopParentGroup(theRecord)
----------------------------
Ermittelt die oberste Gruppe der Datenbankstruktur relativ zu einem Record.
*)
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
(*
findConfigRecordInDBRoot(dbRootRecord)
--------------------------------------
Prüft, ob in der gegebenen DB-Wurzel bereits eine gültige Konfiguration existiert.
*)
on findConfigRecordInDBRoot(dbRootRecord)
set configGroupRecord to my findChildByName(dbRootRecord, configGroupName)
if configGroupRecord is missing value then return missing value
set configRecord to my findChildByName(configGroupRecord, configRecordName)
if configRecord is missing value then return missing value
set cfgText to my recordText(configRecord)
if cfgText does not contain requiredConfigKey then return missing value
return configRecord
end findConfigRecordInDBRoot
(*
getCurrentYear()
----------------
Ermittelt das aktuelle Jahr als 4-stelligen String (z.B. "2026").
*)
on getCurrentYear()
return do shell script "date '+%Y'"
end getCurrentYear
(*
getCurrentMonth()
-----------------
Ermittelt den aktuellen Monat als Zahl (1-12).
*)
on getCurrentMonth()
set monthStr to do shell script "date '+%m'"
return (monthStr as integer)
end getCurrentMonth
(*
isNovemberOrDecember()
----------------------
Prüft, ob der aktuelle Monat November (11) oder Dezember (12) ist.
*)
on isNovemberOrDecember()
set currentMonth to my getCurrentMonth()
return (currentMonth = 11 or currentMonth = 12)
end isNovemberOrDecember
(*
createYearCycleInGroup(parentGroup, yearString)
-----------------------------------------------
Erstellt einen Jahresordner (z.B. "2026") unter parentGroup
und erzeugt die Monatsordner (01_Januar bis 12_Dezember) darin.
*)
on createYearCycleInGroup(parentGroup, yearString)
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"}
tell application id "DNtp"
set yearGroupRecord to create record with {name:yearString, type:group} in parentGroup
-- Monatsordner erstellen
repeat with monthName in monthNames
try
create record with {name:monthName, type:group} in yearGroupRecord
on error
-- Falls Ordner bereits existiert, einfach weitermachen
end try
end repeat
-- Versendet-Ordner am Ende hinzufügen
try
create record with {name:"Versendet", type:group} in yearGroupRecord
on error
-- Falls Ordner bereits existiert, einfach weitermachen
end try
-- Provisionsabrechnungen-Ordner hinzufügen
try
create record with {name:"Provisionsabrechnungen", type:group} in yearGroupRecord
on error
-- Falls Ordner bereits existiert, einfach weitermachen
end try
end tell
end createYearCycleInGroup
(*
run
---
Haupteinstiegspunkt des Setup-Scripts.
*)
on run
with timeout of 3600 seconds
tell application id "DNtp"
set sel to selection
if sel is {} then
display dialog "Bitte zuerst ein Objekt in der gewuenschten DEVONthink-Datenbank auswählen." buttons {"OK"} default button "OK"
return
end if
set selectedRecord to item 1 of sel
end tell
set dbRootRecord to my getTopParentGroup(selectedRecord)
set existingConfigRecord to my findConfigRecordInDBRoot(dbRootRecord)
if existingConfigRecord is not missing value then
my runSiblingScript(mainScriptName)
return
end if
tell application id "DNtp"
set sel to selection
if sel is not {} then
set selectedRecord to item 1 of sel
try
set selectedName to name of selectedRecord as rich text
on error
set selectedName to ""
end try
try
set selectedIsGroup to ((type of selectedRecord) is group)
on error
set selectedIsGroup to false
end try
else
set selectedName to ""
set selectedIsGroup to false
end if
end tell
if selectedIsGroup and selectedName is "Belege" then
display dialog "Es wurde noch keine gültige Konfiguration für den Belegversand gefunden." & return & return & "Die Einrichtung wird jetzt mit der bereits ausgewählten Belege-Hauptgruppe gestartet." buttons {"OK"} default button "OK"
else
display dialog "Es wurde noch keine gültige Konfiguration für den Belegversand gefunden." & return & return & "Bitte jetzt die Belege-Hauptgruppe auswählen und anschließend mit OK fortfahren." buttons {"OK"} default button "OK"
end if
tell application id "DNtp"
set sel to selection
if sel is {} then
display dialog "Bitte zuerst die Belege-Hauptgruppe in DEVONthink auswählen." buttons {"OK"} default button "OK"
return
end if
if (count of sel) is not 1 then
display dialog "Bitte genau eine Gruppe auswählen: die Belege-Hauptgruppe." buttons {"OK"} default button "OK"
return
end if
set belegeRootRecord to item 1 of sel
set belegeRootName to name of belegeRootRecord as rich text
set belegeRootUUID to uuid of belegeRootRecord as rich text
try
set belegeRootType to type of belegeRootRecord as rich text
on error
set belegeRootType to "unbekannt"
end try
set isRootGroup to my isGroupByRecord(belegeRootRecord)
end tell
if isRootGroup is false then
display dialog "Das ausgewählte Objekt ist keine Gruppe." & return & return & ¬
"Name: " & belegeRootName & return & ¬
"Typ: " & belegeRootType & return & return & ¬
"Bitte den Ordner Belege auswählen, bzw. den Ordner, in dem alle Belege und Quittungen liegen." buttons {"OK"} default button "OK"
return
end if
if belegeRootName is not "Belege" then
set confirmChoice to button returned of (display dialog "Die ausgewählte Gruppe heißt nicht Belege, sondern " & belegeRootName & "." & return & return & "Soll diese Gruppe wirklich als Belege-Hauptgruppe in die Config-Datei eingetragen werden?" buttons {"Abbrechen", "Trotzdem verwenden"} default button "Abbrechen")
if confirmChoice is "Abbrechen" then return
end if
set yearCycleChoice to button returned of (display dialog "Soll ein Ordner Belege mit Jahresordner und Monatszyklen angelegt werden?" buttons {"Nein", "Ja"} default button "Ja")
set dbRootRecord to my getTopParentGroup(belegeRootRecord)
if yearCycleChoice is "Ja" then
set existingBelegeGroup to my findChildByName(belegeRootRecord, "Belege")
if existingBelegeGroup is missing value then
tell application id "DNtp"
set belageGroup to create record with {name:"Belege", type:group} in belegeRootRecord
set belegeRootUUID to uuid of belageGroup as rich text
end tell
else
tell application id "DNtp"
set belegeRootUUID to uuid of existingBelegeGroup as rich text
end tell
end if
set currentYear to my getCurrentYear()
tell application id "DNtp"
set belageGroup to my findChildByName(belegeRootRecord, "Belege")
end tell
my createYearCycleInGroup(belageGroup, currentYear)
if my isNovemberOrDecember() then
set nextYear to (currentYear as integer) + 1
my createYearCycleInGroup(belageGroup, (nextYear as text))
end if
end if
tell application id "DNtp"
set dbRootName to name of dbRootRecord as rich text
end tell
set mailToDefault to text returned of (display dialog "Standard-Empfängeradresse:" default answer defaultMailTo)
set mailCcToDefault to text returned of (display dialog "Standard-CC-Adresse (optional):" default answer "")
set subjectPrefix to text returned of (display dialog "Standard-Betreff-Präfix:" default answer defaultSubjectPrefix)
set zipPasswordDefault to text returned of (display dialog "Standard-ZIP-Passwort (optional):" default answer defaultZipPassword with hidden answer)
set logSentGroupName to text returned of (display dialog "Name der Versand-Loggruppe:" default answer defaultLogSentGroupName)
set dbgChoice to button returned of (display dialog "Debug-Logging standardmäßig aktivieren?" buttons {"Nein", "Ja"} default button "Nein")
if dbgChoice is "Ja" then
set writeDebugLogValue to "true"
else
set writeDebugLogValue to "false"
end if
set showReadmeValue to defaultShowReadme
set mailBodyFolderName to defaultMailBodyFolderName
set mailBodyRecordName to defaultMailBodyRecordName
set sentTagForDocs to defaultSentTagForDocs
set dryRunPreviewPath to defaultDryRunPreviewPath
set configGroupRecord to my getOrCreateChildGroup(dbRootRecord, configGroupName)
set readmeRecord to my getOrCreateMarkdownRecord(configGroupRecord, readmeRecordName, my buildReadmeText())
set mailBodyFolderRecord to my getOrCreateChildGroup(configGroupRecord, mailBodyFolderName)
set mailBodyRecord to my getOrCreateTextRecord(mailBodyFolderRecord, mailBodyRecordName, my buildDefaultMailBodyText())
set outsiderLogGroupRecord to my getOrCreateChildGroup(dbRootRecord, "Versendet_Outsider")
tell application id "DNtp"
set readmeUUID to uuid of readmeRecord as rich text
set outsiderLogGroupUUID to uuid of outsiderLogGroupRecord as rich text
end tell
set configRecord to my getOrCreateTextRecord(configGroupRecord, configRecordName, "")
set configText to my buildConfigText(mailToDefault, mailCcToDefault, subjectPrefix, zipPasswordDefault, logSentGroupName, belegeRootUUID, writeDebugLogValue, showReadmeValue, readmeUUID, mailBodyFolderName, mailBodyRecordName, sentTagForDocs, dryRunPreviewPath)
tell application id "DNtp"
try
set content of configRecord to configText
on error
set plain text of configRecord to configText
end try
try
set content of readmeRecord to my buildReadmeText()
on error
set plain text of readmeRecord to my buildReadmeText()
end try
try
set content of mailBodyRecord to my buildDefaultMailBodyText()
on error
set plain text of mailBodyRecord to my buildDefaultMailBodyText()
end try
set configUUID to uuid of configRecord as rich text
set configGroupUUID to uuid of configGroupRecord as rich text
set mailBodyFolderUUID to uuid of mailBodyFolderRecord as rich text
set mailBodyUUID to uuid of mailBodyRecord as rich text
end tell
if writeDebugLogValue is "true" then
set debugInfoText to return & return & "Debug-Log: /tmp/DT-Belegversand-debug.log"
else
set debugInfoText to ""
end if
if writeDebugLogValue is "true" then
set debugInfoText to return & return & "Debug-Log: /tmp/DT-Belegversand-debug.log"
else
set debugInfoText to ""
end if
-- Absolute Basis-Path für Script-Kompilierung
set basePath to (do shell script "echo ~") & basePath
display dialog "Belegversand wurde eingerichtet." & return & return & ¬
"Datenbank-Wurzel: " & dbRootName & return & ¬
"Belege-Gruppe: " & belegeRootName & return & ¬
"BELEGE_ROOT_UUID: " & belegeRootUUID & return & return & ¬
"Config-Gruppe: " & configGroupName & return & ¬
"CONFIG_GROUP_UUID: " & configGroupUUID & return & ¬
"Config-Datei: " & configRecordName & return & ¬
"CONFIG_RECORD_UUID: " & configUUID & return & return & ¬
"README-Datei: " & readmeRecordName & return & ¬
"README_UUID: " & readmeUUID & return & return & ¬
"Mailbody-Ordner: " & mailBodyFolderName & return & ¬
"MAILBODY_FOLDER_UUID: " & mailBodyFolderUUID & return & return & ¬
"Mailbody-Datei: " & mailBodyRecordName & return & ¬
"MAILBODY_UUID: " & mailBodyUUID & return & return & ¬
"Globale Log-Gruppe (Outsider): Versendet_Outsider" & return & ¬
"OUTSIDER_LOG_UUID: " & outsiderLogGroupUUID & debugInfoText buttons {"OK"} default button "OK"
end timeout
end run