Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
2026-07-22 19:10:11 +02:00
29 changed files with 1462 additions and 190 deletions

View File

@@ -91,6 +91,7 @@ const selectedProductcategorie = ref(null)
const services = ref([])
const servicecategories = ref([])
const selectedServicecategorie = ref(null)
const collapsedTitleRowIds = ref(new Set())
const customers = ref([])
const contacts = ref([])
const contracts = ref([])
@@ -935,6 +936,45 @@ const getPositionAddMenuItems = (row) => [
}))
]
const toggleTitleSection = (rowId) => {
const nextCollapsedTitleRowIds = new Set(collapsedTitleRowIds.value)
if (nextCollapsedTitleRowIds.has(rowId)) {
nextCollapsedTitleRowIds.delete(rowId)
} else {
nextCollapsedTitleRowIds.add(rowId)
}
collapsedTitleRowIds.value = nextCollapsedTitleRowIds
}
const hiddenDocumentRowIds = computed(() => {
const hiddenRowIds = new Set()
const collapsedTitleLevels = []
itemInfo.value.rows.forEach((row) => {
if (row.mode === 'title') {
const titleLevel = Math.min(Math.max(Number(row.titleLevel) || 1, 1), 3)
while (
collapsedTitleLevels.length > 0
&& collapsedTitleLevels[collapsedTitleLevels.length - 1] >= titleLevel
) {
collapsedTitleLevels.pop()
}
if (collapsedTitleLevels.length > 0) hiddenRowIds.add(row.id)
if (collapsedTitleRowIds.value.has(row.id)) collapsedTitleLevels.push(titleLevel)
} else if (collapsedTitleLevels.length > 0) {
hiddenRowIds.add(row.id)
}
})
return hiddenRowIds
})
const isDocumentRowHidden = (rowId) => hiddenDocumentRowIds.value.has(rowId)
const getFullWidthPositionColspan = () => deliveryNoteLikeDocumentTypes.includes(itemInfo.value.type) ? 5 : 8
const getTitlePositionColspan = () => deliveryNoteLikeDocumentTypes.includes(itemInfo.value.type) ? 4 : 7
@@ -2897,13 +2937,28 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
@end="setPosNumbers"
>
<template #item="{element: row}">
<tr>
<tr :class="{ collapse: isDocumentRowHidden(row.id) }">
<td>
<UIcon
class="handle"
name="i-mdi-menu"
v-if="itemInfo.type !== 'cancellationInvoices'"
/>
<div class="flex items-center gap-1 whitespace-nowrap">
<UIcon
class="handle shrink-0"
name="i-mdi-menu"
v-if="itemInfo.type !== 'cancellationInvoices'"
/>
<UTooltip
v-if="row.mode === 'title'"
:text="collapsedTitleRowIds.has(row.id) ? 'Titelbereich ausklappen' : 'Titelbereich einklappen'"
>
<UButton
class="shrink-0"
variant="ghost"
color="neutral"
:icon="collapsedTitleRowIds.has(row.id) ? 'i-heroicons-chevron-right' : 'i-heroicons-chevron-down'"
:aria-label="collapsedTitleRowIds.has(row.id) ? 'Titelbereich ausklappen' : 'Titelbereich einklappen'"
@click="toggleTitleSection(row.id)"
/>
</UTooltip>
</div>
</td>
<td
v-if="row.mode === 'pagebreak'"

View File

