feat(accounting): edit cost centres from finalized outgoing documents

This commit is contained in:
2026-10-07 18:50:38 +02:00
parent 29a9ec2242
commit ac00d2187e

View File

@@ -22,6 +22,69 @@ const linkedDocument =ref({})
const links = ref([])
const portalReleaseLoading = ref(false)
const bankBookingModalOpen = ref(false)
const costCentresOpen = ref(false)
const costCentresLoading = ref(false)
const costCentresSaving = ref(false)
const costCentres = ref([])
const costCentreDraft = ref({ costcentre: null, rows: [] })
const costCentreId = (value) => value?.id || value || null
const costCentreLabel = (id) => {
const centre = costCentres.value.find(item => item.id === id)
return centre ? [centre.number, centre.name].filter(Boolean).join(" - ") : (id ? "Bisherige Kostenstelle" : "Keine Kostenstelle")
}
const openCostCentres = async () => {
costCentresLoading.value = true
try {
costCentres.value = await useEntities("costcentres").select("*")
costCentreDraft.value = {
costcentre: costCentreId(itemInfo.value.costcentre),
rows: (itemInfo.value.rows || [])
.filter(row => row?.id != null && row.id !== "" && !["title", "subtitle", "text", "pagebreak"].includes(row.mode))
.map((row, index) => ({
rowId: String(row.id),
label: `${row.pos || index + 1}. ${row.text || row.name || row.descriptionText || "Position"}`,
costcentre: costCentreId(row.costCentre ?? row.costcentre),
})),
}
costCentresOpen.value = true
} catch (error) {
toast.add({ title: "Kostenstellen konnten nicht geladen werden", color: "error" })
} finally {
costCentresLoading.value = false
}
}
const saveCostCentres = async () => {
if (costCentresSaving.value) return
costCentresSaving.value = true
try {
const originalRows = new Map((itemInfo.value.rows || []).map(row => [String(row.id), row]))
const body = {
rowCostCentres: costCentreDraft.value.rows
.filter(row => row.costcentre !== costCentreId(originalRows.get(row.rowId)?.costCentre ?? originalRows.get(row.rowId)?.costcentre))
.map(({ rowId, costcentre }) => ({ rowId, costcentre })),
}
if (costCentreDraft.value.costcentre !== costCentreId(itemInfo.value.costcentre)) {
body.costcentre = costCentreDraft.value.costcentre
}
if (body.rowCostCentres.length || Object.hasOwn(body, "costcentre")) {
const { document } = await useNuxtApp().$api(`/api/functions/outgoing-documents/${itemInfo.value.id}/cost-centres`, {
method: "PUT",
body,
})
itemInfo.value.costcentre = document.costcentre
itemInfo.value.rows = document.rows
toast.add({ title: "Kostenstellen gespeichert", color: "success" })
}
costCentresOpen.value = false
} catch (error) {
toast.add({ title: "Kostenstellen konnten nicht gespeichert werden", description: error?.data?.error || "Bitte versuche es erneut.", color: "error" })
} finally {
costCentresSaving.value = false
}
}
const portalEligibleTypes = ["invoices", "advanceInvoices", "cancellationInvoices"]
const loadStatementAllocations = async () => {
@@ -212,6 +275,13 @@ const togglePortalRelease = async () => {
>
Bearbeiten
</UButton>
<UButton
v-if="itemInfo.state === 'Gebucht' && itemInfo.type !== 'serialInvoices'"
icon="i-heroicons-tag"
variant="outline"
:loading="costCentresLoading"
@click="openCostCentres"
>Kostenstellen bearbeiten</UButton>
<!-- <UButton
:to="dataStore.documents.find(i => i.createdDocument === itemInfo.id) ? dataStore.documents.find(i => i.createdDocument === itemInfo.id).url : ''"
target="_blank"
@@ -286,6 +356,35 @@ const togglePortalRelease = async () => {
</template>
</UDashboardToolbar>
<UModal v-model:open="costCentresOpen" title="Kostenstellen bearbeiten" :dismissible="!costCentresSaving" :close="!costCentresSaving" :ui="{ content: 'sm:max-w-2xl' }">
<template #body>
<p class="mb-4 text-sm text-muted">Die Belegkostenstelle und abweichende Positionskostenstellen können nachträglich geändert oder entfernt werden.</p>
<div class="space-y-4">
<UFormField label="Belegkostenstelle">
<InputGroup>
<USelectMenu v-model="costCentreDraft.costcentre" :items="costCentres" value-key="id" label-key="name" :filter-fields="['name', 'number']" class="w-full" :disabled="costCentresSaving">
<template #default>{{ costCentreLabel(costCentreDraft.costcentre) }}</template>
<template #item="{ item }">{{ [item.number, item.name].filter(Boolean).join(' - ') }}</template>
</USelectMenu>
<UButton icon="i-heroicons-x-mark" variant="outline" aria-label="Belegkostenstelle entfernen" :disabled="costCentresSaving || !costCentreDraft.costcentre" @click="costCentreDraft.costcentre = null" />
</InputGroup>
</UFormField>
<UFormField v-for="row in costCentreDraft.rows" :key="row.rowId" :label="row.label">
<InputGroup>
<USelectMenu v-model="row.costcentre" :items="costCentres" value-key="id" label-key="name" :filter-fields="['name', 'number']" class="w-full" :disabled="costCentresSaving">
<template #default>{{ costCentreLabel(row.costcentre) }}</template>
<template #item="{ item }">{{ [item.number, item.name].filter(Boolean).join(' - ') }}</template>
</USelectMenu>
<UButton icon="i-heroicons-x-mark" variant="outline" :aria-label="`Kostenstelle für ${row.label} entfernen`" :disabled="costCentresSaving || !row.costcentre" @click="row.costcentre = null" />
</InputGroup>
</UFormField>
</div>
</template>
<template #footer>
<UButton variant="outline" :disabled="costCentresSaving" @click="costCentresOpen = false">Abbrechen</UButton>
<UButton :loading="costCentresSaving" @click="saveCostCentres">Kostenstellen speichern</UButton>
</template>
</UModal>
<UModal v-model:open="bankBookingModalOpen" :ui="{ content: 'sm:max-w-2xl' }">
<template #content>
<UCard>