Gruppiert Verknüpfungen in ein Dropdown und lädt Bankbuchungen für Ausgangsbelege gezielt mit Bankstatement-Relation nach, damit das Bankbuchungsdatum korrekt angezeigt wird.
347 lines
11 KiB
Vue
347 lines
11 KiB
Vue
<script setup>
|
|
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
|
|
import dayjs from "dayjs";
|
|
|
|
|
|
|
|
defineShortcuts({
|
|
'backspace': () => {
|
|
router.push("/createDocument")
|
|
},
|
|
})
|
|
|
|
const modal = useModal()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const auth = useAuthStore()
|
|
const dataStore = useDataStore()
|
|
const toast = useToast()
|
|
|
|
const itemInfo = ref({})
|
|
const linkedDocument =ref({})
|
|
const links = ref([])
|
|
const portalReleaseLoading = ref(false)
|
|
const bankBookingModalOpen = ref(false)
|
|
const portalEligibleTypes = ["invoices", "advanceInvoices", "cancellationInvoices"]
|
|
|
|
const loadStatementAllocations = async () => {
|
|
if (!itemInfo.value?.id) return
|
|
|
|
const statementAllocations = await useEntities("statementallocations").select("*, bankstatement(*)")
|
|
itemInfo.value.statementallocations = statementAllocations.filter((allocation) => {
|
|
const createdDocumentId = allocation.createddocument?.id || allocation.createddocument
|
|
|
|
return String(createdDocumentId) === String(itemInfo.value.id)
|
|
})
|
|
}
|
|
|
|
const setupPage = async () => {
|
|
if(route.params) {
|
|
if(route.params.id) {
|
|
itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*,files(*),linkedDocument(*)")
|
|
await loadStatementAllocations()
|
|
}
|
|
|
|
if(itemInfo.value.type === "invoices"){
|
|
const createddocuments = await useEntities("createddocuments").select()
|
|
links.value = createddocuments.filter(i => i.createddocument?.id === itemInfo.value.id)
|
|
}
|
|
|
|
linkedDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
|
}
|
|
}
|
|
|
|
setupPage()
|
|
|
|
const getStatementLike = (allocation) => allocation?.bankstatement || (typeof allocation?.bs_id === "object" ? allocation.bs_id : null)
|
|
const getStatementId = (allocation) => allocation?.bankstatement?.id || allocation?.bs_id?.id || allocation?.bankstatement || allocation?.bs_id || null
|
|
const getBankBookingDate = (allocation) => {
|
|
const statement = getStatementLike(allocation)
|
|
|
|
return statement?.date || statement?.valueDate || allocation?.manualBookingDate || null
|
|
}
|
|
const formatDate = (value) => value ? dayjs(value).format("DD.MM.YYYY") : "-"
|
|
const formatCurrency = (value) => `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`
|
|
const bankBookingDates = computed(() => [...new Set(
|
|
(itemInfo.value.statementallocations || [])
|
|
.map(getBankBookingDate)
|
|
.filter(Boolean)
|
|
)].sort())
|
|
const bankBookingDateLabel = computed(() => {
|
|
if (bankBookingDates.value.length === 0) return "Kein Bankbuchungsdatum"
|
|
if (bankBookingDates.value.length === 1) return formatDate(bankBookingDates.value[0])
|
|
|
|
return bankBookingDates.value.map(formatDate).join(", ")
|
|
})
|
|
const linkedDocumentLabel = (document) => {
|
|
const documentType = dataStore.documentTypesForCreation[document.type]?.labelSingle || "Dokument"
|
|
|
|
return `${documentType} - ${document.documentNumber || document.title || document.id}`
|
|
}
|
|
const linkedItems = computed(() => {
|
|
const items = []
|
|
|
|
if(itemInfo.value.project) {
|
|
items.push({
|
|
label: "Projekt",
|
|
icon: "i-heroicons-link",
|
|
onSelect: () => router.push(`/standardEntity/projects/show/${itemInfo.value.project?.id}`)
|
|
})
|
|
}
|
|
|
|
if(itemInfo.value.customer) {
|
|
items.push({
|
|
label: "Kunde",
|
|
icon: "i-heroicons-link",
|
|
onSelect: () => router.push(`/standardEntity/customers/show/${itemInfo.value.customer?.id}`)
|
|
})
|
|
}
|
|
|
|
if(itemInfo.value.plant) {
|
|
items.push({
|
|
label: "Objekt",
|
|
icon: "i-heroicons-link",
|
|
onSelect: () => router.push(`/standardEntity/plants/show/${itemInfo.value.plant?.id}`)
|
|
})
|
|
}
|
|
|
|
if(itemInfo.value.contract) {
|
|
items.push({
|
|
label: "Vertrag",
|
|
icon: "i-heroicons-link",
|
|
onSelect: () => router.push(`/standardEntity/contracts/show/${itemInfo.value.contract?.id}`)
|
|
})
|
|
}
|
|
|
|
if(itemInfo.value.contact) {
|
|
items.push({
|
|
label: "Ansprechpartner",
|
|
icon: "i-heroicons-link",
|
|
onSelect: () => router.push(`/standardEntity/contacts/show/${itemInfo.value.contact?.id}`)
|
|
})
|
|
}
|
|
|
|
if(itemInfo.value.createddocument) {
|
|
items.push({
|
|
label: linkedDocumentLabel(itemInfo.value.createddocument),
|
|
icon: "i-heroicons-document-duplicate",
|
|
onSelect: () => router.push(`/createDocument/show/${itemInfo.value.createddocument.id}`)
|
|
})
|
|
}
|
|
|
|
;(itemInfo.value.createddocuments || []).forEach((document) => {
|
|
items.push({
|
|
label: linkedDocumentLabel(document),
|
|
icon: "i-heroicons-document-duplicate",
|
|
onSelect: () => router.push(`/createDocument/show/${document.id}`)
|
|
})
|
|
})
|
|
|
|
if(itemInfo.value.statementallocations?.length > 0) {
|
|
items.push({
|
|
label: "Bankbuchungen öffnen",
|
|
icon: "i-heroicons-building-library",
|
|
onSelect: openBankstatements
|
|
})
|
|
items.push({
|
|
label: "Bankdetails anzeigen",
|
|
icon: "i-heroicons-calendar-days",
|
|
onSelect: () => { bankBookingModalOpen.value = true }
|
|
})
|
|
}
|
|
|
|
return [items]
|
|
})
|
|
|
|
const openEmail = () => {
|
|
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
|
|
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(auth.activeTenantData.standardEmailForInvoices || "")}`)
|
|
} else {
|
|
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]`)
|
|
}
|
|
}
|
|
|
|
const openBankstatements = () => {
|
|
const statementIds = (itemInfo.value.statementallocations || []).map(getStatementId).filter(Boolean)
|
|
|
|
if(statementIds.length === 0) return
|
|
|
|
if(statementIds.length > 1) {
|
|
navigateTo(`/banking/?filter=${JSON.stringify(statementIds)}`)
|
|
} else {
|
|
navigateTo(`/banking/statements/edit/${statementIds[0]}`)
|
|
}
|
|
}
|
|
|
|
const togglePortalRelease = async () => {
|
|
if (!itemInfo.value?.id) return
|
|
|
|
portalReleaseLoading.value = true
|
|
|
|
try {
|
|
const nextValue = !itemInfo.value.availableInPortal
|
|
await useEntities("createddocuments").update(itemInfo.value.id, {
|
|
availableInPortal: nextValue
|
|
}, true)
|
|
|
|
itemInfo.value = {
|
|
...itemInfo.value,
|
|
availableInPortal: nextValue
|
|
}
|
|
|
|
toast.add({
|
|
title: nextValue ? "Für Kundenportal freigegeben" : "Portal-Freigabe entfernt"
|
|
})
|
|
} finally {
|
|
portalReleaseLoading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<UDashboardNavbar
|
|
title="Erstelltes Dokument anzeigen"
|
|
>
|
|
|
|
</UDashboardNavbar>
|
|
<UDashboardToolbar>
|
|
<template #left>
|
|
<UButton
|
|
@click="router.push(`/createDocument/edit/${itemInfo.id}`)"
|
|
v-if="itemInfo.state === 'Entwurf'"
|
|
>
|
|
Bearbeiten
|
|
</UButton>
|
|
<!-- <UButton
|
|
:to="dataStore.documents.find(i => i.createdDocument === itemInfo.id) ? dataStore.documents.find(i => i.createdDocument === itemInfo.id).url : ''"
|
|
target="_blank"
|
|
>In neuen Tab anzeigen</UButton>-->
|
|
<UButton
|
|
icon="i-heroicons-arrow-right-end-on-rectangle"
|
|
@click="modal.open(CopyCreatedDocumentModal, {
|
|
id: itemInfo.id,
|
|
type: itemInfo.type
|
|
})"
|
|
variant="outline"
|
|
>
|
|
Kopieren
|
|
</UButton>
|
|
<UButton
|
|
@click="openEmail"
|
|
icon="i-heroicons-envelope"
|
|
>
|
|
E-Mail
|
|
</UButton>
|
|
<UButton
|
|
v-if="portalEligibleTypes.includes(itemInfo.type) && itemInfo.state !== 'Entwurf'"
|
|
:icon="itemInfo.availableInPortal ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
|
|
variant="outline"
|
|
:loading="portalReleaseLoading"
|
|
@click="togglePortalRelease"
|
|
>
|
|
{{ itemInfo.availableInPortal ? "Portal-Freigabe entfernen" : "Für Kundenportal freigeben" }}
|
|
</UButton>
|
|
<UTooltip
|
|
v-if="itemInfo.type === 'invoices' || itemInfo.type === 'advanceInvoices'"
|
|
:text="links.find(i => i.type === 'cancellationInvoices') ? 'Bereits stoniert' : ''"
|
|
>
|
|
<UButton
|
|
@click="router.push(`/createDocument/edit/?createddocument=${itemInfo.id}&loadMode=storno`)"
|
|
variant="outline"
|
|
color="error"
|
|
:disabled="links.find(i => i.type === 'cancellationInvoices')"
|
|
>
|
|
Stornieren
|
|
</UButton>
|
|
</UTooltip>
|
|
|
|
<UDropdownMenu
|
|
v-if="linkedItems[0].length > 0"
|
|
:items="linkedItems"
|
|
:content="{ align: 'start' }"
|
|
>
|
|
<UButton
|
|
icon="i-heroicons-link"
|
|
variant="outline"
|
|
trailing-icon="i-heroicons-chevron-down-20-solid"
|
|
>
|
|
Verknüpfungen
|
|
</UButton>
|
|
</UDropdownMenu>
|
|
<UBadge
|
|
v-if="itemInfo.statementallocations?.length > 0"
|
|
color="primary"
|
|
variant="soft"
|
|
class="self-center"
|
|
>
|
|
Bankbuchungsdatum: {{ bankBookingDateLabel }}
|
|
</UBadge>
|
|
|
|
</template>
|
|
</UDashboardToolbar>
|
|
<UModal v-model:open="bankBookingModalOpen" :ui="{ content: 'sm:max-w-2xl' }">
|
|
<template #content>
|
|
<UCard>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="font-semibold">Bankbuchungen</h3>
|
|
<UButton
|
|
icon="i-heroicons-x-mark"
|
|
color="gray"
|
|
variant="ghost"
|
|
@click="bankBookingModalOpen = false"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="allocation in itemInfo.statementallocations"
|
|
:key="allocation.id"
|
|
class="grid grid-cols-1 gap-2 rounded-md border border-gray-200 p-3 text-sm dark:border-gray-800 md:grid-cols-3"
|
|
>
|
|
<div>
|
|
<p class="text-xs text-gray-500">Bankbuchungsdatum</p>
|
|
<p class="font-medium">{{ formatDate(getBankBookingDate(allocation)) }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500">Betrag</p>
|
|
<p class="font-medium">{{ formatCurrency(allocation.amount) }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500">Text</p>
|
|
<p class="font-medium">{{ getStatementLike(allocation)?.text || allocation.description || "-" }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</template>
|
|
</UModal>
|
|
<UDashboardPanelContent>
|
|
<div
|
|
v-if="portalEligibleTypes.includes(itemInfo.type) && itemInfo.state !== 'Entwurf'"
|
|
class="px-4 py-3"
|
|
>
|
|
<UBadge :color="itemInfo.availableInPortal ? 'primary' : 'neutral'" variant="soft">
|
|
{{ itemInfo.availableInPortal ? "Im Kundenportal sichtbar" : "Nicht im Kundenportal freigegeben" }}
|
|
</UBadge>
|
|
</div>
|
|
<!-- <object
|
|
:data="linkedDocument.url"
|
|
class="w-full previewDocumentMobile"
|
|
/>-->
|
|
<PDFViewer v-if="linkedDocument.id" :file-id="linkedDocument.id" location="show_create_document" />
|
|
|
|
|
|
|
|
|
|
</UDashboardPanelContent>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.previewDocumentMobile {
|
|
aspect-ratio: 1 / 1.414;
|
|
|
|
}
|
|
</style>
|