mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
Add configurable e-mail delivery: direct SMTP send or IMAP drafts
Mahnungs- and SEPA-info-mails could previously only be saved as a local .eml file that still had to be manually imported into Thunderbird. Add a per-store "E-Mail-Versand" configuration (Optionen -> E-Mail-Versand, stored in repository.json alongside the rest of the club's settings, since different stores may use different mailboxes) with four delivery modes: - "Lokal speichern": today's behaviour, unchanged default for existing stores. - "Direkt versenden": sends via SMTP. - "Als Entwurf ablegen": IMAP APPENDs into a configurable drafts folder, so it shows up live in whatever mail client is already watching that account. - "Jedes Mal fragen": prompts once per generation action (not per e-mail -- a SEPA batch can cover dozens of members) with Senden/Entwürfe/Abbrechen. New ccma.services.mail_delivery module (smtplib/imaplib, no new dependency) opens one authenticated connection per batch and reuses it across all messages instead of reconnecting per recipient. Both "Verbindung testen" buttons in Options exercise the same connection path used for real delivery. The archived per-member copy of every generated e-mail is unaffected and still always written regardless of delivery mode. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b7554478ac
commit
6b0da82b45
+23
-10
@@ -25,6 +25,7 @@ from ccma.domain.dates import date_input_hint, format_date_for_display
|
||||
from ccma.services.reminder_mail import generate_and_send_reminder_mail
|
||||
from ccma.storage.repository import MemberRepository, RepositoryError
|
||||
from ccma.ui.document_dialog import DocumentTemplateDialog
|
||||
from ccma.ui.email_delivery_dialog import resolve_delivery_mode
|
||||
from ccma.ui.labels import (
|
||||
CLAIM_ITEM_TYPE_LABELS,
|
||||
REMINDER_CHANNEL_LABELS,
|
||||
@@ -1258,22 +1259,29 @@ class ReminderMailDialog(tk.Toplevel):
|
||||
self.grab_set()
|
||||
|
||||
def _generate(self) -> None:
|
||||
selected = filedialog.asksaveasfilename(
|
||||
parent=self,
|
||||
title="Mahnung als Thunderbird-Entwurf speichern",
|
||||
defaultextension=".eml",
|
||||
initialfile=f"{self.reminder_name}.eml",
|
||||
filetypes=[("E-Mail-Datei", "*.eml"), ("Alle Dateien", "*.*")],
|
||||
)
|
||||
if not selected:
|
||||
delivery_mode = resolve_delivery_mode(self, self.repository)
|
||||
if delivery_mode is None:
|
||||
return
|
||||
output_path = None
|
||||
if delivery_mode == "local":
|
||||
selected = filedialog.asksaveasfilename(
|
||||
parent=self,
|
||||
title="Mahnung als Thunderbird-Entwurf speichern",
|
||||
defaultextension=".eml",
|
||||
initialfile=f"{self.reminder_name}.eml",
|
||||
filetypes=[("E-Mail-Datei", "*.eml"), ("Alle Dateien", "*.*")],
|
||||
)
|
||||
if not selected:
|
||||
return
|
||||
output_path = selected
|
||||
try:
|
||||
result = generate_and_send_reminder_mail(
|
||||
self.repository,
|
||||
self.member_id,
|
||||
self.claim_id,
|
||||
self.reminder_id,
|
||||
output_path=selected,
|
||||
delivery_mode=delivery_mode,
|
||||
output_path=output_path,
|
||||
sender_name=self.sender_name_var.get(),
|
||||
sender_email=self.sender_email_var.get(),
|
||||
signature=self.signature.get("1.0", "end-1c"),
|
||||
@@ -1281,9 +1289,14 @@ class ReminderMailDialog(tk.Toplevel):
|
||||
except (OSError, RepositoryError) as exc:
|
||||
messagebox.showerror("Mahnung konnte nicht erzeugt werden", str(exc), parent=self)
|
||||
return
|
||||
outcome = {
|
||||
"local": f"gespeichert:\n{result.export_path}",
|
||||
"send": "direkt versandt.",
|
||||
"drafts": "als Entwurf im Postfach abgelegt.",
|
||||
}[delivery_mode]
|
||||
messagebox.showinfo(
|
||||
"Mahnung erzeugt",
|
||||
f"Die E-Mail wurde gespeichert und als versandt gebucht:\n{result.export_path}",
|
||||
f"Die E-Mail wurde {outcome}\n\nDie Mahnung wurde als versandt gebucht.",
|
||||
parent=self,
|
||||
)
|
||||
self.destroy()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from ccma.storage.repository import MemberRepository
|
||||
|
||||
|
||||
class EmailDeliveryChoiceDialog(tk.Toplevel):
|
||||
"""Shown once per generation action (not per individual e-mail) when the
|
||||
repository's delivery mode is "ask" -- lets the board decide per-batch whether
|
||||
to send immediately or park the e-mail(s) as a draft for later review."""
|
||||
|
||||
def __init__(self, master: tk.Misc, *, can_send: bool, can_draft: bool):
|
||||
super().__init__(master)
|
||||
self.result: str | None = None
|
||||
self.title("E-Mail-Versand")
|
||||
self.transient(master.winfo_toplevel())
|
||||
self.resizable(False, False)
|
||||
frame = ttk.Frame(self, padding=18)
|
||||
frame.pack(fill="both", expand=True)
|
||||
ttk.Label(
|
||||
frame,
|
||||
text="Wie soll mit dieser E-Mail verfahren werden?",
|
||||
wraplength=360,
|
||||
justify="left",
|
||||
).pack(anchor="w", pady=(0, 14))
|
||||
buttons = ttk.Frame(frame)
|
||||
buttons.pack(anchor="e")
|
||||
ttk.Button(buttons, text="Abbrechen", command=self._cancel).pack(side="left", padx=(0, 8))
|
||||
send_button = ttk.Button(buttons, text="Senden", command=self._choose_send)
|
||||
send_button.pack(side="left", padx=(0, 8))
|
||||
send_button.configure(state="normal" if can_send else "disabled")
|
||||
draft_button = ttk.Button(
|
||||
buttons, text="Als Entwurf ablegen", style="Accent.TButton", command=self._choose_drafts
|
||||
)
|
||||
draft_button.pack(side="left")
|
||||
draft_button.configure(state="normal" if can_draft else "disabled")
|
||||
self.bind("<Escape>", lambda _event: self._cancel())
|
||||
self.protocol("WM_DELETE_WINDOW", self._cancel)
|
||||
self.after_idle(self.grab_set)
|
||||
|
||||
def _choose_send(self) -> None:
|
||||
self.result = "send"
|
||||
self.destroy()
|
||||
|
||||
def _choose_drafts(self) -> None:
|
||||
self.result = "drafts"
|
||||
self.destroy()
|
||||
|
||||
def _cancel(self) -> None:
|
||||
self.result = None
|
||||
self.destroy()
|
||||
|
||||
|
||||
def resolve_delivery_mode(master: tk.Misc, repository: MemberRepository) -> str | None:
|
||||
"""Returns the delivery mode to use for the next generated e-mail(s): "local",
|
||||
"send" or "drafts" -- or None if the board cancelled out of the "ask every time"
|
||||
prompt, in which case the caller should abort without generating anything."""
|
||||
settings = repository.get_email_settings()
|
||||
mode = settings["delivery_mode"]
|
||||
if mode in {"local", "send", "drafts"}:
|
||||
return mode
|
||||
dialog = EmailDeliveryChoiceDialog(
|
||||
master, can_send=bool(settings["smtp_host"]), can_draft=bool(settings["imap_host"])
|
||||
)
|
||||
master.wait_window(dialog)
|
||||
return dialog.result
|
||||
@@ -30,6 +30,19 @@ REMINDER_CHANNEL_LABELS = {
|
||||
"personal": "Persönlich",
|
||||
}
|
||||
|
||||
EMAIL_DELIVERY_MODE_LABELS = {
|
||||
"local": "Lokal speichern (bisheriges Verhalten)",
|
||||
"send": "Direkt versenden",
|
||||
"drafts": "Als Entwurf im Postfach ablegen",
|
||||
"ask": "Jedes Mal fragen",
|
||||
}
|
||||
|
||||
EMAIL_SECURITY_LABELS = {
|
||||
"starttls": "STARTTLS",
|
||||
"ssl": "SSL/TLS",
|
||||
"none": "Keine",
|
||||
}
|
||||
|
||||
|
||||
def display_label(labels: Mapping[str, str], key: str) -> str:
|
||||
return labels.get(key, key)
|
||||
|
||||
@@ -12,10 +12,17 @@ from ccma.services.intervals import (
|
||||
IntervalValidationError,
|
||||
normalize_anniversary_intervals,
|
||||
)
|
||||
from ccma.services.mail_delivery import test_imap_connection, test_smtp_connection
|
||||
from ccma.storage.repository import MemberRepository, RepositoryError, validate_member_number_pattern
|
||||
from ccma.ui.changelog_view import ChangelogView
|
||||
from ccma.ui.icons import IconStore
|
||||
from ccma.ui.labels import THEME_LABELS, display_label, storage_key
|
||||
from ccma.ui.labels import (
|
||||
EMAIL_DELIVERY_MODE_LABELS,
|
||||
EMAIL_SECURITY_LABELS,
|
||||
THEME_LABELS,
|
||||
display_label,
|
||||
storage_key,
|
||||
)
|
||||
|
||||
|
||||
class OptionsDialog(tk.Toplevel):
|
||||
@@ -94,6 +101,7 @@ class OptionsDialog(tk.Toplevel):
|
||||
organization = ttk.Frame(notebook, padding=16)
|
||||
automation = ttk.Frame(notebook, padding=16)
|
||||
reminders = ttk.Frame(notebook, padding=16)
|
||||
email = ttk.Frame(notebook, padding=16)
|
||||
changelog = ChangelogView(notebook)
|
||||
notebook.add(paths, text="Pfade")
|
||||
notebook.add(appearance, text="Darstellung")
|
||||
@@ -101,6 +109,7 @@ class OptionsDialog(tk.Toplevel):
|
||||
notebook.add(organization, text="Verein / Absender")
|
||||
notebook.add(automation, text="Hausmeister")
|
||||
notebook.add(reminders, text="Mahnungen")
|
||||
notebook.add(email, text="E-Mail-Versand")
|
||||
notebook.add(changelog, text="Changelog")
|
||||
self._build_paths(paths)
|
||||
self._build_appearance(appearance)
|
||||
@@ -108,6 +117,7 @@ class OptionsDialog(tk.Toplevel):
|
||||
self._build_organization(organization)
|
||||
self._build_automation(automation)
|
||||
self._build_reminders(reminders)
|
||||
self._build_email(email)
|
||||
|
||||
buttons = ttk.Frame(root)
|
||||
buttons.grid(row=1, column=0, sticky="e", pady=(12, 0))
|
||||
@@ -576,6 +586,167 @@ class OptionsDialog(tk.Toplevel):
|
||||
del self.standard_items[int(selected[0])]
|
||||
self._refresh_standard_items()
|
||||
|
||||
def _build_email(self, parent: ttk.Frame) -> None:
|
||||
parent.columnconfigure(0, weight=1)
|
||||
email_settings = self.repository.get_email_settings()
|
||||
|
||||
self.email_delivery_mode_var = tk.StringVar(
|
||||
value=display_label(EMAIL_DELIVERY_MODE_LABELS, email_settings["delivery_mode"])
|
||||
)
|
||||
ttk.Label(parent, text="Versandmodus").grid(row=0, column=0, sticky="w", pady=6)
|
||||
ttk.Combobox(
|
||||
parent,
|
||||
textvariable=self.email_delivery_mode_var,
|
||||
values=list(EMAIL_DELIVERY_MODE_LABELS.values()),
|
||||
state="readonly",
|
||||
width=42,
|
||||
).grid(row=0, column=1, sticky="w", pady=6)
|
||||
ttk.Label(
|
||||
parent,
|
||||
text=(
|
||||
"Gilt für Mahnungs- und SEPA-Info-Mails. „Jedes Mal fragen“ zeigt vor jeder "
|
||||
"Erzeugung eine Auswahl zwischen Senden und Entwurf ablegen."
|
||||
),
|
||||
style="Muted.TLabel",
|
||||
wraplength=680,
|
||||
).grid(row=1, column=0, columnspan=2, sticky="w", pady=(0, 12))
|
||||
|
||||
smtp_frame = ttk.LabelFrame(parent, text="SMTP (zum direkten Versenden)", padding=10)
|
||||
smtp_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(4, 0))
|
||||
smtp_frame.columnconfigure(1, weight=1)
|
||||
self.smtp_host_var = tk.StringVar(value=email_settings["smtp_host"])
|
||||
self.smtp_port_var = tk.StringVar(value=str(email_settings["smtp_port"]))
|
||||
self.smtp_security_var = tk.StringVar(
|
||||
value=display_label(EMAIL_SECURITY_LABELS, email_settings["smtp_security"])
|
||||
)
|
||||
self.smtp_username_var = tk.StringVar(value=email_settings["smtp_username"])
|
||||
self.smtp_password_var = tk.StringVar(value=email_settings["smtp_password"])
|
||||
ttk.Label(smtp_frame, text="Server").grid(row=0, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(smtp_frame, textvariable=self.smtp_host_var).grid(
|
||||
row=0, column=1, sticky="ew", pady=4, padx=(0, 8)
|
||||
)
|
||||
ttk.Label(smtp_frame, text="Port").grid(row=0, column=2, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(smtp_frame, textvariable=self.smtp_port_var, width=8).grid(row=0, column=3, pady=4)
|
||||
ttk.Label(smtp_frame, text="Verschlüsselung").grid(
|
||||
row=1, column=0, sticky="w", pady=4, padx=(0, 8)
|
||||
)
|
||||
ttk.Combobox(
|
||||
smtp_frame,
|
||||
textvariable=self.smtp_security_var,
|
||||
values=list(EMAIL_SECURITY_LABELS.values()),
|
||||
state="readonly",
|
||||
width=14,
|
||||
).grid(row=1, column=1, sticky="w", pady=4)
|
||||
ttk.Label(smtp_frame, text="Benutzername").grid(row=2, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(smtp_frame, textvariable=self.smtp_username_var).grid(
|
||||
row=2, column=1, columnspan=3, sticky="ew", pady=4
|
||||
)
|
||||
ttk.Label(smtp_frame, text="Passwort").grid(row=3, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(smtp_frame, textvariable=self.smtp_password_var, show="•").grid(
|
||||
row=3, column=1, columnspan=3, sticky="ew", pady=4
|
||||
)
|
||||
ttk.Button(smtp_frame, text="Verbindung testen", command=self._test_smtp_connection).grid(
|
||||
row=4, column=0, columnspan=4, sticky="w", pady=(8, 0)
|
||||
)
|
||||
|
||||
imap_frame = ttk.LabelFrame(parent, text="IMAP (für die Ablage als Entwurf)", padding=10)
|
||||
imap_frame.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(16, 0))
|
||||
imap_frame.columnconfigure(1, weight=1)
|
||||
self.imap_host_var = tk.StringVar(value=email_settings["imap_host"])
|
||||
self.imap_port_var = tk.StringVar(value=str(email_settings["imap_port"]))
|
||||
self.imap_security_var = tk.StringVar(
|
||||
value=display_label(EMAIL_SECURITY_LABELS, email_settings["imap_security"])
|
||||
)
|
||||
self.imap_username_var = tk.StringVar(value=email_settings["imap_username"])
|
||||
self.imap_password_var = tk.StringVar(value=email_settings["imap_password"])
|
||||
self.imap_drafts_folder_var = tk.StringVar(value=email_settings["imap_drafts_folder"])
|
||||
ttk.Label(imap_frame, text="Server").grid(row=0, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(imap_frame, textvariable=self.imap_host_var).grid(
|
||||
row=0, column=1, sticky="ew", pady=4, padx=(0, 8)
|
||||
)
|
||||
ttk.Label(imap_frame, text="Port").grid(row=0, column=2, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(imap_frame, textvariable=self.imap_port_var, width=8).grid(row=0, column=3, pady=4)
|
||||
ttk.Label(imap_frame, text="Verschlüsselung").grid(
|
||||
row=1, column=0, sticky="w", pady=4, padx=(0, 8)
|
||||
)
|
||||
ttk.Combobox(
|
||||
imap_frame,
|
||||
textvariable=self.imap_security_var,
|
||||
values=list(EMAIL_SECURITY_LABELS.values()),
|
||||
state="readonly",
|
||||
width=14,
|
||||
).grid(row=1, column=1, sticky="w", pady=4)
|
||||
ttk.Label(imap_frame, text="Benutzername").grid(row=2, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(imap_frame, textvariable=self.imap_username_var).grid(
|
||||
row=2, column=1, columnspan=3, sticky="ew", pady=4
|
||||
)
|
||||
ttk.Label(imap_frame, text="Passwort").grid(row=3, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(imap_frame, textvariable=self.imap_password_var, show="•").grid(
|
||||
row=3, column=1, columnspan=3, sticky="ew", pady=4
|
||||
)
|
||||
ttk.Label(imap_frame, text="Entwürfe-Ordner").grid(row=4, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(imap_frame, textvariable=self.imap_drafts_folder_var).grid(
|
||||
row=4, column=1, columnspan=3, sticky="ew", pady=4
|
||||
)
|
||||
ttk.Button(imap_frame, text="Verbindung testen", command=self._test_imap_connection).grid(
|
||||
row=5, column=0, columnspan=4, sticky="w", pady=(8, 0)
|
||||
)
|
||||
|
||||
ttk.Label(
|
||||
parent,
|
||||
text=(
|
||||
"Server, Zugangsdaten und Passwörter werden mit den übrigen Vereinseinstellungen "
|
||||
"in repository.json innerhalb dieses Mitglieder-Stores gespeichert -- pro Store "
|
||||
"lässt sich so ein eigenes Postfach hinterlegen."
|
||||
),
|
||||
style="Muted.TLabel",
|
||||
wraplength=680,
|
||||
).grid(row=4, column=0, columnspan=2, sticky="w", pady=(12, 0))
|
||||
|
||||
def _collect_email_settings(self) -> dict:
|
||||
return {
|
||||
"delivery_mode": storage_key(EMAIL_DELIVERY_MODE_LABELS, self.email_delivery_mode_var.get()),
|
||||
"smtp_host": self.smtp_host_var.get(),
|
||||
"smtp_port": self.smtp_port_var.get(),
|
||||
"smtp_security": storage_key(EMAIL_SECURITY_LABELS, self.smtp_security_var.get()),
|
||||
"smtp_username": self.smtp_username_var.get(),
|
||||
"smtp_password": self.smtp_password_var.get(),
|
||||
"imap_host": self.imap_host_var.get(),
|
||||
"imap_port": self.imap_port_var.get(),
|
||||
"imap_security": storage_key(EMAIL_SECURITY_LABELS, self.imap_security_var.get()),
|
||||
"imap_username": self.imap_username_var.get(),
|
||||
"imap_password": self.imap_password_var.get(),
|
||||
"imap_drafts_folder": self.imap_drafts_folder_var.get(),
|
||||
}
|
||||
|
||||
def _test_smtp_connection(self) -> None:
|
||||
settings = self._collect_email_settings()
|
||||
try:
|
||||
settings["smtp_port"] = int(settings["smtp_port"])
|
||||
except ValueError:
|
||||
messagebox.showerror("Ungültiger Port", "Der SMTP-Port muss eine ganze Zahl sein.", parent=self)
|
||||
return
|
||||
try:
|
||||
test_smtp_connection(settings)
|
||||
except RepositoryError as exc:
|
||||
messagebox.showerror("SMTP-Verbindung fehlgeschlagen", str(exc), parent=self)
|
||||
return
|
||||
messagebox.showinfo("SMTP-Verbindung", "Verbindung und Anmeldung erfolgreich.", parent=self)
|
||||
|
||||
def _test_imap_connection(self) -> None:
|
||||
settings = self._collect_email_settings()
|
||||
try:
|
||||
settings["imap_port"] = int(settings["imap_port"])
|
||||
except ValueError:
|
||||
messagebox.showerror("Ungültiger Port", "Der IMAP-Port muss eine ganze Zahl sein.", parent=self)
|
||||
return
|
||||
try:
|
||||
test_imap_connection(settings)
|
||||
except RepositoryError as exc:
|
||||
messagebox.showerror("IMAP-Verbindung fehlgeschlagen", str(exc), parent=self)
|
||||
return
|
||||
messagebox.showinfo("IMAP-Verbindung", "Verbindung und Anmeldung erfolgreich.", parent=self)
|
||||
|
||||
def _choose_store(self) -> None:
|
||||
current = Path(self.store_var.get()).expanduser() if self.store_var.get().strip() else Path.home()
|
||||
initial = current if current.is_dir() else current.parent
|
||||
@@ -621,6 +792,15 @@ class OptionsDialog(tk.Toplevel):
|
||||
except (ValueError, IntervalValidationError) as exc:
|
||||
messagebox.showerror("Ungültige Hausmeister-Einstellung", str(exc), parent=self)
|
||||
return
|
||||
email_settings = self._collect_email_settings()
|
||||
try:
|
||||
email_settings["smtp_port"] = int(email_settings["smtp_port"])
|
||||
email_settings["imap_port"] = int(email_settings["imap_port"])
|
||||
except ValueError:
|
||||
messagebox.showerror(
|
||||
"Ungültige E-Mail-Einstellung", "SMTP-/IMAP-Port müssen ganze Zahlen sein.", parent=self
|
||||
)
|
||||
return
|
||||
store_text = self.store_var.get().strip()
|
||||
if not store_text:
|
||||
messagebox.showerror(
|
||||
@@ -685,6 +865,7 @@ class OptionsDialog(tk.Toplevel):
|
||||
levels=self.levels,
|
||||
standard_fee_items=self.standard_items,
|
||||
)
|
||||
self.repository.save_email_settings(**email_settings)
|
||||
except (OSError, RepositoryError) as exc:
|
||||
messagebox.showerror("Optionen konnten nicht gespeichert werden", str(exc), parent=self)
|
||||
return
|
||||
|
||||
+22
-12
@@ -10,6 +10,7 @@ from ccma.domain.dates import DateValidationError, normalize_date_input
|
||||
from ccma.services.sepa import pending_direct_debits, write_csv, write_pain008
|
||||
from ccma.services.sepa_mail import generate_debit_mails
|
||||
from ccma.storage.repository import MemberRepository, RepositoryError
|
||||
from ccma.ui.email_delivery_dialog import resolve_delivery_mode
|
||||
|
||||
|
||||
def _next_weekday(selected: date) -> date:
|
||||
@@ -277,26 +278,33 @@ class SepaMailExportDialog(tk.Toplevel):
|
||||
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left")
|
||||
ttk.Button(
|
||||
buttons,
|
||||
text="Ordner wählen und Entwürfe erzeugen",
|
||||
text="Info-Mails erzeugen",
|
||||
style="Accent.TButton",
|
||||
command=self._generate,
|
||||
).pack(side="left", padx=(8, 0))
|
||||
self.grab_set()
|
||||
|
||||
def _generate(self) -> None:
|
||||
selected = filedialog.askdirectory(
|
||||
parent=self,
|
||||
title="Ausgabeordner für Thunderbird-Entwürfe auswählen",
|
||||
mustexist=True,
|
||||
)
|
||||
if not selected:
|
||||
delivery_mode = resolve_delivery_mode(self, self.repository)
|
||||
if delivery_mode is None:
|
||||
return
|
||||
output_directory = None
|
||||
if delivery_mode == "local":
|
||||
selected = filedialog.askdirectory(
|
||||
parent=self,
|
||||
title="Ausgabeordner für Thunderbird-Entwürfe auswählen",
|
||||
mustexist=True,
|
||||
)
|
||||
if not selected:
|
||||
return
|
||||
output_directory = selected
|
||||
try:
|
||||
generated, warnings = generate_debit_mails(
|
||||
self.repository,
|
||||
self.debits,
|
||||
collection_date=self.collection_date,
|
||||
output_directory=selected,
|
||||
delivery_mode=delivery_mode,
|
||||
output_directory=output_directory,
|
||||
sender_name=self.sender_name_var.get(),
|
||||
sender_email=self.sender_email_var.get(),
|
||||
signature=self.signature.get("1.0", "end-1c"),
|
||||
@@ -304,10 +312,12 @@ class SepaMailExportDialog(tk.Toplevel):
|
||||
except (OSError, RepositoryError) as exc:
|
||||
messagebox.showerror("Info-Mails konnten nicht erzeugt werden", str(exc), parent=self)
|
||||
return
|
||||
detail = (
|
||||
f"{len(generated)} Entwurf/Entwürfe wurden in\n{selected}\nerzeugt und in den "
|
||||
"Mitgliederakten archiviert."
|
||||
)
|
||||
location = {
|
||||
"local": f"in\n{output_directory}\nerzeugt",
|
||||
"send": "direkt versandt",
|
||||
"drafts": "als Entwurf im Postfach abgelegt",
|
||||
}[delivery_mode]
|
||||
detail = f"{len(generated)} Info-Mail(s) wurden {location} und in den Mitgliederakten archiviert."
|
||||
if warnings:
|
||||
detail += "\n\nÜbersprungen:\n" + "\n".join(warnings[:12])
|
||||
messagebox.showinfo("Info-Mails erzeugt", detail, parent=self)
|
||||
|
||||
Reference in New Issue
Block a user