601 lines
27 KiB
Vue
601 lines
27 KiB
Vue
<script setup>
|
|
import dayjs from "dayjs";
|
|
// import {filter} from "vuedraggable/dist/vuedraggable.common.js"; // Scheint nicht genutzt zu werden, auskommentiert
|
|
|
|
defineShortcuts({
|
|
'backspace': () => {
|
|
router.push("/banking")
|
|
}
|
|
})
|
|
|
|
const dataStore = useDataStore()
|
|
const tempStore = useTempStore()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const mode = ref(route.params.mode || "show")
|
|
|
|
const itemInfo = ref({statementallocations: []})
|
|
const oldItemInfo = ref({})
|
|
|
|
const openDocuments = ref([])
|
|
const allocatedDocuments = ref([])
|
|
const openIncomingInvoices = ref([])
|
|
const allocatedIncomingInvoices = ref([])
|
|
|
|
const customers = ref([])
|
|
const vendors = ref([])
|
|
|
|
const createddocuments = ref([])
|
|
const incominginvoices = ref([])
|
|
const accounts = ref([])
|
|
const ownaccounts = ref([])
|
|
|
|
const loading = ref(true)
|
|
|
|
const setup = async () => {
|
|
loading.value = true
|
|
if (route.params.id) {
|
|
itemInfo.value = await useEntities("bankstatements").selectSingle(route.params.id, "*, statementallocations(*, cd_id(*), ii_id(*))", undefined, undefined, true)
|
|
|
|
console.log(itemInfo.value)
|
|
}
|
|
if (itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
|
|
|
manualAllocationSum.value = calculateOpenSum.value
|
|
|
|
createddocuments.value = (await useEntities("createddocuments").select("*, statementallocations(*), customer(id,name)"))
|
|
const documents = createddocuments.value.filter(i => i.type === "invoices" || i.type === "advanceInvoices")
|
|
incominginvoices.value = (await useEntities("incominginvoices").select("*, statementallocations(*), vendor(id,name)")).filter(i => i.state === "Gebucht")
|
|
|
|
accounts.value = (await useEntities("accounts").selectSpecial("*", "number", true))
|
|
ownaccounts.value = (await useEntities("ownaccounts").select())
|
|
customers.value = (await useEntities("customers").select())
|
|
vendors.value = (await useEntities("vendors").select())
|
|
|
|
openDocuments.value = documents.filter(i => i.statementallocations.reduce((n, {amount}) => n + amount, 0).toFixed(2) !== useSum().getCreatedDocumentSum(i, createddocuments.value).toFixed(2))
|
|
openDocuments.value = openDocuments.value.map(i => {
|
|
return {
|
|
...i,
|
|
docTotal: useSum().getCreatedDocumentSum(i, createddocuments.value),
|
|
statementTotal: Number(i.statementallocations.reduce((n, {amount}) => n + amount, 0)),
|
|
openSum: (useSum().getCreatedDocumentSum(i, createddocuments.value) - Number(i.statementallocations.reduce((n, {amount}) => n + amount, 0))).toFixed(2)
|
|
}
|
|
})
|
|
|
|
allocatedDocuments.value = documents.filter(i => i.statementallocations.find(x => x.bankstatement === itemInfo.value.id))
|
|
allocatedIncomingInvoices.value = incominginvoices.value.filter(i => i.statementallocations.find(x => x.bankstatement === itemInfo.value.id))
|
|
|
|
openIncomingInvoices.value = (await useEntities("incominginvoices").select("*, statementallocations(*), vendor(*)")).filter(i => !i.archived && i.statementallocations.reduce((n, {amount}) => n + amount, 0).toFixed(2) !== getInvoiceSum(i, false))
|
|
|
|
loading.value = false
|
|
}
|
|
|
|
const displayCurrency = (value, currency = "€") => {
|
|
return `${Number(value).toFixed(2).replace(".", ",")} ${currency}`
|
|
}
|
|
|
|
// Angepasst: Prüft nun auch auf null/undefined, nicht nur auf leeren String
|
|
const separateIBAN = (input) => {
|
|
if (!input) return ""
|
|
const separates = input.toString().match(/.{1,4}/g)
|
|
return separates ? separates.join(" ") : input
|
|
}
|
|
|
|
const getInvoiceSum = (invoice, onlyOpenSum) => {
|
|
let sum = 0
|
|
if (invoice.accounts) {
|
|
invoice.accounts.forEach(account => {
|
|
sum += (account.amountTax || 0)
|
|
sum += (account.amountNet || 0)
|
|
})
|
|
}
|
|
|
|
if (onlyOpenSum) sum = sum + Number(invoice.statementallocations.reduce((n, {amount}) => n + amount, 0))
|
|
|
|
if (invoice.expense) {
|
|
return (sum * -1).toFixed(2)
|
|
} else {
|
|
return sum.toFixed(2)
|
|
}
|
|
}
|
|
|
|
const calculateOpenSum = computed(() => {
|
|
let startingAmount = 0
|
|
if (itemInfo.value.statementallocations) {
|
|
itemInfo.value.statementallocations.forEach(item => {
|
|
startingAmount += item.amount
|
|
})
|
|
}
|
|
return (itemInfo.value.amount - startingAmount).toFixed(2)
|
|
})
|
|
|
|
const showAccountSelection = ref(false)
|
|
const accountToSave = ref("")
|
|
const ownAccountToSave = ref("")
|
|
const customerAccountToSave = ref("")
|
|
const vendorAccountToSave = ref("")
|
|
|
|
const selectAccount = (id) => {
|
|
accountToSave.value = id
|
|
showAccountSelection.value = false
|
|
}
|
|
|
|
const manualAllocationSum = ref(itemInfo.value.amount || 0)
|
|
const allocationDescription = ref("")
|
|
const showMoreWithoutRecipe = ref(false)
|
|
const showMoreText = ref(false)
|
|
|
|
const saveAllocation = async (allocation) => {
|
|
const res = await useNuxtApp().$api("/api/banking/statements", {
|
|
method: "POST",
|
|
body: {data: allocation}
|
|
})
|
|
|
|
if (res) {
|
|
await setup()
|
|
accountToSave.value = null
|
|
vendorAccountToSave.value = null
|
|
customerAccountToSave.value = null
|
|
ownAccountToSave.value = null
|
|
// allocationDescription.value = null // Optional: Beschreibung behalten für nächste Buchung?
|
|
}
|
|
}
|
|
|
|
const removeAllocation = async (allocationId) => {
|
|
await useNuxtApp().$api(`/api/banking/statements/${allocationId}`, {
|
|
method: "DELETE"
|
|
})
|
|
await setup()
|
|
}
|
|
|
|
const searchString = ref(tempStore.searchStrings["bankstatementsedit"] || '')
|
|
|
|
const clearSearchString = () => {
|
|
searchString.value = ''
|
|
tempStore.clearSearchString("bankstatementsedit")
|
|
}
|
|
|
|
const filteredDocuments = computed(() => {
|
|
return useSearch(searchString.value, openDocuments.value.filter(i => i.state === "Gebucht"))
|
|
})
|
|
|
|
const filteredIncomingInvoices = computed(() => {
|
|
return useSearch(searchString.value, openIncomingInvoices.value.filter(i => i.state === "Gebucht"))
|
|
})
|
|
|
|
const archiveStatement = async () => {
|
|
let temp = {...itemInfo.value}
|
|
delete temp.statementallocations
|
|
await useEntities("bankstatements").archive(temp.id)
|
|
}
|
|
|
|
// Helpers for the allocation list display
|
|
const getAllocationLabel = (item) => {
|
|
if (item.account) return `${accounts.value.find(i => i.id === item.account)?.number} - ${accounts.value.find(i => i.id === item.account)?.label}`
|
|
if (item.ownaccount) return `${ownaccounts.value.find(i => i.id === item.ownaccount)?.number} - ${ownaccounts.value.find(i => i.id === item.ownaccount)?.name}`
|
|
if (item.customer) return `${customers.value.find(i => i.id === item.customer)?.customerNumber} - ${customers.value.find(i => i.id === item.customer)?.name}`
|
|
if (item.vendor) return `${vendors.value.find(i => i.id === item.vendor)?.vendorNumber} - ${vendors.value.find(i => i.id === item.vendor)?.name}`
|
|
|
|
if (item.incominginvoice) {
|
|
const inv = incominginvoices.value.find(i => i.id === item.incominginvoice)
|
|
return `Eingangsrechnung: ${inv?.reference} (${inv?.vendor?.name})`
|
|
}
|
|
if (item.createddocument) {
|
|
const doc = createddocuments.value.find(i => i.id === item.createddocument)
|
|
return `${doc?.documentNumber} (${doc?.customer?.name})`
|
|
}
|
|
return "Unbekannte Zuordnung"
|
|
}
|
|
|
|
const getAllocationIcon = (item) => {
|
|
if (item.account || item.ownaccount) return 'i-heroicons-book-open'
|
|
if (item.customer || item.createddocument) return 'i-heroicons-arrow-up-right' // Einnahme / Kunde
|
|
if (item.vendor || item.incominginvoice) return 'i-heroicons-arrow-down-left' // Ausgabe / Lieferant
|
|
return 'i-heroicons-question-mark-circle'
|
|
}
|
|
|
|
setup()
|
|
</script>
|
|
|
|
<template>
|
|
<UDashboardNavbar :ui="{center: 'flex items-stretch gap-1.5 min-w-0'}">
|
|
<template #left>
|
|
<UButton icon="i-heroicons-chevron-left" variant="outline" @click="router.back()">Zurück</UButton>
|
|
<UButton icon="i-heroicons-building-library" variant="ghost" @click="router.push(`/banking`)">Kontobewegungen
|
|
</UButton>
|
|
</template>
|
|
<template #center>
|
|
<h1 class="text-xl font-medium">Kontobewegung bearbeiten</h1>
|
|
</template>
|
|
<template #badge v-if="itemInfo">
|
|
<UBadge v-if="calculateOpenSum == 0" color="green" variant="subtle">Zugewiesen</UBadge>
|
|
<UBadge v-else color="amber" variant="subtle">Offen</UBadge>
|
|
</template>
|
|
<template #right>
|
|
<ArchiveButton color="rose" variant="outline" type="bankstatements" @confirmed="archiveStatement"/>
|
|
</template>
|
|
</UDashboardNavbar>
|
|
|
|
<UDashboardPanelContent class="p-0 bg-gray-50 dark:bg-gray-900 overflow-hidden" v-if="!loading">
|
|
<div class="flex flex-col lg:flex-row h-[calc(100vh-4rem)]">
|
|
|
|
<div
|
|
class="w-full lg:w-5/12 xl:w-4/12 flex flex-col border-r border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 h-full overflow-hidden">
|
|
|
|
<div class="p-4 border-b border-gray-100 dark:border-gray-800 shrink-0">
|
|
<div class="flex justify-between items-start mb-2">
|
|
<div>
|
|
<h2 class="text-lg font-bold text-gray-900 dark:text-white line-clamp-1">
|
|
{{ itemInfo.amount > 0 ? itemInfo.debName : itemInfo.credName || 'Unbekannter Partner' }}
|
|
</h2>
|
|
<div class="text-xs text-gray-500 flex gap-2 mt-1">
|
|
<span>{{ dayjs(itemInfo.date).format("DD.MM.YYYY") }}</span>
|
|
<template v-if="itemInfo.debIban || itemInfo.credIban">
|
|
<span>•</span>
|
|
<span class="font-mono">{{ separateIBAN(itemInfo.debIban || itemInfo.credIban) }}</span>
|
|
</template>
|
|
<template v-else>
|
|
<span>•</span>
|
|
<span class="italic text-gray-400">Sammelbuchung / Keine IBAN</span>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-xl font-bold font-mono"
|
|
:class="itemInfo.amount > 0 ? 'text-emerald-600' : 'text-rose-600'">
|
|
{{ displayCurrency(itemInfo.amount) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 p-2 rounded-md cursor-pointer hover:bg-gray-100 transition-colors"
|
|
@click="showMoreText = !showMoreText">
|
|
<p :class="{'line-clamp-2': !showMoreText}">{{ itemInfo.text }}</p>
|
|
<div class="flex justify-center mt-1" v-if="!showMoreText">
|
|
<UIcon name="i-heroicons-chevron-down" class="w-3 h-3 text-gray-400"/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<div class="flex justify-between text-xs mb-1 font-medium">
|
|
<span :class="calculateOpenSum != 0 ? 'text-amber-600' : 'text-green-600'">
|
|
{{
|
|
calculateOpenSum != 0 ? `${displayCurrency(calculateOpenSum)} offen` : 'Vollständig zugewiesen'
|
|
}}
|
|
</span>
|
|
<span class="text-gray-400">{{
|
|
((Math.abs(itemInfo.amount) - Math.abs(calculateOpenSum)) / Math.abs(itemInfo.amount) * 100).toFixed(0)
|
|
}}%</span>
|
|
</div>
|
|
<UProgress
|
|
:value="Math.abs(itemInfo.amount) - Math.abs(calculateOpenSum)"
|
|
:max="Math.abs(itemInfo.amount)"
|
|
:color="calculateOpenSum != 0 ? 'amber' : 'green'"
|
|
size="sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-y-auto min-h-0 bg-gray-50/50 dark:bg-gray-900">
|
|
<div v-if="itemInfo.statementallocations.length === 0" class="p-8 text-center text-gray-400">
|
|
<UIcon name="i-heroicons-banknotes" class="w-12 h-12 mb-2 opacity-20"/>
|
|
<p class="text-sm">Noch keine Buchungen zugeordnet.</p>
|
|
</div>
|
|
|
|
<ul class="divide-y divide-gray-100 dark:divide-gray-800" v-else>
|
|
<li
|
|
v-for="item in itemInfo.statementallocations"
|
|
:key="item.id"
|
|
class="bg-white dark:bg-gray-900 p-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors group"
|
|
>
|
|
<div class="flex justify-between items-start">
|
|
<div class="flex gap-3">
|
|
<div class="mt-1">
|
|
<UAvatar :icon="getAllocationIcon(item)" size="xs" :ui="{ rounded: 'rounded-md' }"
|
|
class="bg-gray-100 dark:bg-gray-800 text-gray-500"/>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ getAllocationLabel(item) }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 mt-0.5" v-if="item.description">{{ item.description }}</div>
|
|
<div class="flex gap-2 mt-1" v-if="item.createddocument || item.incominginvoice">
|
|
<UButton
|
|
v-if="item.createddocument"
|
|
size="2xs"
|
|
variant="link"
|
|
:padded="false"
|
|
icon="i-heroicons-eye"
|
|
@click="navigateTo(`/createDocument/show/${item.createddocument}`)"
|
|
>Beleg anzeigen
|
|
</UButton>
|
|
<UButton
|
|
v-if="item.incominginvoice"
|
|
size="2xs"
|
|
variant="link"
|
|
:padded="false"
|
|
icon="i-heroicons-eye"
|
|
@click="navigateTo(`/incominginvoices/show/${item.incominginvoice}`)"
|
|
>Beleg anzeigen
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="font-mono text-sm font-semibold">{{ displayCurrency(item.amount) }}</div>
|
|
<UButton
|
|
icon="i-heroicons-trash"
|
|
color="rose"
|
|
variant="ghost"
|
|
size="xs"
|
|
class="opacity-0 group-hover:opacity-100 transition-opacity"
|
|
@click="removeAllocation(item.id)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="w-full lg:w-7/12 xl:w-8/12 flex flex-col h-full overflow-hidden bg-gray-50 dark:bg-gray-950">
|
|
|
|
<div
|
|
class="p-4 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 shadow-sm shrink-0 z-10">
|
|
<div class="grid grid-cols-12 gap-4 items-end">
|
|
<div class="col-span-12 md:col-span-3">
|
|
<UFormGroup label="Betrag" size="sm">
|
|
<UInput v-model="manualAllocationSum" type="number" step="0.01">
|
|
<template #trailing><span class="text-gray-500 text-xs">EUR</span></template>
|
|
</UInput>
|
|
</UFormGroup>
|
|
</div>
|
|
<div class="col-span-12 md:col-span-5">
|
|
<UFormGroup label="Konto / Manuelle Buchung" size="sm">
|
|
<div class="flex gap-1">
|
|
<USelectMenu
|
|
class="w-full"
|
|
:options="accounts"
|
|
value-attribute="id"
|
|
option-attribute="label"
|
|
v-model="accountToSave"
|
|
searchable
|
|
:search-attributes="['number','label']"
|
|
placeholder="Konto suchen..."
|
|
>
|
|
<template #label>
|
|
<span v-if="accountToSave"
|
|
class="truncate">{{ accounts.find(i => i.id === accountToSave).number }} - {{ accounts.find(i => i.id === accountToSave).label }}</span>
|
|
<span v-else>Direkt verbuchen...</span>
|
|
</template>
|
|
<template #option="{option}">
|
|
<span class="font-mono text-xs text-gray-500 mr-2">{{ option.number }}</span> {{ option.label }}
|
|
</template>
|
|
</USelectMenu>
|
|
<UTooltip text="Manuell Buchen">
|
|
<UButton
|
|
icon="i-heroicons-plus"
|
|
:disabled="!accountToSave"
|
|
@click="saveAllocation({bankstatement: itemInfo.id, amount: manualAllocationSum, account: accountToSave, description: allocationDescription })"
|
|
/>
|
|
</UTooltip>
|
|
</div>
|
|
</UFormGroup>
|
|
</div>
|
|
<div class="col-span-12 md:col-span-4 flex justify-end gap-2 pb-0.5">
|
|
<UButton variant="soft" color="gray" icon="i-heroicons-adjustments-horizontal"
|
|
@click="showMoreWithoutRecipe = !showMoreWithoutRecipe" label="Erweitert"/>
|
|
<UButton variant="soft" color="gray" icon="i-heroicons-pencil-square"
|
|
@click="allocationDescription = allocationDescription ? '' : 'Manuelle Buchung'"
|
|
:color="allocationDescription ? 'primary' : 'gray'"/>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="showMoreWithoutRecipe"
|
|
class="mt-4 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
<USelectMenu :options="ownaccounts" value-attribute="id" option-attribute="name" v-model="ownAccountToSave"
|
|
searchable placeholder="Eigenes Konto">
|
|
<template #label>
|
|
{{ ownAccountToSave ? ownaccounts.find(i => i.id === ownAccountToSave).name : 'Eigenes Konto' }}
|
|
</template>
|
|
</USelectMenu>
|
|
<USelectMenu :options="customers" value-attribute="id" option-attribute="name"
|
|
v-model="customerAccountToSave" searchable placeholder="Kunde (Guthaben)">
|
|
<template #label>
|
|
{{ customerAccountToSave ? customers.find(i => i.id === customerAccountToSave).name : 'Kunde' }}
|
|
</template>
|
|
</USelectMenu>
|
|
<USelectMenu :options="vendors" value-attribute="id" option-attribute="name" v-model="vendorAccountToSave"
|
|
searchable placeholder="Lieferant (Guthaben)">
|
|
<template #label>
|
|
{{ vendorAccountToSave ? vendors.find(i => i.id === vendorAccountToSave).name : 'Lieferant' }}
|
|
</template>
|
|
</USelectMenu>
|
|
<div class="md:col-span-3 flex justify-end">
|
|
<UButton size="xs" label="Zuweisen"
|
|
@click="saveAllocation({bankstatement: itemInfo.id, amount: manualAllocationSum, ownaccount: ownAccountToSave ? ownAccountToSave : null, customer: customerAccountToSave ? customerAccountToSave : null, vendor: vendorAccountToSave ? vendorAccountToSave : null, description: allocationDescription })"/>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="allocationDescription" class="mt-2">
|
|
<UInput v-model="allocationDescription" placeholder="Beschreibung für Buchung..." icon="i-heroicons-pencil"
|
|
size="sm"/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 flex flex-col min-h-0">
|
|
<div class="p-2 border-b border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900">
|
|
<UInput
|
|
v-model="searchString"
|
|
icon="i-heroicons-magnifying-glass"
|
|
placeholder="Belege suchen (Nr, Name, Referenz)..."
|
|
:ui="{ icon: { trailing: { pointer: '' } } }"
|
|
@change="tempStore.modifySearchString('bankstatementsedit',searchString)"
|
|
>
|
|
<template #trailing>
|
|
<UButton v-if="searchString" color="gray" variant="link" icon="i-heroicons-x-mark" :padded="false"
|
|
@click="clearSearchString"/>
|
|
</template>
|
|
</UInput>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-y-auto p-4 space-y-6">
|
|
|
|
<div v-if="filteredDocuments.length > 0">
|
|
<h3 class="text-xs font-bold text-gray-500 uppercase mb-2 pl-1 flex items-center gap-2">
|
|
<UIcon name="i-heroicons-document-arrow-up"/>
|
|
Ausgangsrechnungen
|
|
</h3>
|
|
<div
|
|
class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 shadow-sm overflow-hidden">
|
|
<div
|
|
v-for="(document, index) in filteredDocuments"
|
|
:key="document.id"
|
|
class="flex items-center justify-between p-3 border-b border-gray-100 dark:border-gray-800 last:border-0 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="flex items-center gap-3 overflow-hidden">
|
|
<div class="bg-primary-50 dark:bg-primary-900/20 text-primary-600 rounded p-1.5 shrink-0">
|
|
<UIcon name="i-heroicons-document-text" class="w-5 h-5"/>
|
|
</div>
|
|
<div class="min-w-0">
|
|
<div class="font-medium text-sm text-gray-900 dark:text-white truncate">
|
|
{{ document.documentNumber }} <span class="text-gray-400 mx-1">|</span>
|
|
{{ document.customer ? document.customer.name : "Ohne Kunde" }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 flex gap-3 mt-0.5">
|
|
<span class="flex items-center gap-1">
|
|
<UIcon name="i-heroicons-calendar" class="w-3 h-3"/>
|
|
{{ dayjs(document.date).format("DD.MM.YYYY") }}
|
|
</span>
|
|
<span class="text-primary-600 font-medium"
|
|
v-if="Number(document.openSum) < Number(document.total)">
|
|
Teilzahlung
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3 pl-2">
|
|
<div class="text-right">
|
|
<div class="font-mono font-bold text-sm">{{ displayCurrency(document.openSum) }}</div>
|
|
<div class="text-xs text-gray-400">Offen</div>
|
|
</div>
|
|
<UTooltip text="Zuweisen">
|
|
<UButton
|
|
v-if="!itemInfo.statementallocations.find(i => i.createddocument === document.id)"
|
|
icon="i-heroicons-check"
|
|
size="sm"
|
|
color="primary"
|
|
variant="soft"
|
|
@click="saveAllocation({createddocument: document.id, bankstatement: itemInfo.id, amount: Number(Number(document.openSum) < manualAllocationSum ? document.openSum : manualAllocationSum), description: allocationDescription})"
|
|
/>
|
|
</UTooltip>
|
|
<UButton
|
|
icon="i-heroicons-eye"
|
|
color="gray"
|
|
variant="ghost"
|
|
size="sm"
|
|
@click="router.push(`/createDocument/show/${document.id}`)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="filteredIncomingInvoices.length > 0">
|
|
<h3 class="text-xs font-bold text-gray-500 uppercase mb-2 pl-1 flex items-center gap-2">
|
|
<UIcon name="i-heroicons-document-arrow-down"/>
|
|
Eingangsbelege
|
|
</h3>
|
|
<div
|
|
class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 shadow-sm overflow-hidden">
|
|
<div
|
|
v-for="(item, index) in filteredIncomingInvoices"
|
|
:key="item.id"
|
|
class="flex items-center justify-between p-3 border-b border-gray-100 dark:border-gray-800 last:border-0 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="flex items-center gap-3 overflow-hidden">
|
|
<div class="bg-rose-50 dark:bg-rose-900/20 text-rose-600 rounded p-1.5 shrink-0">
|
|
<UIcon name="i-heroicons-receipt-refund" class="w-5 h-5"/>
|
|
</div>
|
|
<div class="min-w-0">
|
|
<div class="font-medium text-sm text-gray-900 dark:text-white truncate">
|
|
{{ item.vendor ? item.vendor.name : 'Ohne Lieferant' }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 flex gap-3 mt-0.5">
|
|
<span class="flex items-center gap-1">
|
|
<UIcon name="i-heroicons-calendar" class="w-3 h-3"/>
|
|
{{ dayjs(item.date).format("DD.MM.YYYY") }}
|
|
</span>
|
|
<span class="truncate max-w-[150px]" :title="item.reference">Ref: {{ item.reference }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3 pl-2">
|
|
<div class="text-right">
|
|
<div class="font-mono font-bold text-sm text-rose-600">
|
|
{{ displayCurrency(getInvoiceSum(item, true)) }}
|
|
</div>
|
|
<div class="text-xs text-gray-400">Offen</div>
|
|
</div>
|
|
<UTooltip text="Zuweisen">
|
|
<UButton
|
|
v-if="!itemInfo.statementallocations.find(i => i.incominginvoice === item.id)"
|
|
icon="i-heroicons-check"
|
|
size="sm"
|
|
color="rose"
|
|
variant="soft"
|
|
@click="saveAllocation({incominginvoice: item.id, bankstatement: itemInfo.id, amount: Number(Math.abs(getInvoiceSum(item,true)) > Math.abs(manualAllocationSum) ? manualAllocationSum : getInvoiceSum(item,true)), description: allocationDescription})"
|
|
/>
|
|
</UTooltip>
|
|
<UButton
|
|
icon="i-heroicons-eye"
|
|
color="gray"
|
|
variant="ghost"
|
|
size="sm"
|
|
@click="router.push(`/incominginvoices/show/${item.id}`)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="filteredDocuments.length === 0 && filteredIncomingInvoices.length === 0"
|
|
class="text-center py-10 text-gray-400">
|
|
<UIcon name="i-heroicons-magnifying-glass" class="w-8 h-8 mx-auto mb-2 opacity-50"/>
|
|
<p>Keine passenden offenen Belege gefunden.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</UDashboardPanelContent>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Custom scrollbar styling just in case, though usually handled by Tailwind/System */
|
|
::-webkit-scrollbar {
|
|
width: 6px;
|
|
height: 6px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #cbd5e1;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #94a3b8;
|
|
}
|
|
|
|
.dark ::-webkit-scrollbar-thumb {
|
|
background: #475569;
|
|
}
|
|
</style> |