diff --git a/Helpers/scripts/_belegversand.applescript b/Helpers/scripts/_belegversand.applescript index 3ad13c4..2a454af 100644 --- a/Helpers/scripts/_belegversand.applescript +++ b/Helpers/scripts/_belegversand.applescript @@ -1,5 +1,5 @@ (* -_belegversand.scpt – Version 1.4 +_belegversand.scpt – Version 1.5 Rolle dieses Scripts -------------------- Dieses Script ist die eigentliche Versandlogik des Systems. @@ -7,11 +7,27 @@ 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 +- Pruefung der Dateitypen (SUPPORTED_FILE_TYPES_FOR_SENDING) +- Aufteilung auf mehrere Mails nach Groesse (MAX_MAIL_SIZE_MB) +- Export in einen Arbeitsordner (Stage) +- je Mail: passwortgeschuetztes ZIP-Archiv ODER Einzeldateien (ZIP_ARCHIVE) +- Erzeugen der Mail-Entwuerfe in Apple Mail - optionales Tagging der verarbeiteten Dokumente - Protokollierung des Versands in NDJSON und Markdown + +Version 1.5: +----------- +- ZIP_ARCHIVE=true/false: ZIP-Archiv oder Belege einzeln anhaengen + (Passwort-Dialog nur noch im ZIP-Modus) +- MAX_MAIL_SIZE_MB: Belege werden automatisch auf mehrere Mails verteilt. + Gerechnet wird mit Puffer, da Mail Anhaenge per Base64 um ca. 1/3 aufblaeht. +- SUPPORTED_FILE_TYPES_FOR_SENDING: nicht erlaubte Dateitypen werden vor + dem Versand gemeldet (Abbrechen oder ohne diese weiter) +- Stage-Ordner jetzt unter ~/Library/Caches/DT-Belegversand/ + (optional per STAGE_DIR), wird nicht mehr sofort geloescht, sondern + alte Laeufe (> 7 Tage) werden beim naechsten Start aufgeraeumt +- Mehrere Mails: Betreff "(1/2)", Batch-ID "-1", Logs je Mail +- Fehlen die neuen Schluessel in der Conf, laeuft alles wie in 1.4 *) property sentTag : "gesendet" @@ -22,8 +38,17 @@ property defaultWriteDebugLog : false property defaultMailBodyFolderName : "mail_vorlagen" property defaultMailBodyRecordName : "belegversand" property defaultDryRunPreviewPath : "/tmp/DT-Belegversand-preview.txt" +-- Neu in 1.5 +property defaultZipArchive : true +property defaultMaxMailSizeMB : 20 +property defaultSupportedFileTypes : "pdf, tif, tiff" +-- Anteil der Mailgroesse, der fuer Anhaenge genutzt wird. +-- Base64 blaeht um ~37 % auf (inkl. Zeilenumbrueche), dazu Header/Text. +property usableMailShare : 0.7 +-- Alte Stage-Laeufe werden nach so vielen Tagen geloescht. +property stageKeepDays : 7 -property appVersion : "1.4" +property appVersion : "1.5" property basePath : "Library/Application Scripts/com.devon-technologies.think/Menu/Belegversand" property configGroupName : "_config" @@ -44,6 +69,15 @@ property cfgMailBodyFolderName : missing value property cfgMailBodyRecordName : missing value property cfgSentTagForDocs : missing value property cfgDryRunPreviewPath : missing value +property cfgZipArchive : missing value +property cfgMaxMailSizeMB : missing value +property cfgSupportedTypes : missing value +property cfgSupportedTypesText : missing value +property cfgStageDir : missing value + +-- ============================================================================= +-- Debug +-- ============================================================================= on writeDebug(msg) if my writeDebugLogEnabled() is false then return @@ -54,7 +88,7 @@ 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 ===") + my writeDebug("=== Debug-Session gestartet (v" & appVersion & ") ===") end resetDebugLog on writeDebugLogEnabled() @@ -66,6 +100,10 @@ on writeDebugLogEnabled() end try end writeDebugLogEnabled +-- ============================================================================= +-- Allgemeine Helfer +-- ============================================================================= + 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 @@ -112,6 +150,63 @@ on replaceText(find, repl, txt) end try end replaceText +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 + +-- Kommagetrennte Liste in AppleScript-Liste (getrimmt, ohne Leereintraege) +on parseCSVList(csvText) + if csvText is "" or csvText is missing value then return {} + + set csvText to csvText as text + set outList 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 outList to trimmedPart + end if + end repeat + + return outList + on error errMsg + set AppleScript's text item delimiters to saveDelimiters + my writeDebug("parseCSVList error: " & errMsg) + return {} + end try +end parseCSVList + +on parseCSVEmails(csvText) + return my parseCSVList(csvText) +end parseCSVEmails + +-- Bytes als "12.3" (MB, eine Nachkommastelle) +on formatMB(bytes) + return do shell script "awk 'BEGIN{printf \"%.1f\", " & (bytes as integer) & "/1048576}'" +end formatMB + +-- Dateiendung (ohne Punkt), "" wenn keine vorhanden +on fileExtension(fileName) + set fileName to fileName as text + if fileName does not contain "." then return "" + set oldTIDs to AppleScript's text item delimiters + set AppleScript's text item delimiters to "." + set ext to last text item of fileName + set AppleScript's text item delimiters to oldTIDs + return ext +end fileExtension + +-- ============================================================================= +-- DEVONthink-Records +-- ============================================================================= + on isGroupByUUID(recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID @@ -319,6 +414,37 @@ on getRecordFileNameByUUID(recordUUID) end tell end getRecordFileNameByUUID +-- Dateiendung eines Records: zuerst aus "filename", sonst aus "path". +-- (Der DEVONthink-Typ allein reicht nicht: JPG und TIFF sind beide "picture".) +on getRecordExtensionByUUID(recordUUID) + set fnText to "" + 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 + end try + if fnText is "" then + try + set p to path of r + if p is not missing value then set fnText to p as rich text + end try + end if + end tell + return my fileExtension(fnText) +end getRecordExtensionByUUID + +on getRecordSizeByUUID(recordUUID) + tell application id "DNtp" + set r to get record with uuid recordUUID + try + return (size of r) as integer + on error + return 0 + end try + end tell +end getRecordSizeByUUID + on getReferenceURLByUUID(recordUUID) tell application id "DNtp" set r to get record with uuid recordUUID @@ -396,6 +522,10 @@ on getTopParentGroupFromUUID(recordUUID) end tell end getTopParentGroupFromUUID +-- ============================================================================= +-- Konfiguration +-- ============================================================================= + on findConfigRecordUUIDFromCurrentSelection() tell application id "DNtp" set sel to selection @@ -416,38 +546,6 @@ on findConfigRecordUUIDFromCurrentSelection() 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 @@ -464,6 +562,14 @@ on configValue(configText, keyName) return missing value end configValue +-- true/false-Wert aus der Conf, mit Default bei fehlendem/leerem Schluessel +on configBool(configText, keyName, defaultValue) + set v to my configValue(configText, keyName) + if v is missing value or v is "" then return defaultValue + set vLower to do shell script "printf %s " & quoted form of v & " | tr '[:upper:]' '[:lower:]'" + return (vLower is "true") +end configBool + on loadConfig() set configUUID to my findConfigRecordUUIDFromCurrentSelection() set cfgText to my getDTRecordTextByUUID(configUUID) @@ -487,13 +593,7 @@ on loadConfig() 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 vDebugBool to my configBool(cfgText, "WRITE_DEBUG_LOG", defaultWriteDebugLog) set vMailBodyFolderName to my configValue(cfgText, "MAILBODY_FOLDER_NAME") if vMailBodyFolderName is missing value or vMailBodyFolderName is "" then set vMailBodyFolderName to defaultMailBodyFolderName @@ -507,9 +607,40 @@ on loadConfig() 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} + -- Neu in 1.5 ------------------------------------------------------------ + set vZipArchive to my configBool(cfgText, "ZIP_ARCHIVE", defaultZipArchive) + + set vMaxMB to defaultMaxMailSizeMB + set vMaxMBText to my configValue(cfgText, "MAX_MAIL_SIZE_MB") + if vMaxMBText is not missing value and vMaxMBText is not "" then + try + set vMaxMB to vMaxMBText as number + if vMaxMB is less than or equal to 0 then set vMaxMB to defaultMaxMailSizeMB + on error + set vMaxMB to defaultMaxMailSizeMB + end try + end if + + set vTypesText to my configValue(cfgText, "SUPPORTED_FILE_TYPES_FOR_SENDING") + if vTypesText is missing value or vTypesText is "" then set vTypesText to defaultSupportedFileTypes + set vTypesRaw to my parseCSVList(vTypesText) + set vTypes to {} + repeat with t in vTypesRaw + set tText to t as text + if tText starts with "." then set tText to text 2 thru -1 of tText + if tText is not "" then set end of vTypes to tText + end repeat + + set vStageDir to my configValue(cfgText, "STAGE_DIR") + if vStageDir is missing value then set vStageDir to "" + + 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, zipArchive:vZipArchive, maxMailSizeMB:vMaxMB, supportedTypes:vTypes, supportedTypesText:vTypesText, stageDir:vStageDir} end loadConfig +-- ============================================================================= +-- Mail-Vorlagen +-- ============================================================================= + on runTemplateSelector() set expandedPath to (do shell script "echo ~") & "/" & basePath set scriptPath to expandedPath & "/Helpers/show_mail_templates.scpt" @@ -517,15 +648,7 @@ on runTemplateSelector() try set loadedScript to load script (POSIX file scriptPath as alias) set standaloneMode of loadedScript to false - - -- DEBUG - -- display dialog "standaloneMode gesetzt auf: false" buttons {"OK"} - set templateResult to selectTemplate() of loadedScript - - -- DEBUG - -- display dialog "templateResult: " & templateResult buttons {"OK"} - return templateResult on error errMsg display dialog "FEHLER: " & errMsg buttons {"OK"} @@ -558,7 +681,6 @@ on getMailBodyTextFromCurrentDB() 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!") @@ -582,53 +704,196 @@ on getMailBodyTextFromCurrentDB() end tell end getMailBodyTextFromCurrentDB +-- ============================================================================= +-- Neu in 1.5: Dateitypen, Groessen, Pakete, Stage +-- ============================================================================= + +on isSupportedExtension(ext) + if ext is "" then return false + -- AppleScript vergleicht Text standardmaessig ohne Gross-/Kleinschreibung + repeat with t in my cfgSupportedTypes + if ext is (t as text) then return true + end repeat + return false +end isSupportedExtension + +-- Teilt eine UUID-Liste in {erlaubt, nicht erlaubt} +on splitBySupportedType(uuidList) + set okList to {} + set badList to {} + repeat with u in uuidList + set uText to u as text + set ext to my getRecordExtensionByUUID(uText) + if my isSupportedExtension(ext) then + set end of okList to uText + else + set end of badList to uText + my writeDebug("Typ nicht erlaubt uuid=" & uText & " ext='" & ext & "'") + end if + end repeat + return {okList, badList} +end splitBySupportedType + +-- Namensliste fuer Dialoge, gekuerzt auf maxShown Eintraege +on namesListText(uuidList, maxShown, withSize) + set outText to "" + set shownCount to 0 + repeat with u in uuidList + if shownCount is greater than or equal to maxShown then exit repeat + set uText to u as text + set lineText to "• " & my getRecordFileNameByUUID(uText) + if withSize then set lineText to lineText & " (" & my formatMB(my getRecordSizeByUUID(uText)) & " MB)" + set outText to outText & lineText & return + set shownCount to shownCount + 1 + end repeat + if (count of uuidList) is greater than maxShown then + set outText to outText & "… und " & ((count of uuidList) - maxShown) & " weitere" & return + end if + return outText +end namesListText + +-- Netto-Budget fuer Anhaenge pro Mail in Bytes +on attachmentBudgetBytes() + return ((my cfgMaxMailSizeMB) * 1048576 * usableMailShare) as integer +end attachmentBudgetBytes + +(* +buildPackages(uuidList, limitBytes) +----------------------------------- +Verteilt Belege auf moeglichst wenige Mails ("First Fit Decreasing"): +groesste Belege zuerst, jeder Beleg in die erste Mail, in die er noch passt. +Belege, die allein schon groesser als das Budget sind, landen in "oversized". +Rueckgabe: {packages:{{uuid,...},...}, sizes:{bytes,...}, oversized:{uuid,...}} +*) +on buildPackages(uuidList, limitBytes) + set itemsList to {} + repeat with u in uuidList + set uText to u as text + set end of itemsList to {uuidText:uText, sz:(my getRecordSizeByUUID(uText))} + end repeat + + -- absteigend nach Groesse sortieren + set n to count of itemsList + repeat with i from 1 to n - 1 + set maxIdx to i + repeat with j from i + 1 to n + if (sz of item j of itemsList) > (sz of item maxIdx of itemsList) then set maxIdx to j + end repeat + if maxIdx is not i then + set tmp to item i of itemsList + set item i of itemsList to item maxIdx of itemsList + set item maxIdx of itemsList to tmp + end if + end repeat + + set pkgList to {} + set pkgSizes to {} + set oversizedList to {} + + repeat with anEntry in itemsList + set s to sz of anEntry + set u to uuidText of anEntry + if s > limitBytes then + set end of oversizedList to u + else + set placed to false + repeat with k from 1 to count of pkgSizes + if ((item k of pkgSizes) + s) is less than or equal to limitBytes then + set p to item k of pkgList + set end of p to u + set item k of pkgSizes to (item k of pkgSizes) + s + set placed to true + exit repeat + end if + end repeat + if not placed then + set end of pkgList to {u} + set end of pkgSizes to s + end if + end if + end repeat + + return {packages:pkgList, sizes:pkgSizes, oversized:oversizedList} +end buildPackages + +on stageBaseDir() + set homeDir to POSIX path of (path to home folder) + set p to my cfgStageDir + if p is missing value or p is "" then return homeDir & "Library/Caches/DT-Belegversand/" + set p to p as text + if p starts with "~/" then set p to homeDir & (text 3 thru -1 of p) + if p does not end with "/" then set p to p & "/" + return p +end stageBaseDir + +-- Loescht alte Stage-Laeufe. Aus Sicherheitsgruenden nur, wenn der Pfad +-- erkennbar ein Belegversand-Ordner ist (nie versehentlich ~ oder /). +on cleanupOldStages(baseDir) + if baseDir does not contain "Belegversand" then + my writeDebug("cleanupOldStages übersprungen (Pfad enthält nicht 'Belegversand'): " & baseDir) + return + end if + try + do shell script "test -d " & quoted form of baseDir & " && find " & quoted form of baseDir & " -mindepth 1 -maxdepth 1 -type d -mtime +" & stageKeepDays & " -exec rm -rf {} + 2>/dev/null || true" + my writeDebug("cleanupOldStages done: " & baseDir) + on error errMsg + my writeDebug("cleanupOldStages error: " & errMsg) + end try +end cleanupOldStages + +-- ============================================================================= +-- Dry Run +-- ============================================================================= + 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) +on modeText() + if my cfgZipArchive then return "ZIP-Archiv" + return "Einzeldateien" +end modeText + +on writeDryRunPreviewFile(pkgList, pkgSizes, 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 nMails to count of pkgList + set nItems to 0 + repeat with p in pkgList + set nItems to nItems + (count of p) + end repeat + set previewText to "Belegversand – Vorschau (Dry Run)" & return & return & ¬ "Empfänger: " & theTo & return & ¬ - "Anzahl Belege: " & (count of toSendUUIDs) & return & return + "Versandmodus: " & my modeText() & return & ¬ + "Anzahl Belege: " & nItems & return & ¬ + "Anzahl Mails: " & nMails & " (max. " & my cfgMaxMailSizeMB & " MB je Mail)" & 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 + repeat with k from 1 to nMails + set previewText to previewText & return & "Mail " & k & " von " & nMails & " – ca. " & my formatMB(item k of pkgSizes) & " MB Anhänge:" & return + repeat with u in (item k of pkgList) + set uText to u as text + set previewText to previewText & i & ". " & my getRecordFileNameByUUID(uText) & " (" & my formatMB(my getRecordSizeByUUID(uText)) & " MB)" & return + set i to i + 1 + end repeat end repeat do shell script "cat > " & quoted form of previewPath & " <<'EOF'" & linefeed & previewText & "EOF" return previewPath end writeDryRunPreviewFile -on dryRunSummaryText(toSendUUIDs, previewPath) +on dryRunSummaryText(toSendUUIDs, nMails, 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 "Insgesamt würden " & nItems & " Belege verarbeitet," & return & ¬ + "verteilt auf " & nMails & " Mail(s) – Modus: " & my modeText() & "." & return & return + set outText to outText & my namesListText(toSendUUIDs, 4, false) set outText to outText & return & "Die gesamte Liste findest du hier:" & return & previewPath return outText end dryRunSummaryText @@ -637,6 +902,10 @@ on showDryRunPreviewInFinder(previewPath) do shell script "open -R " & quoted form of previewPath end showDryRunPreviewInFinder +-- ============================================================================= +-- Buchungsjahre +-- ============================================================================= + on isYearGroupName(groupName) try if (length of groupName) is not 4 then return false @@ -693,12 +962,10 @@ on getSelectedBookingYearInfo(uuidList) 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 @@ -718,75 +985,74 @@ on getOrCreateGlobalSentGroupUUID(referenceUUID) 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) +-- ============================================================================= +-- Ergebnis & Meldungen +-- ============================================================================= + +on joinList(theList, sep) + set outText to "" + repeat with anItem in theList + if outText is "" then + set outText to (anItem as text) + else + set outText to outText & sep & (anItem as text) + end if + end repeat + return outText +end joinList + +on writeResultRecordByUUID(recordUUID, ts, bid, toAddrs, ccAddrs, nItems, nMails, 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 - + set toDisplay to my joinList(toAddrs, ", ") + set ccDisplay to my joinList(ccAddrs, ", ") if ccDisplay is "" then set ccDisplay to "(keine)" - my writeDebug("toDisplay=" & toDisplay & " ccDisplay=" & ccDisplay) + if nMails is 1 then + set draftText to "Der Mail-Entwurf wurde in Apple Mail erstellt." + else + set draftText to nMails & " Mail-Entwürfe wurden in Apple Mail erstellt." + end if 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 & ¬ + "**Versandmodus:** " & my modeText() & return & ¬ "**Anzahl Belege:** " & nItems & return & ¬ + "**Anzahl Mails:** " & nMails & return & ¬ "**Status:** ok Erfolgreich" & return & return & ¬ - "Der Mail-Entwurf wurde in Apple Mail erstellt." + draftText 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) +on showCompletionNotification(nItems, nMails) try - set msgText to (nItems as text) & " Beleg(e) – Mail-Entwurf erstellt." - my writeDebug("notification message=" & msgText) - + if nMails is 1 then + set msgText to (nItems as text) & " Beleg(e) – Mail-Entwurf erstellt." + else + set msgText to (nItems as text) & " Beleg(e) – " & nMails & " Mail-Entwürfe erstellt." + end if tell application id "DNtp" - display notification msgText with title "Belegversand" subtitle "Mail-Entwurf erstellt." + display notification msgText with title "Belegversand" subtitle "Bitte Entwürfe prüfen und senden." 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) @@ -797,11 +1063,16 @@ on friendlyRunHeader(ts) end try end friendlyRunHeader +-- ============================================================================= +-- Hauptablauf +-- ============================================================================= + on run with timeout of 3600 seconds try + -- ── Auswahl & Konfiguration ───────────────────────────────────── set selInfo to my getSelectedUUIDs() set selCount to item 1 of selInfo set selectedUUIDs to item 2 of selInfo @@ -827,29 +1098,83 @@ on run set my cfgMailBodyRecordName to mailBodyRecordName of cfg set my cfgSentTagForDocs to sentTagForDocs of cfg set my cfgDryRunPreviewPath to dryRunPreviewPath of cfg + set my cfgZipArchive to zipArchive of cfg + set my cfgMaxMailSizeMB to maxMailSizeMB of cfg + set my cfgSupportedTypes to supportedTypes of cfg + set my cfgSupportedTypesText to supportedTypesText of cfg + set my cfgStageDir to stageDir 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 + my writeDebug("Mode=" & my modeText() & " maxMB=" & my cfgMaxMailSizeMB & " types=" & my cfgSupportedTypesText) + my writeDebug("Booking year: yearUUID=" & bookingYearUUID & " yearName=" & bookingYearName & " isOutsider=" & isOutsider) + my writeDebug("Selected raw count=" & selCount & " usable=" & (count of selectedUUIDs)) 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 windowTitle & return & return & "Modus waehlen:" buttons {"Dry Run", "Senden (Entwurf erstellen)", "Abbrechen"} default button "Dry Run") + -- ── Prüfung 1: Dateitypen ─────────────────────────────────────── + set typeSplit to my splitBySupportedType(toSend) + set badTypeUUIDs to item 2 of typeSplit + + if (count of badTypeUUIDs) > 0 then + set badText to (count of badTypeUUIDs) & " Beleg(e) haben einen Dateityp, der nicht versendet wird." & return & ¬ + "Erlaubt sind: " & my cfgSupportedTypesText & return & return & my namesListText(badTypeUUIDs, 8, false) & return & ¬ + "Tipp: Fotos (JPG, HEIC, PNG) vorher in DEVONthink zu PDF konvertieren." + + if (count of item 1 of typeSplit) is 0 then + display dialog windowTitle & return & return & badText buttons {"OK"} default button "OK" + return + end if + + set typeChoice to button returned of (display dialog windowTitle & return & return & badText buttons {"Abbrechen", "Ohne diese weiter"} default button "Abbrechen") + if typeChoice is "Abbrechen" then + my writeDebug("User abort: unsupported types") + return + end if + set toSend to item 1 of typeSplit + end if + + -- ── Prüfung 2: Größe & Aufteilung ─────────────────────────────── + set budgetBytes to my attachmentBudgetBytes() + set pkgResult to my buildPackages(toSend, budgetBytes) + set pkgList to packages of pkgResult + set pkgSizes to sizes of pkgResult + set oversizedUUIDs to oversized of pkgResult + my writeDebug("Budget bytes=" & budgetBytes & " packages=" & (count of pkgList) & " oversized=" & (count of oversizedUUIDs)) + + if (count of oversizedUUIDs) > 0 then + set bigText to (count of oversizedUUIDs) & " Beleg(e) sind allein schon zu groß für eine Mail" & return & ¬ + "(max. " & my cfgMaxMailSizeMB & " MB Mailgröße, netto ca. " & my formatMB(budgetBytes) & " MB Anhänge):" & return & return & my namesListText(oversizedUUIDs, 8, true) & return & ¬ + "Tipp: PDF verkleinern oder getrennt versenden." + + if (count of pkgList) is 0 then + display dialog windowTitle & return & return & bigText buttons {"OK"} default button "OK" + return + end if + + set bigChoice to button returned of (display dialog windowTitle & return & return & bigText buttons {"Abbrechen", "Ohne diese weiter"} default button "Abbrechen") + if bigChoice is "Abbrechen" then + my writeDebug("User abort: oversized") + return + end if + end if + + -- toSend = genau das, was in den Paketen steckt + set toSend to {} + repeat with p in pkgList + repeat with u in p + set end of toSend to (u as text) + end repeat + end repeat + set nItems to count of toSend + set nMails to count of pkgList + + -- ── Modus & Eingaben ──────────────────────────────────────────── + set drChoice to button returned of (display dialog windowTitle & return & return & "Modus wählen:" buttons {"Dry Run", "Senden (Entwurf erstellen)", "Abbrechen"} default button "Dry Run") if drChoice is "Abbrechen" then my writeDebug("User hat Abbrechen geklickt") @@ -859,24 +1184,20 @@ on run 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) + set theTo to text returned of (display dialog "Empfänger E-Mail:" default answer my cfgMailToDefault) + set theCcTo to text returned of (display dialog "CC-Empfänger (optional):" default answer my cfgMailCcToDefault) + + if my cfgZipArchive then + set zipPass to text returned of (display dialog "ZIP-Passwort:" default answer my cfgZipPasswordDefault with hidden answer) + else + set zipPass to "" + end if 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 previewPath to my writeDryRunPreviewFile(pkgList, pkgSizes, theTo) + set dryRunText to my dryRunSummaryText(toSend, nMails, previewPath) set dryRunChoice to button returned of (display dialog windowTitle & return & return & dryRunText buttons {"OK", "Liste im Finder zeigen"} default button "OK") if dryRunChoice is "Liste im Finder zeigen" then @@ -887,116 +1208,136 @@ on run return end if - set proceed to button returned of (display dialog previewText & return & "Entwurf erstellen & ZIP bauen?" buttons {"Abbrechen", "Weiter"} default button "Weiter") + set previewText to "Ausgewählt: " & selCount & return & ¬ + "Zu senden (ungesendet): " & nItems & return & ¬ + "Versandmodus: " & my modeText() & return & ¬ + "Anzahl Mails: " & nMails & 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 my cfgZipArchive is false then + set previewText to previewText & return & "Hinweis: Belege gehen unverschlüsselt (ohne ZIP-Passwort) raus." & return + end if + + if nMails is 1 then + set confirmText to "Mail-Entwurf erstellen?" + else + set confirmText to nMails & " Mail-Entwürfe erstellen?" + end if + + set proceed to button returned of (display dialog previewText & return & confirmText buttons {"Abbrechen", "Weiter"} default button "Weiter") if proceed is "Abbrechen" then my writeDebug("User aborted before export") return end if + -- ── Stage vorbereiten ─────────────────────────────────────────── 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 + set stageBase to my stageBaseDir() + my ensureFolder(stageBase) + my cleanupOldStages(stageBase) + set stageRootPOSIX to stageBase & bid & "/" + my ensureFolder(stageRootPOSIX) 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") + -- Mailtext einmal für alle Mails holen (Vorlagenauswahl nur einmal) set mailBodyText to my getMailBodyTextFromCurrentDB() - if mailBodyText is "" then - set mailBodyText to "Im Anhang die aktuellen Belege." - end if + if mailBodyText is "" then set mailBodyText to "Im Anhang die aktuellen Belege." set toAddresses to my parseCSVEmails(theTo) set ccAddresses to my parseCSVEmails(theCcTo) + my writeDebug("toAddresses=" & my joinList(toAddresses, ", ") & " ccAddresses=" & my joinList(ccAddresses, ", ")) - 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 + -- je Mail: {bidK, zipName} + set mailInfos to {} - 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 + -- ── Je Paket: exportieren, ggf. zippen, Entwurf erstellen ────── + repeat with k from 1 to nMails + set pkg to item k of pkgList + + if nMails is 1 then + set bidK to bid + set subjectText to my cfgMailSubjectPrefix + else + set bidK to bid & "-" & k + set subjectText to my cfgMailSubjectPrefix & " (" & k & "/" & nMails & ")" + end if + + set mailDirPOSIX to stageRootPOSIX & "mail-" & k & "/" + my ensureFolder(mailDirPOSIX) + set mailDirAlias to (POSIX file mailDirPOSIX) as alias + my writeDebug("Mail " & k & "/" & nMails & " dir=" & mailDirPOSIX & " items=" & (count of pkg)) + + repeat with u in pkg + my exportRecordByUUID(u as text, mailDirAlias) + end repeat + + do shell script "find " & quoted form of mailDirPOSIX & " -name '.DS_Store' -type f -delete" + do shell script "find " & quoted form of mailDirPOSIX & " -name '__MACOSX' -exec rm -rf {} + 2>/dev/null || true" + do shell script "find " & quoted form of mailDirPOSIX & " -name 'DEVONtech_storage' -exec rm -rf {} + 2>/dev/null || true" + + set fileListText to do shell script "find " & quoted form of mailDirPOSIX & " -type f ! -name '.*' | sort" + if fileListText is "" then error "Keine exportierten Dateien gefunden (Mail " & k & ")." + + if my cfgZipArchive then + set zipName to "Belege_" & bidK & ".zip" + set zipPath to stageRootPOSIX & zipName + if zipPass is "" then + do shell script "cd " & quoted form of mailDirPOSIX & " ; /usr/bin/zip -r -j " & quoted form of zipPath & " . " + else + do shell script "cd " & quoted form of mailDirPOSIX & " ; /usr/bin/zip -P " & quoted form of zipPass & " -r -j " & quoted form of zipPath & " . " + end if + set attachPaths to {zipPath} + my writeDebug("ZIP created: " & zipPath) + else + set zipName to "" + set attachPaths to paragraphs of fileListText + my writeDebug("Single files: " & (count of attachPaths)) + end if + + tell application "Mail" + set newMessage to make new outgoing message with properties {visible:true, subject:subjectText, 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 + repeat with aPath in attachPaths + make new attachment with properties {file name:(POSIX file (aPath as rich text) as alias)} at after the last paragraph + delay 0.2 + end repeat + end tell end tell - activate - end tell + + my writeDebug("Mail draft created " & k & "/" & nMails) + set end of mailInfos to {bidK, zipName} + end repeat - my writeDebug("Mail draft created") + tell application "Mail" to activate + -- ── Logs vorbereiten ──────────────────────────────────────────── 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, "") @@ -1004,59 +1345,61 @@ on run set resultUUID to my getOrCreateMarkdownLogUUID(logGroupUUID, "!Ergebnis", "# Letzter Sendelauf" & return) set runHeader to my friendlyRunHeader(ts) + set tableHead to "| Zeit | Batch | Empfänger | ZIP | DEVONthink-Name | Dateiname | DEVONthink |" & return & "|---|---|---|---|---|---|---|" 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 & "|---|---|---|---|---|---|---|" + set mdRunIntro to return & "## " & runHeader & return & return & tableHead else - set mdRunIntro to return & return & "## " & runHeader & return & return & "| Zeit | Batch | Empfänger | ZIP | DEVONthink-Name | Dateiname | DEVONthink |" & return & "|---|---|---|---|---|---|---|" + set mdRunIntro to return & return & "## " & runHeader & return & return & tableHead end if my appendLineToDTRecordByUUID(mdLogUUID, mdRunIntro) - my writeDebug("ndjsonLogUUID=" & ndjsonLogUUID) - my writeDebug("mdLogUUID=" & mdLogUUID) + if my cfgZipArchive then + set modeKey to "zip" + else + set modeKey to "single" + end if - 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 "" + -- ── Taggen & protokollieren, je Mail ──────────────────────────── + repeat with k from 1 to nMails + set pkg to item k of pkgList + set bidK to item 1 of (item k of mailInfos) + set zipName to item 2 of (item k of mailInfos) + if zipName is "" then + set zipCell to "einzeln" else - set mdLink to "[oeffnen](" & recRef & ")" + set zipCell to "`" & my mdEscape(zipName) & "`" 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) + repeat with thisUUID in pkg + set uuidText to thisUUID as text + my addTagByUUID(uuidText, my cfgSentTagForDocs) + + set recName to my getRecordNameByUUID(uuidText) + set recFileName to my getRecordFileNameByUUID(uuidText) + set recRef to my getReferenceURLByUUID(uuidText) + + set j to "{\"ts\":\"" & ts & "\",\"batch\":\"" & bidK & "\",\"mail\":\"" & k & "/" & nMails & "\",\"mode\":\"" & modeKey & "\",\"to\":\"" & my jsonEscape(theTo) & "\",\"zip\":\"" & my jsonEscape(zipName) & "\",\"uuid\":\"" & my jsonEscape(uuidText) & "\",\"ref\":\"" & my jsonEscape(recRef) & "\",\"name\":\"" & my jsonEscape(recName) & "\",\"filename\":\"" & my jsonEscape(recFileName) & "\"}" + + if recRef is "" then + set mdLink to "" + else + set mdLink to "[öffnen](" & recRef & ")" + end if + + set mdLine to "| " & ts & " | `" & bidK & "` | " & my mdEscape(theTo) & " | " & zipCell & " | " & my mdEscape(recName) & " | " & my mdEscape(recFileName) & " | " & mdLink & " |" + + my appendLineToDTRecordByUUID(ndjsonLogUUID, j) + my appendLineToDTRecordByUUID(mdLogUUID, mdLine) + my writeDebug("PROCESS DONE uuid=" & uuidText & " mail=" & k) + end repeat end repeat - do shell script "rm -rf " & quoted form of stageFilesPOSIX - my writeDebug("Cleanup stage files done") + -- Stage bleibt bewusst stehen (Mail lädt Anhänge evtl. verzögert); + -- alte Läufe werden beim nächsten Start aufgeräumt. - my writeResultRecordByUUID(resultUUID, ts, bid, toAddresses, ccAddresses, (count of toSend), isOutsider) - - my showCompletionNotification((count of toSend)) + my writeResultRecordByUUID(resultUUID, ts, bid, toAddresses, ccAddresses, nItems, nMails, isOutsider) + my showCompletionNotification(nItems, nMails) my writeDebug("Script end - success") @@ -1067,4 +1410,4 @@ on run end timeout -end run +end run \ No newline at end of file