KI-AGENT: Empfängerauswahl beim Mailversand erweitern
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user