(* _belegversand.scpt – Version 1.4 Rolle dieses Scripts -------------------- Dieses Script ist die eigentliche Versandlogik des Systems. Es uebernimmt: - Auslesen der Konfiguration aus DEVONthink - Pruefung der aktuellen Auswahl - Herausfiltern bereits markierter Dokumente - Export in einen temporaeren Arbeitsordner - Erzeugen eines passwortgeschuetzten ZIP-Archivs - Erzeugen eines Mail-Entwurfs in Apple Mail - optionales Tagging der verarbeiteten Dokumente - Protokollierung des Versands in NDJSON und Markdown *) property sentTag : "gesendet" property defaultMailTo : "belegonline@irgendwer.de" property defaultMailSubjectPrefix : "Belege" property defaultLogSentGroupName : "Versendet" property defaultWriteDebugLog : false property defaultMailBodyFolderName : "mail_vorlagen" property defaultMailBodyRecordName : "belegversand" property defaultDryRunPreviewPath : "/tmp/DT-Belegversand-preview.txt" property configGroupName : "_config" property configRecordName : "belegversand.conf" property debugLogPath : "/tmp/DT-Belegversand-debug.log" property cfgConfigRecordUUID : missing value property cfgMailToDefault : missing value property cfgMailCcToDefault : missing value property cfgMailSubjectPrefix : missing value property cfgZipPasswordDefault : missing value property cfgLogSentGroupName : missing value property cfgBelegeRootUUID : missing value property cfgWriteDebugLog : missing value property cfgMailBodyFolderName : missing value property cfgMailBodyRecordName : missing value property cfgSentTagForDocs : missing value property cfgDryRunPreviewPath : missing value on writeDebug(msg) if my writeDebugLogEnabled() is false then return set ts to do shell script "date '+%Y-%m-%d %H:%M:%S'" do shell script "printf %s\\\\n " & quoted form of ("[" & ts & "] " & msg) & " >> " & quoted form of my debugLogPath end writeDebug on resetDebugLog() if my writeDebugLogEnabled() is false then return do shell script "rm -f " & quoted form of my debugLogPath my writeDebug("=== Debug-Session gestartet ===") end resetDebugLog on writeDebugLogEnabled() try if my cfgWriteDebugLog is missing value then return defaultWriteDebugLog return my cfgWriteDebugLog on error return defaultWriteDebugLog end try end writeDebugLogEnabled on isoTimestamp() return (do shell script "date '+%Y-%m-%dT%H:%M:%S%z' | sed -E 's/([+-][0-9]{2})([0-9]{2})/\\1:\\2/'") end isoTimestamp on batchId() return (do shell script "date '+%Y%m%d-%H%M%S'") end batchId on ensureFolder(posixPath) do shell script "mkdir -p " & quoted form of posixPath end ensureFolder on jsonEscape(s) if s is missing value then set s to "" set s to s as text set s to my replaceText("\\", "\\\\", s) set s to my replaceText("\"", "\\\"", s) set s to my replaceText(return, "\\n", s) return s end jsonEscape on mdEscape(s) if s is missing value then set s to "" set s to s as text set s to my replaceText("|", "\\|", s) set s to my replaceText(return, " ", s) return s end mdEscape on replaceText(find, repl, txt) set oldTIDs to AppleScript's text item delimiters try if txt is missing value then set txt to "" set txt to txt as text set AppleScript's text item delimiters to find set parts to every text item of txt set AppleScript's text item delimiters to repl set txt2 to parts as text set AppleScript's text item delimiters to oldTIDs return txt2 on error errMsg number errNum set AppleScript's text item delimiters to oldTIDs error errMsg number errNum end try end replaceText on isGroupByUUID(recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID try return ((type of r) is group) on error errMsg my writeDebug("isGroupByUUID error uuid=" & recordUUID & " msg=" & errMsg) return false end try end tell end isGroupByUUID on getDTRecordTextByUUID(recordUUID) tell application id "DNtp" set theRecord to get record with uuid recordUUID try set oldText to plain text of theRecord if oldText is missing value then set oldText to "" return oldText as rich text on error try set oldText to content of theRecord if oldText is missing value then set oldText to "" return oldText as rich text on error return "" end try end try end tell end getDTRecordTextByUUID on setDTRecordTextByUUID(recordUUID, newText) tell application id "DNtp" set theRecord to get record with uuid recordUUID try set content of theRecord to newText on error try set plain text of theRecord to newText on error errMsg error "setDTRecordTextByUUID failed: " & errMsg end try end try end tell end setDTRecordTextByUUID on appendLineToDTRecordByUUID(recordUUID, lineText) my writeDebug("appendLineToDTRecordByUUID START recordUUID=" & recordUUID) set oldText to my getDTRecordTextByUUID(recordUUID) if oldText is "" then set newText to lineText & return else if oldText ends with return then set newText to oldText & lineText & return else set newText to oldText & return & lineText & return end if my setDTRecordTextByUUID(recordUUID, newText) my writeDebug("appendLineToDTRecordByUUID DONE recordUUID=" & recordUUID) end appendLineToDTRecordByUUID on getSelectedUUIDs() tell application id "DNtp" set sel to selection if sel is {} then error "Keine Objekte ausgewählt." set selCount to count of sel set rawList to {} repeat with i from 1 to selCount set anItem to item i of sel set end of rawList to (uuid of anItem as rich text) end repeat end tell set outList to {} repeat with itemUUID in rawList set uuidText to itemUUID as text if my isGroupByUUID(uuidText) then my writeDebug("Selection ignored group uuid=" & uuidText) else set end of outList to uuidText end if end repeat return {selCount, outList} end getSelectedUUIDs on recordHasTagByUUID(recordUUID, tagName) tell application id "DNtp" set r to get record with uuid recordUUID try set t to tags of r if t is missing value then return false return (t contains tagName) on error return false end try end tell end recordHasTagByUUID on filterUUIDsWithoutTag(uuidList, tagName) set outList to {} repeat with thisUUID in uuidList set uuidText to thisUUID as text set hasTag to my recordHasTagByUUID(uuidText, tagName) my writeDebug("Tag-Check uuid=" & uuidText & " hasTag=" & (hasTag as text)) if hasTag is false then set end of outList to uuidText end if end repeat return outList end filterUUIDsWithoutTag on findChildRecordUUIDByName(parentUUID, childName) tell application id "DNtp" set parentRecord to get record with uuid parentUUID 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 (uuid of c as rich text) end repeat end try end tell return missing value end findChildRecordUUIDByName on getOrCreateChildGroupUUID(parentUUID, groupName) set existingUUID to my findChildRecordUUIDByName(parentUUID, groupName) if existingUUID is not missing value then my writeDebug("Group exists: " & groupName & " uuid=" & existingUUID) return existingUUID end if tell application id "DNtp" set parentRecord to get record with uuid parentUUID set newGroup to create record with {name:groupName, type:group} in parentRecord set newUUID to uuid of newGroup as rich text my writeDebug("Group created: " & groupName & " uuid=" & newUUID) return newUUID end tell end getOrCreateChildGroupUUID on getOrCreateTextLogUUID(parentUUID, childName, initialText) set existingUUID to my findChildRecordUUIDByName(parentUUID, childName) if existingUUID is not missing value then my writeDebug("Text log exists: " & childName & " uuid=" & existingUUID) return existingUUID end if tell application id "DNtp" set parentRecord to get record with uuid parentUUID set newRecord to create record with {name:childName, type:txt, content:initialText} in parentRecord set newUUID to uuid of newRecord as rich text my writeDebug("Text log created: " & childName & " uuid=" & newUUID) return newUUID end tell end getOrCreateTextLogUUID on getOrCreateMarkdownLogUUID(parentUUID, childName, initialText) set existingUUID to my findChildRecordUUIDByName(parentUUID, childName) if existingUUID is not missing value then my writeDebug("Markdown log exists: " & childName & " uuid=" & existingUUID) return existingUUID end if tell application id "DNtp" set parentRecord to get record with uuid parentUUID set newRecord to create record with {name:childName, type:markdown, content:initialText} in parentRecord set newUUID to uuid of newRecord as rich text my writeDebug("Markdown log created: " & childName & " uuid=" & newUUID) return newUUID end tell end getOrCreateMarkdownLogUUID on getRecordNameByUUID(recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID try return name of r as rich text on error return "" end try end tell end getRecordNameByUUID on getRecordFileNameByUUID(recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID try set fn to filename of r if fn is not missing value then set fnText to fn as rich text if fnText is not "" then return fnText end if end try try return name of r as rich text on error return "" end try end tell end getRecordFileNameByUUID on getReferenceURLByUUID(recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID try return reference URL of r as rich text on error return "" end try end tell end getReferenceURLByUUID on addTagByUUID(recordUUID, tagName) my writeDebug("addTag START uuid=" & recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID set t to {} try set t to tags of r if t is missing value then set t to {} on error errMsg my writeDebug("addTag tags-read error uuid=" & recordUUID & " msg=" & errMsg) set t to {} end try if t does not contain tagName then set end of t to tagName set tags of r to t my writeDebug("addTag wrote tag uuid=" & recordUUID) else my writeDebug("addTag tag already present uuid=" & recordUUID) end if end tell set verifyTag to my recordHasTagByUUID(recordUUID, tagName) my writeDebug("addTag DONE uuid=" & recordUUID & " verify=" & (verifyTag as text)) end addTagByUUID on exportRecordByUUID(recordUUID, targetFolderAlias) my writeDebug("export START uuid=" & recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID if r is missing value then error "Record nicht gefunden für UUID: " & recordUUID export record r to targetFolderAlias end tell my writeDebug("export DONE uuid=" & recordUUID) end exportRecordByUUID on findChildRecordByName(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 findChildRecordByName on getTopParentGroupFromUUID(recordUUID) tell application id "DNtp" set currentRecord to get record with uuid recordUUID 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 getTopParentGroupFromUUID on findConfigRecordUUIDFromCurrentSelection() tell application id "DNtp" set sel to selection if sel is {} then error "Keine Objekte ausgewählt." set selectedRecord to item 1 of sel set selectedUUID to uuid of selectedRecord as rich text end tell set dbRootRecord to my getTopParentGroupFromUUID(selectedUUID) set configGroupRecord to my findChildRecordByName(dbRootRecord, my configGroupName) if configGroupRecord is missing value then error "Keine Konfiguration gefunden (_config fehlt). Bitte zuerst das Setup-Script ausführen." set configRecord to my findChildRecordByName(configGroupRecord, my configRecordName) if configRecord is missing value then error "Keine Konfiguration gefunden (" & my configRecordName & " fehlt). Bitte zuerst das Setup-Script ausführen." tell application id "DNtp" return uuid of configRecord as rich text end tell end findConfigRecordUUIDFromCurrentSelection on trimText(s) set s to s as text set s to do shell script "printf %s " & quoted form of s & " | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'" return s end trimText on parseCSVEmails(csvText) if csvText is "" or csvText is missing value then return {} set csvText to csvText as text set emails to {} set saveDelimiters to AppleScript's text item delimiters try set AppleScript's text item delimiters to "," set parts to every text item of csvText set AppleScript's text item delimiters to saveDelimiters repeat with part in parts set trimmedPart to my trimText(part as text) if trimmedPart is not "" then set end of emails to trimmedPart end if end repeat return emails on error errMsg set AppleScript's text item delimiters to saveDelimiters my writeDebug("parseCSVEmails error: " & errMsg) return {} end try end parseCSVEmails 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 my trimText(val) end if end if end repeat return missing value end configValue on loadConfig() set configUUID to my findConfigRecordUUIDFromCurrentSelection() set cfgText to my getDTRecordTextByUUID(configUUID) if cfgText is "" then error "Konfigurationsdatei ist leer: " & my configRecordName set vMailTo to my configValue(cfgText, "MAIL_TO_DEFAULT") if vMailTo is missing value then set vMailTo to defaultMailTo set vMailCcTo to my configValue(cfgText, "MAIL_CC_TO_DEFAULT") if vMailCcTo is missing value or vMailCcTo is "" then set vMailCcTo to "" set vSubjectPrefix to my configValue(cfgText, "MAIL_SUBJECT_PREFIX") if vSubjectPrefix is missing value then set vSubjectPrefix to defaultMailSubjectPrefix set vZipPasswordDefault to my configValue(cfgText, "ZIP_PASSWORD_DEFAULT") if vZipPasswordDefault is missing value then set vZipPasswordDefault to "" set vLogSentGroupName to my configValue(cfgText, "LOG_SENT_GROUP_NAME") if vLogSentGroupName is missing value then set vLogSentGroupName to defaultLogSentGroupName set vBelegeRootUUID to my configValue(cfgText, "BELEGE_ROOT_UUID") if vBelegeRootUUID is missing value or vBelegeRootUUID is "" then error "BELEGE_ROOT_UUID fehlt in " & my configRecordName set vWriteDebugLog to my configValue(cfgText, "WRITE_DEBUG_LOG") if vWriteDebugLog is missing value then set vDebugBool to defaultWriteDebugLog else set vLower to do shell script "printf %s " & quoted form of vWriteDebugLog & " | tr '[:upper:]' '[:lower:]'" set vDebugBool to (vLower is "true") end if set vMailBodyFolderName to my configValue(cfgText, "MAILBODY_FOLDER_NAME") if vMailBodyFolderName is missing value or vMailBodyFolderName is "" then set vMailBodyFolderName to defaultMailBodyFolderName set vMailBodyRecordName to my configValue(cfgText, "MAILBODY_RECORD_NAME") if vMailBodyRecordName is missing value or vMailBodyRecordName is "" then set vMailBodyRecordName to defaultMailBodyRecordName set vSentTagForDocs to my configValue(cfgText, "SENT_TAG_FOR_DOCS") if vSentTagForDocs is missing value or vSentTagForDocs is "" then set vSentTagForDocs to sentTag set vDryRunPreviewPath to my configValue(cfgText, "PATH_TO_DRYRUN_PREVIEW") if vDryRunPreviewPath is missing value or vDryRunPreviewPath is "" then set vDryRunPreviewPath to defaultDryRunPreviewPath return {configUUID:configUUID, mailToDefault:vMailTo, mailCcToDefault:vMailCcTo, subjectPrefix:vSubjectPrefix, zipPasswordDefault:vZipPasswordDefault, logSentGroupName:vLogSentGroupName, belegeRootUUID:vBelegeRootUUID, writeDebugLog:vDebugBool, mailBodyFolderName:vMailBodyFolderName, mailBodyRecordName:vMailBodyRecordName, sentTagForDocs:vSentTagForDocs, dryRunPreviewPath:vDryRunPreviewPath} end loadConfig on runTemplateSelector() set basePath to (do shell script "echo ~") & "/Library/Application Scripts/com.devon-technologies.think/Menu/Belegversand_1-4" set scriptPath to basePath & "/Helpers/show_mail_templates.scpt" my writeDebug("Versuche Template-Script zu laden: " & scriptPath) try set loadedScript to load script (POSIX file scriptPath as alias) my writeDebug("Template-Script geladen, starte jetzt...") set templateResult to run loadedScript my writeDebug("Template-Script fertig. Rueckgabe: '" & templateResult & "'") return templateResult on error errMsg my writeDebug("FEHLER in runTemplateSelector: " & errMsg) return "" end try end runTemplateSelector on getMailBodyTextFromCurrentDB() tell application id "DNtp" set sel to selection if sel is {} then return "" set selectedRecord to item 1 of sel set selectedUUID to uuid of selectedRecord as rich text end tell set dbRootRecord to my getTopParentGroupFromUUID(selectedUUID) set configGroupRecord to my findChildRecordByName(dbRootRecord, my configGroupName) if configGroupRecord is missing value then return "" set mailBodyFolderRecord to my findChildRecordByName(configGroupRecord, my cfgMailBodyFolderName) if mailBodyFolderRecord is missing value then return "" set templateName to my runTemplateSelector() my writeDebug("runTemplateSelector returned: '" & templateName & "'") if templateName is "" then my writeDebug("templateName ist leer, fallback zu default: " & my cfgMailBodyRecordName) set templateName to my cfgMailBodyRecordName end if my writeDebug("Suche nach Mailbody-Record: '" & templateName & "'") set mailBodyRecord to my findChildRecordByName(mailBodyFolderRecord, templateName) my writeDebug("mailBodyRecord: " & (mailBodyRecord is missing value)) if mailBodyRecord is missing value then my writeDebug("ERROR: mailBodyRecord nicht gefunden!") return "" end if tell application id "DNtp" try set t to plain text of mailBodyRecord if t is missing value then set t to "" return t as rich text on error try set t to content of mailBodyRecord if t is missing value then set t to "" return t as rich text on error return "" end try end try end tell end getMailBodyTextFromCurrentDB on safeDryRunPreviewPath() set p to my cfgDryRunPreviewPath if p is missing value or p is "" then set p to defaultDryRunPreviewPath return p as text end safeDryRunPreviewPath on writeDryRunPreviewFile(toSendUUIDs, theTo) set previewPath to my safeDryRunPreviewPath() set previewDir to do shell script "dirname " & quoted form of previewPath do shell script "mkdir -p " & quoted form of previewDir set previewText to "Belegversand – Vorschau (Dry Run)" & return & return & ¬ "Empfänger: " & theTo & return & ¬ "Anzahl Belege: " & (count of toSendUUIDs) & return & return set i to 1 repeat with thisUUID in toSendUUIDs set uuidText to thisUUID as text set recFileName to my getRecordFileNameByUUID(uuidText) set previewText to previewText & i & ". " & recFileName & return set i to i + 1 end repeat do shell script "cat > " & quoted form of previewPath & " <<'EOF'" & linefeed & previewText & "EOF" return previewPath end writeDryRunPreviewFile on dryRunSummaryText(toSendUUIDs, previewPath) set nItems to count of toSendUUIDs set maxPreviewItems to 4 set outText to "Insgesamt würden " & nItems & " Belege verarbeitet:" & return & return set shownCount to 0 repeat with thisUUID in toSendUUIDs if shownCount is greater than or equal to maxPreviewItems then exit repeat set uuidText to thisUUID as text set recFileName to my getRecordFileNameByUUID(uuidText) set outText to outText & "- " & recFileName & return set shownCount to shownCount + 1 end repeat if nItems is greater than maxPreviewItems then set outText to outText & "[...]" & return end if set outText to outText & return & "Die gesamte Liste findest du hier:" & return & previewPath return outText end dryRunSummaryText on showDryRunPreviewInFinder(previewPath) do shell script "open -R " & quoted form of previewPath end showDryRunPreviewInFinder on isYearGroupName(groupName) try if (length of groupName) is not 4 then return false do shell script "printf %s " & quoted form of groupName & " | /usr/bin/grep -Eq '^[0-9]{4}$'" return true on error return false end try end isYearGroupName on getBookingYearInfoFromUUID(recordUUID) tell application id "DNtp" set currentRecord to get record with uuid recordUUID repeat try set p to parents of currentRecord if p is {} then return missing value set parentRecord to item 1 of p set parentName to name of parentRecord as rich text if my isYearGroupName(parentName) then return {yearUUID:(uuid of parentRecord as rich text), yearName:parentName} end if set currentRecord to parentRecord on error errMsg my writeDebug("getBookingYearInfoFromUUID error: " & errMsg) return missing value end try end repeat end tell end getBookingYearInfoFromUUID on getSelectedBookingYearInfo(uuidList) set firstYearUUID to missing value set firstYearName to missing value set hasOutsiders to false repeat with thisUUID in uuidList set uuidText to thisUUID as text set yearInfo to my getBookingYearInfoFromUUID(uuidText) if yearInfo is missing value then set hasOutsiders to true my writeDebug("BookingYearCheck uuid=" & uuidText & " -> Außenseiter (kein Jahr gefunden)") else set thisYearUUID to (yearUUID of yearInfo) as text set thisYearName to (yearName of yearInfo) as text my writeDebug("BookingYearCheck uuid=" & uuidText & " yearName=" & thisYearName & " yearUUID=" & thisYearUUID) if firstYearUUID is missing value then set firstYearUUID to thisYearUUID set firstYearName to thisYearName my writeDebug("BookingYearReference yearName=" & firstYearName & " yearUUID=" & firstYearUUID) else if thisYearUUID is not equal to firstYearUUID then error "Es wurden Belege aus mehreren Buchungsjahren ausgewählt." & return & return & "Bitte pro Sendelauf nur Belege aus einem Jahr auswählen." end if my writeDebug("BookingYearCompare first=" & firstYearName & "/" & firstYearUUID & " current=" & thisYearName & "/" & thisYearUUID) end if end if end repeat if hasOutsiders and firstYearUUID is not missing value then error "Es wurden Belege aus Buchungsjahren UND von außerhalb ausgewählt." & return & return & "Bitte nur Belege aus einem Bereich wählen." end if return {yearUUID:firstYearUUID, yearName:firstYearName, hasOutsiders:hasOutsiders} end getSelectedBookingYearInfo on getOrCreateGlobalSentGroupUUID(referenceUUID) set dbRootRecord to my getTopParentGroupFromUUID(referenceUUID) tell application id "DNtp" set dbRootUUID to uuid of dbRootRecord as rich text end tell my writeDebug("getOrCreateGlobalSentGroupUUID dbRootUUID=" & dbRootUUID) set globalSentGroupUUID to my getOrCreateChildGroupUUID(dbRootUUID, "Versendet_Outsider") my writeDebug("globalSentGroupUUID=" & globalSentGroupUUID) return globalSentGroupUUID end getOrCreateGlobalSentGroupUUID on writeResultRecordByUUID(recordUUID, ts, bid, toAddrs, ccAddrs, nItems, isOutsider) my writeDebug("writeResultRecordByUUID START recordUUID=" & recordUUID) set toDisplay to "" repeat with addr in toAddrs if toDisplay is "" then set toDisplay to (addr as text) else set toDisplay to toDisplay & ", " & (addr as text) end if end repeat set ccDisplay to "" repeat with addr in ccAddrs if ccDisplay is "" then set ccDisplay to (addr as text) else set ccDisplay to ccDisplay & ", " & (addr as text) end if end repeat if ccDisplay is "" then set ccDisplay to "(keine)" my writeDebug("toDisplay=" & toDisplay & " ccDisplay=" & ccDisplay) set resultText to "# Letzter Sendelauf – Ergebnis" & return & return & ¬ "**Zeitstempel:** " & ts & return & ¬ "**Batch-ID:** " & bid & return & ¬ "**Empfänger (TO):** " & toDisplay & return & ¬ "**Empfänger (CC):** " & ccDisplay & return & ¬ "**Anzahl Belege:** " & nItems & return & ¬ "**Status:** ok Erfolgreich" & return & return & ¬ "Der Mail-Entwurf wurde in Apple Mail erstellt." if isOutsider then set resultText to resultText & return & return & "*Hinweis: Dies waren Außenseiter-Belege (von außerhalb eines Buchungsjahres).*" end if my writeDebug("resultText length=" & (length of resultText)) try my setDTRecordTextByUUID(recordUUID, resultText) my writeDebug("writeResultRecordByUUID text set OK") on error errMsg my writeDebug("writeResultRecordByUUID ERROR: " & errMsg) end try my writeDebug("writeResultRecordByUUID DONE") end writeResultRecordByUUID on showCompletionNotification(nItems) my writeDebug("showCompletionNotification START nItems=" & nItems) try set msgText to (nItems as text) & " Beleg(e) – Mail-Entwurf erstellt." my writeDebug("notification message=" & msgText) tell application id "DNtp" display notification msgText with title "Belegversand" subtitle "Mail-Entwurf erstellt." end tell my writeDebug("notification displayed OK") on error errMsg my writeDebug("showCompletionNotification ERROR: " & errMsg) end try my writeDebug("showCompletionNotification DONE") end showCompletionNotification on friendlyRunHeader(ts) try return do shell script "LANG=de_DE.UTF-8 date -j -f '%Y-%m-%dT%H:%M:%S%z' " & quoted form of ((do shell script "printf %s " & quoted form of ts & " | sed 's/://3'")) & " '+Sendelauf am %-d. %B %Y, Uhrzeit %H:%M:%S'" on error return "Sendelauf am " & ts end try end friendlyRunHeader on run with timeout of 3600 seconds try set selInfo to my getSelectedUUIDs() set selCount to item 1 of selInfo set selectedUUIDs to item 2 of selInfo if (count of selectedUUIDs) is 0 then error "Keine versendbaren Belege ausgewählt." set bookingYearInfo to my getSelectedBookingYearInfo(selectedUUIDs) set bookingYearUUID to yearUUID of bookingYearInfo set bookingYearName to yearName of bookingYearInfo set isOutsider to hasOutsiders of bookingYearInfo set cfg to my loadConfig() set my cfgConfigRecordUUID to configUUID of cfg set my cfgMailToDefault to mailToDefault of cfg set my cfgMailCcToDefault to mailCcToDefault of cfg set my cfgMailSubjectPrefix to subjectPrefix of cfg set my cfgZipPasswordDefault to zipPasswordDefault of cfg set my cfgLogSentGroupName to logSentGroupName of cfg set my cfgBelegeRootUUID to belegeRootUUID of cfg set my cfgWriteDebugLog to writeDebugLog of cfg set my cfgMailBodyFolderName to mailBodyFolderName of cfg set my cfgMailBodyRecordName to mailBodyRecordName of cfg set my cfgSentTagForDocs to sentTagForDocs of cfg set my cfgDryRunPreviewPath to dryRunPreviewPath of cfg my resetDebugLog() my writeDebug("Script start") my writeDebug("Config record UUID=" & my cfgConfigRecordUUID) my writeDebug("Config BELEGE_ROOT_UUID=" & my cfgBelegeRootUUID) my writeDebug("Resolved booking year early: yearUUID=" & bookingYearUUID & " yearName=" & bookingYearName & " isOutsider=" & isOutsider) my writeDebug("Selected raw count=" & selCount) my writeDebug("Selected usable count=" & (count of selectedUUIDs)) repeat with u in selectedUUIDs my writeDebug("Selected uuid=" & (u as text)) end repeat set toSend to my filterUUIDsWithoutTag(selectedUUIDs, my cfgSentTagForDocs) my writeDebug("ToSend count=" & (count of toSend)) repeat with u in toSend my writeDebug("ToSend uuid=" & (u as text) & " name=" & my getRecordNameByUUID(u as text)) end repeat if (count of toSend) is 0 then error "Alle ausgewählten Belege sind bereits als versendet markiert und werden übersprungen." set drChoice to button returned of (display dialog "Modus waehlen:" buttons {"Dry Run", "Senden (Entwurf erstellen)", "Abbrechen"} default button "Dry Run") if drChoice is "Abbrechen" then my writeDebug("User hat Abbrechen geklickt") return end if set isDryRun to (drChoice is "Dry Run") my writeDebug("Mode isDryRun=" & (isDryRun as text)) set theTo to text returned of (display dialog "Empfaenger E-Mail:" default answer my cfgMailToDefault) set theCcTo to text returned of (display dialog "CC-Empfaenger (optional):" default answer my cfgMailCcToDefault) set zipPass to text returned of (display dialog "ZIP-Passwort:" default answer my cfgZipPasswordDefault with hidden answer) my writeDebug("Recipient=" & theTo) set nItems to count of toSend set previewText to "Ausgewählt: " & selCount & return & "Zu senden (ungesendet): " & nItems & return & return & "Empfänger: " & theTo & return if isOutsider then set previewText to previewText & "(Außenseiter-Belege → globales Versendet-Log)" & return else set previewText to previewText & "Buchungsjahr: " & bookingYearName & return end if if isDryRun then set previewPath to my writeDryRunPreviewFile(toSend, theTo) set dryRunText to my dryRunSummaryText(toSend, previewPath) set dryRunChoice to button returned of (display dialog dryRunText buttons {"OK", "Liste im Finder zeigen"} default button "OK") if dryRunChoice is "Liste im Finder zeigen" then my showDryRunPreviewInFinder(previewPath) end if my writeDebug("Dry run end") return end if set proceed to button returned of (display dialog previewText & return & "Entwurf erstellen & ZIP bauen?" buttons {"Abbrechen", "Weiter"} default button "Weiter") if proceed is "Abbrechen" then my writeDebug("User aborted before export") return end if set bid to my batchId() set ts to my isoTimestamp() my writeDebug("Batch=" & bid & " ts=" & ts & " isOutsider=" & isOutsider) set tmpAlias to (path to temporary items) as alias set stageRootPOSIX to (POSIX path of tmpAlias) & "DT-Belegversand/" & bid & "/" set stageFilesPOSIX to stageRootPOSIX & "files/" my ensureFolder(stageFilesPOSIX) set zipName to "Belege_" & bid & ".zip" set zipPath to stageRootPOSIX & zipName set stageFolderAlias to (POSIX file stageFilesPOSIX) as alias my writeDebug("Stage root=" & stageRootPOSIX) my writeDebug("Stage files=" & stageFilesPOSIX) my writeDebug("ZIP path=" & zipPath) repeat with thisUUID in toSend set uuidText to thisUUID as text my exportRecordByUUID(uuidText, stageFolderAlias) end repeat my writeDebug("Stage content before cleanup:") try set stageListBefore to do shell script "find " & quoted form of stageRootPOSIX & " -print" repeat with oneLine in paragraphs of stageListBefore if oneLine is not "" then my writeDebug(" " & oneLine) end repeat on error errMsg my writeDebug("Stage listing before cleanup failed: " & errMsg) end try do shell script "find " & quoted form of stageRootPOSIX & " -name '.DS_Store' -type f -delete" do shell script "find " & quoted form of stageRootPOSIX & " -name '__MACOSX' -exec rm -rf {} + 2>/dev/null || true" do shell script "find " & quoted form of stageRootPOSIX & " -name 'DEVONtech_storage' -exec rm -rf {} + 2>/dev/null || true" my writeDebug("Stage content after cleanup:") try set stageListAfter to do shell script "find " & quoted form of stageRootPOSIX & " -print" repeat with oneLine in paragraphs of stageListAfter if oneLine is not "" then my writeDebug(" " & oneLine) end repeat on error errMsg my writeDebug("Stage listing after cleanup failed: " & errMsg) end try if zipPass is "" then do shell script "cd " & quoted form of stageFilesPOSIX & " ; " & ¬ "/usr/bin/find . -type f -print -quit | /usr/bin/grep -q . || { echo 'Keine exportierten Dateien gefunden.' >&2; exit 1; } ; " & ¬ "/usr/bin/zip -r -j " & quoted form of zipPath & " . " else do shell script "cd " & quoted form of stageFilesPOSIX & " ; " & ¬ "/usr/bin/find . -type f -print -quit | /usr/bin/grep -q . || { echo 'Keine exportierten Dateien gefunden.' >&2; exit 1; } ; " & ¬ "/usr/bin/zip -P " & quoted form of zipPass & " -r -j " & quoted form of zipPath & " . " end if my writeDebug("ZIP created") set mailBodyText to my getMailBodyTextFromCurrentDB() if mailBodyText is "" then set mailBodyText to "Im Anhang die aktuellen Belege." end if set toAddresses to my parseCSVEmails(theTo) set ccAddresses to my parseCSVEmails(theCcTo) my writeDebug("toAddresses count=" & (count of toAddresses)) repeat with toAddr in toAddresses my writeDebug(" toAddr=" & (toAddr as text)) end repeat my writeDebug("ccAddresses count=" & (count of ccAddresses)) repeat with ccAddr in ccAddresses my writeDebug(" ccAddr=" & (ccAddr as text)) end repeat tell application "Mail" set newMessage to make new outgoing message with properties {visible:true, subject:(my cfgMailSubjectPrefix), content:(mailBodyText & return & return)} tell newMessage repeat with toAddr in toAddresses make new to recipient at end of to recipients with properties {address:(toAddr as rich text)} end repeat repeat with ccAddr in ccAddresses make new cc recipient at end of cc recipients with properties {address:(ccAddr as rich text)} end repeat make new attachment with properties {file name:(POSIX file zipPath as alias)} at after the last paragraph end tell activate end tell my writeDebug("Mail draft created") if isOutsider then set refUUID to item 1 of toSend as text set logGroupUUID to my getOrCreateGlobalSentGroupUUID(refUUID) set ndjsonName to "sentlog_outsiders.ndjson" set mdName to "sentlog_outsiders" set mdHeader to "# Belegversand-Log (außerhalb von Buchungsjahren)" & return my writeDebug("Using global outsider log group") else set logGroupUUID to my getOrCreateChildGroupUUID(bookingYearUUID, my cfgLogSentGroupName) set ndjsonName to "sentlog_" & bookingYearName & ".ndjson" set mdName to "sentlog_" & bookingYearName set mdHeader to "# Belegversand-Log " & bookingYearName & return my writeDebug("Using year-specific log group for year=" & bookingYearName) end if set ndjsonLogUUID to my getOrCreateTextLogUUID(logGroupUUID, ndjsonName, "") set mdLogUUID to my getOrCreateMarkdownLogUUID(logGroupUUID, mdName, mdHeader) set resultUUID to my getOrCreateMarkdownLogUUID(logGroupUUID, "!Ergebnis", "# Letzter Sendelauf" & return) set runHeader to my friendlyRunHeader(ts) set currentMdText to my getDTRecordTextByUUID(mdLogUUID) if currentMdText is "" or currentMdText is mdHeader or currentMdText is (mdHeader & return) then set mdRunIntro to return & "## " & runHeader & return & return & "| Zeit | Batch | Empfänger | ZIP | DEVONthink-Name | Dateiname | DEVONthink |" & return & "|---|---|---|---|---|---|---|" else set mdRunIntro to return & return & "## " & runHeader & return & return & "| Zeit | Batch | Empfänger | ZIP | DEVONthink-Name | Dateiname | DEVONthink |" & return & "|---|---|---|---|---|---|---|" end if my appendLineToDTRecordByUUID(mdLogUUID, mdRunIntro) my writeDebug("ndjsonLogUUID=" & ndjsonLogUUID) my writeDebug("mdLogUUID=" & mdLogUUID) repeat with thisUUID in toSend set uuidText to thisUUID as text my writeDebug("PROCESS START uuid=" & uuidText) my addTagByUUID(uuidText, my cfgSentTagForDocs) set recName to my getRecordNameByUUID(uuidText) my writeDebug("recName=" & recName) set recFileName to my getRecordFileNameByUUID(uuidText) my writeDebug("recFileName=" & recFileName) set recRef to my getReferenceURLByUUID(uuidText) my writeDebug("recRef=" & recRef) set j to "{\"ts\":\"" & ts & "\",\"batch\":\"" & bid & "\",\"to\":\"" & my jsonEscape(theTo) & "\",\"zip\":\"" & my jsonEscape(zipName) & "\",\"uuid\":\"" & my jsonEscape(uuidText) & "\",\"ref\":\"" & my jsonEscape(recRef) & "\",\"name\":\"" & my jsonEscape(recName) & "\",\"filename\":\"" & my jsonEscape(recFileName) & "\"}" my writeDebug("JSON built for uuid=" & uuidText) if recRef is "" then set mdLink to "" else set mdLink to "[oeffnen](" & recRef & ")" end if set mdLine to "| " & ts & " | `" & bid & "` | " & my mdEscape(theTo) & " | `" & my mdEscape(zipName) & "` | " & my mdEscape(recName) & " | " & my mdEscape(recFileName) & " | " & mdLink & " |" my writeDebug("MD line built for uuid=" & uuidText) my appendLineToDTRecordByUUID(ndjsonLogUUID, j) my writeDebug("NDJSON append done uuid=" & uuidText) my appendLineToDTRecordByUUID(mdLogUUID, mdLine) my writeDebug("Markdown append done uuid=" & uuidText) my writeDebug("PROCESS DONE uuid=" & uuidText) end repeat do shell script "rm -rf " & quoted form of stageFilesPOSIX my writeDebug("Cleanup stage files done") my writeResultRecordByUUID(resultUUID, ts, bid, toAddresses, ccAddresses, (count of toSend), isOutsider) my showCompletionNotification((count of toSend)) my writeDebug("Script end - success") on error errMsg number errNum my writeDebug("ERROR number=" & errNum & " message=" & errMsg) display dialog "Hinweis: " & errMsg buttons {"OK"} default button "OK" end try end timeout end run