Public Access
Initial commit: v1.4 with documentation
This commit is contained in:
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
+68
@@ -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
|
||||
@@ -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.
@@ -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"
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user