Passt Kassenbuch Ansicht an
Kassenbücher werden nun zuerst tabellarisch angezeigt. Das Erstellen einer Barkasse erfolgt über ein Modal und einzelne Kassenbücher öffnen sich über eine Detailseite.
This commit is contained in:
@@ -2,9 +2,10 @@
|
||||
import dayjs from "dayjs"
|
||||
|
||||
const toast = useToast()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(true)
|
||||
const savingCashbook = ref(false)
|
||||
const savingBooking = ref(false)
|
||||
const cashbooks = ref([])
|
||||
const bookings = ref([])
|
||||
@@ -13,7 +14,7 @@ const customers = ref([])
|
||||
const vendors = ref([])
|
||||
const ownaccounts = ref([])
|
||||
const incomingInvoices = ref([])
|
||||
const selectedCashbookId = ref(null)
|
||||
const selectedCashbookId = computed(() => Number(route.params.id))
|
||||
const counterSearch = ref("")
|
||||
const expandedGroups = ref([])
|
||||
|
||||
@@ -25,12 +26,6 @@ const DATEV_TAX_KEY_ITEMS = [
|
||||
{ value: "18", label: "18 - EU Vorsteuer 7 %" }
|
||||
]
|
||||
|
||||
const newCashbook = reactive({
|
||||
name: "",
|
||||
datevNumber: "1000",
|
||||
openingBalance: 0
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
date: dayjs().format("YYYY-MM-DD"),
|
||||
direction: "expense",
|
||||
@@ -168,7 +163,7 @@ const getBookingCounter = (booking) => {
|
||||
}
|
||||
|
||||
const loadBookings = async () => {
|
||||
if (!selectedCashbookId.value) {
|
||||
if (!selectedCashbookId.value || !Number.isFinite(selectedCashbookId.value)) {
|
||||
bookings.value = []
|
||||
return
|
||||
}
|
||||
@@ -196,41 +191,10 @@ const loadData = async () => {
|
||||
.filter((invoice) => invoice.state === "Gebucht" && !invoice.archived)
|
||||
.filter((invoice) => getIncomingInvoiceOpenAmount(invoice) > 0.004)
|
||||
|
||||
if (!selectedCashbookId.value && cashbooks.value.length > 0) {
|
||||
selectedCashbookId.value = cashbooks.value[0].id
|
||||
}
|
||||
|
||||
await loadBookings()
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const createCashbook = async () => {
|
||||
if (!newCashbook.name || !newCashbook.datevNumber) {
|
||||
toast.add({ title: "Bitte Bezeichnung und Kontennummer ausfüllen.", color: "warning" })
|
||||
return
|
||||
}
|
||||
|
||||
savingCashbook.value = true
|
||||
try {
|
||||
const created = await useNuxtApp().$api("/api/banking/cashbooks", {
|
||||
method: "POST",
|
||||
body: {
|
||||
name: newCashbook.name,
|
||||
datevNumber: newCashbook.datevNumber,
|
||||
openingBalance: Number(newCashbook.openingBalance || 0)
|
||||
}
|
||||
})
|
||||
toast.add({ title: "Barkasse erstellt." })
|
||||
newCashbook.name = ""
|
||||
newCashbook.datevNumber = "1000"
|
||||
newCashbook.openingBalance = 0
|
||||
await loadData()
|
||||
selectedCashbookId.value = created.id
|
||||
} finally {
|
||||
savingCashbook.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const saveBooking = async () => {
|
||||
if (!selectedCashbookId.value || !form.date || !form.amount || !form.counter) {
|
||||
toast.add({ title: "Bitte Kasse, Datum, Betrag und Gegenkonto auswählen.", color: "warning" })
|
||||
@@ -271,69 +235,21 @@ const deleteBooking = async (booking) => {
|
||||
await loadBookings()
|
||||
}
|
||||
|
||||
watch(selectedCashbookId, loadBookings)
|
||||
|
||||
onMounted(loadData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardPanelContent>
|
||||
<UDashboardNavbar title="Kassenbuch" :badge="cashbooks.length" />
|
||||
<UDashboardNavbar :title="selectedCashbook ? selectedCashbook.name : 'Kassenbuch'">
|
||||
<template #left>
|
||||
<UButton icon="i-heroicons-arrow-left" variant="ghost" color="neutral" @click="router.push('/accounting/cashbooks')">
|
||||
Kassenbücher
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-[320px_minmax(0,1fr)]">
|
||||
<div class="space-y-6">
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div>
|
||||
<h2 class="font-semibold text-gray-900 dark:text-white">Barkassen</h2>
|
||||
<p class="text-sm text-gray-500">Kassen werden als manuelle Bankkonten geführt.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="loading" class="py-6 text-center text-gray-500">Lade Kassen...</div>
|
||||
<div v-else-if="cashbooks.length === 0" class="py-6 text-center text-gray-500">Noch keine Barkasse angelegt.</div>
|
||||
<div v-else class="space-y-2">
|
||||
<button
|
||||
v-for="cashbook in cashbooks"
|
||||
:key="cashbook.id"
|
||||
type="button"
|
||||
class="w-full rounded-lg border px-3 py-2 text-left transition"
|
||||
:class="selectedCashbookId === cashbook.id
|
||||
? 'border-primary-500 bg-primary-50 dark:bg-primary-950/30'
|
||||
: 'border-gray-200 bg-white hover:border-gray-300 dark:border-gray-800 dark:bg-gray-900'"
|
||||
@click="selectedCashbookId = cashbook.id"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span class="font-medium">{{ cashbook.name }}</span>
|
||||
<span class="font-mono text-sm text-gray-500">{{ cashbook.datevNumber }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-gray-900 dark:text-white">Neue Barkasse</h2>
|
||||
</template>
|
||||
|
||||
<div class="space-y-4">
|
||||
<UFormField label="Bezeichnung">
|
||||
<UInput v-model="newCashbook.name" placeholder="z. B. Hauptkasse" />
|
||||
</UFormField>
|
||||
<UFormField label="Kontennummer">
|
||||
<UInput v-model="newCashbook.datevNumber" placeholder="1000" />
|
||||
</UFormField>
|
||||
<UFormField label="Anfangsbestand">
|
||||
<UInput v-model="newCashbook.openingBalance" type="number" step="0.01" />
|
||||
</UFormField>
|
||||
<UButton block icon="i-heroicons-plus" :loading="savingCashbook" @click="createCashbook">
|
||||
Barkasse anlegen
|
||||
</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedCashbook" class="space-y-6">
|
||||
<div v-if="loading" class="py-10 text-center text-gray-500">Lade Kassenbuch...</div>
|
||||
<div v-else-if="selectedCashbook" class="space-y-6">
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<UCard>
|
||||
<div class="text-sm text-gray-500">Aktuelle Kasse</div>
|
||||
@@ -502,11 +418,11 @@ onMounted(loadData)
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<UCard v-else>
|
||||
<div class="py-16 text-center text-gray-500">Lege eine Barkasse an, um das Kassenbuch zu führen.</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<UCard v-else>
|
||||
<div class="py-16 text-center text-gray-500">Dieses Kassenbuch wurde nicht gefunden.</div>
|
||||
</UCard>
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
165
frontend/pages/accounting/cashbooks/index.vue
Normal file
165
frontend/pages/accounting/cashbooks/index.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
const tempStore = useTempStore()
|
||||
|
||||
const loading = ref(true)
|
||||
const savingCashbook = ref(false)
|
||||
const createCashbookModalOpen = ref(false)
|
||||
const cashbooks = ref([])
|
||||
const searchString = ref(tempStore.searchStrings["cashbooks"] || "")
|
||||
|
||||
const newCashbook = reactive({
|
||||
name: "",
|
||||
datevNumber: "1000",
|
||||
openingBalance: 0
|
||||
})
|
||||
|
||||
const displayCurrency = (value) => `${Number(value || 0).toFixed(2).replace(".", ",")} €`
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Bezeichnung"
|
||||
},
|
||||
{
|
||||
key: "datevNumber",
|
||||
label: "Kontennummer"
|
||||
},
|
||||
{
|
||||
key: "balance",
|
||||
label: "Anfangsbestand"
|
||||
},
|
||||
{
|
||||
key: "syncedAt",
|
||||
label: "Erstellt"
|
||||
}
|
||||
]
|
||||
|
||||
const filteredRows = computed(() => useSearch(searchString.value, cashbooks.value))
|
||||
|
||||
const clearSearchString = () => {
|
||||
tempStore.clearSearchString("cashbooks")
|
||||
searchString.value = ""
|
||||
}
|
||||
|
||||
const loadCashbooks = async () => {
|
||||
loading.value = true
|
||||
cashbooks.value = await useNuxtApp().$api("/api/banking/cashbooks")
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const resetCreateForm = () => {
|
||||
newCashbook.name = ""
|
||||
newCashbook.datevNumber = "1000"
|
||||
newCashbook.openingBalance = 0
|
||||
}
|
||||
|
||||
const createCashbook = async () => {
|
||||
if (!newCashbook.name || !newCashbook.datevNumber) {
|
||||
toast.add({ title: "Bitte Bezeichnung und Kontennummer ausfüllen.", color: "warning" })
|
||||
return
|
||||
}
|
||||
|
||||
savingCashbook.value = true
|
||||
try {
|
||||
const created = await useNuxtApp().$api("/api/banking/cashbooks", {
|
||||
method: "POST",
|
||||
body: {
|
||||
name: newCashbook.name,
|
||||
datevNumber: newCashbook.datevNumber,
|
||||
openingBalance: Number(newCashbook.openingBalance || 0)
|
||||
}
|
||||
})
|
||||
toast.add({ title: "Barkasse erstellt." })
|
||||
createCashbookModalOpen.value = false
|
||||
resetCreateForm()
|
||||
await loadCashbooks()
|
||||
router.push(`/accounting/cashbooks/${created.id}`)
|
||||
} finally {
|
||||
savingCashbook.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadCashbooks)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardPanelContent>
|
||||
<UDashboardNavbar title="Kassenbücher" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
@change="tempStore.modifySearchString('cashbooks', searchString)"
|
||||
/>
|
||||
<UButton
|
||||
v-if="searchString.length > 0"
|
||||
icon="i-heroicons-x-mark"
|
||||
variant="outline"
|
||||
color="error"
|
||||
@click="clearSearchString"
|
||||
/>
|
||||
<UButton icon="i-heroicons-plus" @click="createCashbookModalOpen = true">
|
||||
Barkasse
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UTable
|
||||
:data="filteredRows"
|
||||
:columns="normalizeTableColumns(templateColumns)"
|
||||
:loading="loading"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
:on-select="(row) => router.push(`/accounting/cashbooks/${row.original?.id || row.id}`)"
|
||||
:empty="{ icon: 'i-heroicons-banknotes', label: 'Keine Kassenbücher angelegt' }"
|
||||
>
|
||||
<template #datevNumber-cell="{ row }">
|
||||
<span class="font-mono">{{ row.original.datevNumber }}</span>
|
||||
</template>
|
||||
<template #balance-cell="{ row }">
|
||||
{{ displayCurrency(row.original.balance) }}
|
||||
</template>
|
||||
<template #syncedAt-cell="{ row }">
|
||||
{{ row.original.createdAt ? new Date(row.original.createdAt).toLocaleDateString("de-DE") : "-" }}
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
<UModal v-model:open="createCashbookModalOpen">
|
||||
<template #content>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="text-lg font-semibold">Barkasse anlegen</div>
|
||||
</template>
|
||||
|
||||
<UForm :state="newCashbook" class="space-y-4" @submit.prevent="createCashbook">
|
||||
<UFormField label="Bezeichnung">
|
||||
<UInput v-model="newCashbook.name" placeholder="z. B. Hauptkasse" />
|
||||
</UFormField>
|
||||
<UFormField label="Kontennummer">
|
||||
<UInput v-model="newCashbook.datevNumber" placeholder="1000" />
|
||||
</UFormField>
|
||||
<UFormField label="Anfangsbestand">
|
||||
<UInput v-model="newCashbook.openingBalance" type="number" step="0.01" />
|
||||
</UFormField>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<UButton color="gray" variant="soft" @click="createCashbookModalOpen = false">
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton type="submit" color="primary" :loading="savingCashbook">
|
||||
Barkasse anlegen
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</UCard>
|
||||
</template>
|
||||
</UModal>
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
Reference in New Issue
Block a user