@@ -149,6 +149,18 @@ const bankBookingDateLabel = computed(() => {
return bankBookingDates.value.map(formatDate).join(", ")
})
const vendorName = computed(() => vendors.value.find((vendor) => vendor.id === itemInfo.value.vendor)?.name || "-")
const eInvoiceValidation = computed(() => itemInfo.value.eInvoiceValidation || null)
const eInvoiceSourceLabel = computed(() => {
if (itemInfo.value.preparationSource !== "e-invoice") return null
if (itemInfo.value.eInvoiceSyntax === "cii") {
return String(itemInfo.value.eInvoiceProfile || "").toLowerCase().includes("xrechnung")
? "XRechnung (CII)"
: "ZUGFeRD / Factur-X (CII)"
}
if (itemInfo.value.eInvoiceSyntax === "ubl-credit-note") return "XRechnung / UBL-Gutschrift"
return "XRechnung / UBL"
})
const eInvoiceProfileLabel = computed(() => itemInfo.value.eInvoiceProfile || "Profil nicht angegeben")
const getAccountLabel = (item) => {
const account = accounts.value.find((entry) => entry.id === item.account)
@@ -356,6 +368,35 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
Dokument andocken
</UButton>
<UAlert
v-if="eInvoiceSourceLabel"
:title="eInvoiceSourceLabel"
color="primary"
variant="soft"
icon="i-heroicons-document-check"
>
<template #description>
<div class="mt-1 space-y-2 text-sm">
<p class="break-all">{{ eInvoiceProfileLabel }}</p>
<div v-if="eInvoiceValidation?.errors?.length">
<p class="font-semibold">Validierungsfehler</p>
<ul class="list-inside list-disc">
<li v-for="error in eInvoiceValidation.errors" :key="error">{{ error }}</li>
</ul>
</div>
<div v-if="eInvoiceValidation?.warnings?.length">
<p class="font-semibold">Hinweise</p>
<ul class="list-inside list-disc">
<li v-for="warning in eInvoiceValidation.warnings" :key="warning">{{ warning }}</li>
</ul>
</div>
<p v-if="!eInvoiceValidation?.errors?.length && !eInvoiceValidation?.warnings?.length">
Pflichtfelder und Rechnungssummen wurden erfolgreich geprüft.
</p>
</div>
</template>
</UAlert>
<UAlert
v-if="mode !== 'show' && findIncomingInvoiceErrors.length > 0"
title="Prüfung erforderlich"

View File

@@ -287,6 +287,18 @@ const selectIncomingInvoice = (invoiceLike) => {
<span v-if="row.original.state === 'Vorbereitet'" class="text-cyan-500">{{row.original.state}}</span>
<span v-else-if="row.original.state === 'Entwurf'" class="text-red-500">{{row.original.state}}</span>
<span v-else-if="row.original.state === 'Gebucht'" class="text-primary-500">{{row.original.state}}</span>
<span v-else-if="row.original.state === 'Prüfung erforderlich'" class="text-orange-500">{{row.original.state}}</span>
<span v-else>{{row.original.state}}</span>
</template>
<template #preparationSource-cell="{row}">
<UBadge v-if="row.original.preparationSource === 'e-invoice'" color="primary" variant="soft">
{{ row.original.eInvoiceSyntax === 'cii'
? (String(row.original.eInvoiceProfile || '').toLowerCase().includes('xrechnung') ? 'XRechnung / CII' : 'ZUGFeRD / CII')
: 'XRechnung / UBL' }}
</UBadge>
<UBadge v-else-if="row.original.preparationSource === 'gpt'" color="neutral" variant="soft">
PDF / KI
</UBadge>
</template>
<template #date-cell="{row}">
{{dayjs(row.original.date).format("DD.MM.YYYY")}}

View File

@@ -11,7 +11,8 @@ const router = useRouter()
const state = reactive({
email: '',
password: ''
password: '',
rememberMe: true
})
const loading = ref(false)
@@ -19,7 +20,7 @@ const loading = ref(false)
const doLogin = async (event: FormSubmitEvent<typeof state>) => {
loading.value = true
try {
await auth.login(event.data.email, event.data.password)
await auth.login(event.data.email, event.data.password, event.data.rememberMe)
toast.add({title:"Einloggen erfolgreich"})
await router.push("/")
} catch (err: any) {
@@ -79,6 +80,12 @@ const doLogin = async (event: FormSubmitEvent<typeof state>) => {
/>
</UFormField>
<UCheckbox
v-model="state.rememberMe"
name="rememberMe"
label="Angemeldet bleiben"
/>
<UButton type="submit" block class="w-full" :loading="loading">
Weiter
</UButton>