KI-AGENT: Empfängerauswahl beim Mailversand erweitern

This commit is contained in:
2026-09-07 20:31:20 +02:00
parent 36969e272d
commit 8d8b07ce0d

View File

@@ -1,6 +1,4 @@
<script setup>
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
@@ -22,6 +20,10 @@ const loadedDocuments = ref([])
const loaded = ref(false)
const noAccountsPresent = ref(false)
const sourceMessage = ref(null)
const recipientOptions = ref([])
const selectedRecipient = ref(null)
const loadingRecipients = ref(false)
const preferredCustomerId = ref(null)
const composeMode = computed(() => {
const mode = String(route.query.mode || "")
return ["reply", "replyAll", "forward"].includes(mode) ? mode : "new"
@@ -46,6 +48,160 @@ const formatAddress = (address) => {
const formatAddressList = (addresses = []) => addresses.map(formatAddress).filter(Boolean).join(", ")
const normalizeEmail = (value = "") => {
const text = String(value).trim()
const bracketMatch = text.match(/<([^<>]+)>/)
return String(bracketMatch?.[1] || text.split(/[;,]/)[0] || "").trim().toLowerCase()
}
const parseInfoData = (value) => {
if (!value) return {}
if (typeof value === "object") return value
try {
return JSON.parse(value)
} catch {
return {}
}
}
const addRecipientOption = (target, option) => {
const email = normalizeEmail(option.email)
if (!email) return
const existing = target.find(item => item.email === email)
if (existing) {
if (option.priority < existing.priority) Object.assign(existing, option, { email })
return
}
target.push({ ...option, email })
}
const syncSelectedRecipient = () => {
const currentValue = String(emailData.value.to || "").trim()
if (!currentValue) {
selectedRecipient.value = null
return
}
const currentEmail = normalizeEmail(currentValue)
const matchingOption = recipientOptions.value.find(option => option.email === currentEmail)
if (matchingOption && (currentValue.match(/@/g) || []).length === 1) {
selectedRecipient.value = matchingOption.value
return
}
const currentOption = {
label: `Aktuelle Empfänger · ${currentValue}`,
value: currentValue,
email: currentEmail,
priority: -2,
}
recipientOptions.value = [
currentOption,
...recipientOptions.value.filter(option => option.value !== currentValue),
]
selectedRecipient.value = currentValue
}
const loadRecipientOptions = async () => {
loadingRecipients.value = true
try {
const [customers, contacts] = await Promise.all([
useEntities("customers").select("*", "name", true),
useEntities("contacts").select("*, customer(id,name)", "fullName", true),
])
const options = []
const customerNames = new Map(customers.map(customer => [Number(customer.id), customer.name]))
customers
.filter(customer => customer.active !== false)
.forEach(customer => {
const infoData = parseInfoData(customer.infoData)
const isPreferred = Number(customer.id) === Number(preferredCustomerId.value)
const priority = isPreferred ? 0 : 20
addRecipientOption(options, {
email: infoData.email,
value: infoData.email,
label: `${isPreferred ? "Passender Kunde" : "Kunde"} · ${customer.name} · ${infoData.email || ""}`,
priority,
customerId: Number(customer.id),
kind: "customer",
})
addRecipientOption(options, {
email: infoData.invoiceEmail,
value: infoData.invoiceEmail,
label: `${isPreferred ? "Passender Kunde" : "Kunde"} · ${customer.name} · Rechnung · ${infoData.invoiceEmail || ""}`,
priority: priority + 1,
customerId: Number(customer.id),
kind: "invoice",
})
})
contacts
.filter(contact => contact.active !== false && contact.email)
.forEach(contact => {
const customerId = Number(contact.customer?.id || contact.customer || 0)
const customerName = contact.customer?.name || customerNames.get(customerId)
const contactName = contact.fullName
|| [contact.firstName, contact.lastName].filter(Boolean).join(" ")
|| contact.email
const isPreferred = customerId && customerId === Number(preferredCustomerId.value)
addRecipientOption(options, {
email: contact.email,
value: formatAddress({ name: contactName, address: contact.email }),
label: [
isPreferred ? "Passender Ansprechpartner" : "Ansprechpartner",
contactName,
customerName,
contact.email,
].filter(Boolean).join(" · "),
priority: isPreferred ? 2 : 30,
customerId,
kind: "contact",
})
})
if (!preferredCustomerId.value) {
const currentEmail = normalizeEmail(emailData.value.to)
preferredCustomerId.value = options.find(option => option.email === currentEmail)?.customerId || null
}
if (preferredCustomerId.value) {
options.forEach(option => {
if (Number(option.customerId) !== Number(preferredCustomerId.value)) return
option.priority = option.kind === "customer" ? 0 : option.kind === "invoice" ? 1 : 2
option.label = option.label
.replace(/^Kunde ·/, "Passender Kunde ·")
.replace(/^Ansprechpartner ·/, "Passender Ansprechpartner ·")
})
}
recipientOptions.value = options.sort((first, second) =>
first.priority - second.priority || first.label.localeCompare(second.label, "de")
)
syncSelectedRecipient()
} catch (err) {
recipientOptions.value = []
syncSelectedRecipient()
toast.add({
title: "Adressbuch konnte nicht geladen werden",
description: err?.data?.error || err?.message,
color: "warning",
})
} finally {
loadingRecipients.value = false
}
}
const selectRecipient = (value) => {
if (!value) return
emailData.value.to = value
}
const prefixedSubject = (prefix, subject = "") => {
const cleanSubject = String(subject || "")
return cleanSubject.toLowerCase().startsWith(prefix.toLowerCase()) ? cleanSubject : `${prefix} ${cleanSubject}`.trim()
@@ -124,38 +280,50 @@ const setupPage = async () => {
if(route.query.loadDocuments) {
console.log(JSON.parse(route.query.loadDocuments))
const data = await useFiles().selectSomeDocuments(JSON.parse(route.query.loadDocuments))
console.log(data)
if(data) loadedDocuments.value = data
loadedDocuments.value = await Promise.all(loadedDocuments.value.map(async doc => {
const document = await useEntities("createddocuments").selectSingle(doc.createddocument)
console.log(document)
return {
...doc,
createddocument: document
}}))
//console.log(loadedDocuments.value)
if(loadedDocuments.value.length > 0) {
console.log(loadedDocuments.value[0])
emailData.value.subject = `${loadedDocuments.value[0].createddocument.title} von ${auth.activeTenantData.businessInfo.name}`
const createdDocument = loadedDocuments.value[0].createddocument
const customerId = Number(createdDocument.customer?.id || createdDocument.customer || 0) || null
const contactId = Number(createdDocument.contact?.id || createdDocument.contact || 0) || null
const customer = createdDocument.customer && typeof createdDocument.customer === "object"
? createdDocument.customer
: customerId
? await useEntities("customers").selectSingle(customerId)
: null
const contact = createdDocument.contact && typeof createdDocument.contact === "object"
? createdDocument.contact
: contactId
? await useEntities("contacts").selectSingle(contactId)
: null
if(loadedDocuments.value[0].createddocument.contact && loadedDocuments.value[0].createddocument.contact.email) {
console.log("Contact")
emailData.value.to = loadedDocuments.value[0].createddocument.contact.email
} else if(loadedDocuments.value[0].createddocument.customer && loadedDocuments.value[0].createddocument.customer.infoData.invoiceEmail) {
emailData.value.to = loadedDocuments.value[0].createddocument.customer.infoData.invoiceEmail
} else if(loadedDocuments.value[0].createddocument.customer && loadedDocuments.value[0].createddocument.customer.infoData.email) {
emailData.value.to = loadedDocuments.value[0].createddocument.customer.infoData.email
preferredCustomerId.value = customerId || Number(contact?.customer?.id || contact?.customer || 0) || null
emailData.value.subject = `${createdDocument.title} von ${auth.activeTenantData.businessInfo.name}`
if(contact?.email) {
emailData.value.to = formatAddress({
name: contact.fullName,
address: contact.email,
})
} else if(parseInfoData(customer?.infoData).invoiceEmail) {
emailData.value.to = parseInfoData(customer.infoData).invoiceEmail
} else if(parseInfoData(customer?.infoData).email) {
emailData.value.to = parseInfoData(customer.infoData).email
}
}
}
await loadRecipientOptions()
loaded.value = true
}
@@ -311,10 +479,28 @@ const sendEmail = async () => {
<UFormField
label="Empfänger"
>
<UInput
class="w-full my-1"
v-model="emailData.to"
<div class="grid gap-2 md:grid-cols-[minmax(260px,0.9fr)_minmax(320px,1.1fr)]">
<USelectMenu
v-model="selectedRecipient"
:items="recipientOptions"
value-key="value"
label-key="label"
:loading="loadingRecipients"
:search-input="{ placeholder: 'Kunde, Ansprechpartner oder E-Mail suchen' }"
placeholder="Aus Kunden und Ansprechpartnern wählen"
class="w-full"
@update:model-value="selectRecipient"
/>
<UInput
class="w-full"
v-model="emailData.to"
placeholder="E-Mail-Adresse oder mehrere Adressen"
@update:model-value="selectedRecipient = null"
/>
</div>
<p class="mt-1 text-xs text-dimmed">
Passende Adressen zum vorausgefüllten Kunden werden zuerst angezeigt. Freie Eingaben bleiben möglich.
</p>
</UFormField>
<UFormField
label="Kopie"