Added Abschreibungen

This commit is contained in:
2026-03-25 17:13:59 +01:00
parent f6c9875320
commit 42e0d7b35e
16 changed files with 13054 additions and 55 deletions

View File

@@ -3,6 +3,14 @@ import InputGroup from "~/components/InputGroup.vue";
import dayjs from "dayjs";
import { parseDate } from "@internationalized/date"
import { useDraggable } from '@vueuse/core'
import {
DEPRECIATION_METHOD_ITEMS,
EXPENSE_BOOKING_MODE_ITEMS,
createIncomingInvoiceAccount,
ensureDepreciationDefaults,
isDepreciationBookingMode,
normalizeIncomingInvoiceAccounts
} from "~/composables/useDepreciation"
// --- Standard Setup & Data ---
const dataStore = useDataStore()
@@ -30,14 +38,7 @@ const itemInfo = ref({
description: "",
state: "Entwurf",
accounts: [
{
account: null,
amountNet: null,
amountTax: null,
taxType: "19",
costCentre: null,
amountGross: null
}
createIncomingInvoiceAccount()
]
})
@@ -61,12 +62,12 @@ const setup = async () => {
itemInfo.value = {
...invoiceData,
vendor: invoiceData.vendor?.id || invoiceData.vendor,
accounts: invoiceData.accounts || []
accounts: normalizeIncomingInvoiceAccounts(invoiceData.accounts || [], invoiceData.date)
}
// Fallback Accounts
if(itemInfo.value.accounts.length === 0) {
itemInfo.value.accounts.push({ account: null, amountNet: null, amountTax: null, taxType: "19", costCentre: null })
itemInfo.value.accounts.push(createIncomingInvoiceAccount({ depreciationStartDate: itemInfo.value.date || null }))
}
// Datei laden
@@ -92,6 +93,14 @@ const setup = async () => {
setup()
watch(() => itemInfo.value.date, (value) => {
;(itemInfo.value.accounts || []).forEach((account) => {
if (isDepreciationItem(account) && !account.depreciationStartDate) {
account.depreciationStartDate = value || null
}
})
})
// --- Berechnungslogik ---
const useNetMode = ref(false)
@@ -144,6 +153,11 @@ const totalCalculated = computed(() => {
const hasAmount = (value) => value !== null && value !== undefined && value !== ""
const hasValidNumber = (value) => hasAmount(value) && Number.isFinite(Number(value))
const isDepreciationItem = (item) => isDepreciationBookingMode(item?.bookingMode)
const updateBookingMode = (item) => {
ensureDepreciationDefaults(item, itemInfo.value.date)
}
const recalculateItem = (item, source) => {
const taxRate = Number(taxOptions.value.find(i => i.key === item.taxType)?.percentage || 0);
@@ -186,6 +200,7 @@ const updateIncomingInvoice = async (setBooked = false) => {
}
let item = { ...itemInfo.value }
item.accounts = (item.accounts || []).map((account) => ensureDepreciationDefaults({ ...account }, item.date))
delete item.files
item.state = setBooked ? "Gebucht" : "Entwurf"
@@ -205,6 +220,7 @@ const findIncomingInvoiceErrors = computed(() => {
if(!i.accounts || i.accounts.length === 0) errors.push({message: "Es ist keine Position vorhanden", type: "breaking"})
i.accounts.forEach((account, idx) => {
ensureDepreciationDefaults(account, i.date)
if(!account.account) errors.push({message: `Pos ${idx+1}: Keine Kategorie`, type: "breaking"})
if(!hasValidNumber(account.amountNet) && !hasValidNumber(account.amountGross)) {
errors.push({message: `Pos ${idx+1}: Kein gültiger Betrag`, type: "breaking"})
@@ -213,6 +229,12 @@ const findIncomingInvoiceErrors = computed(() => {
if(hasValidNumber(account.amountNet) && !hasValidNumber(account.amountTax)) {
errors.push({message: `Pos ${idx+1}: Steuerbetrag fehlt, bitte Steuer neu berechnen`, type: "warning"})
}
if(isDepreciationItem(account) && !Number(account.depreciationMonths)) {
errors.push({message: `Pos ${idx+1}: Abschreibungsdauer fehlt`, type: "breaking"})
}
if(account.bookingMode === "depreciation_bundle" && !String(account.depreciationGroup || "").trim()) {
errors.push({message: `Pos ${idx+1}: Sammelposten benötigt einen Gruppennamen`, type: "breaking"})
}
})
const order = { breaking: 0, warning: 1 }
@@ -509,7 +531,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
value-key="id"
:disabled="mode === 'show'"
:filter-fields="['label', 'number']"
:color="item.account ? 'primary' : 'error'"
:color="item.account ? 'primary' : 'error'"
>
<template #item="{ item: option }">
<span class="font-mono text-xs text-gray-500 mr-2">{{ option.number }}</span> {{ option.label }}
@@ -521,6 +543,20 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
</UFormField>
</div>
<div class="col-span-12 md:col-span-6">
<UFormField label="Aufwandsart">
<USelectMenu
class="w-full"
:items="EXPENSE_BOOKING_MODE_ITEMS"
value-key="value"
label-key="label"
v-model="item.bookingMode"
:disabled="mode === 'show'"
@update:model-value="updateBookingMode(item)"
/>
</UFormField>
</div>
<div class="col-span-12 md:col-span-6">
<UFormField label="Kostenstelle">
<USelectMenu
@@ -539,6 +575,68 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
</UFormField>
</div>
<template v-if="isDepreciationItem(item)">
<div class="col-span-12 md:col-span-4">
<UFormField label="Abschreibungsdauer (Monate)">
<UInput
class="w-full"
type="number"
min="1"
step="1"
v-model="item.depreciationMonths"
:disabled="mode === 'show'"
/>
</UFormField>
</div>
<div class="col-span-12 md:col-span-4">
<UFormField label="Methode">
<USelectMenu
class="w-full"
:items="DEPRECIATION_METHOD_ITEMS"
value-key="value"
label-key="label"
v-model="item.depreciationMethod"
:disabled="mode === 'show'"
/>
</UFormField>
</div>
<div class="col-span-12 md:col-span-4">
<UFormField label="Start Abschreibung">
<UInput
class="w-full"
type="date"
v-model="item.depreciationStartDate"
:disabled="mode === 'show'"
/>
</UFormField>
</div>
<div class="col-span-12 md:col-span-4">
<UFormField :label="item.bookingMode === 'depreciation_bundle' ? 'Sammelposten' : 'Bezeichnung Abschreibung'">
<UInput
class="w-full"
v-model="item[item.bookingMode === 'depreciation_bundle' ? 'depreciationGroup' : 'depreciationLabel']"
:disabled="mode === 'show'"
:placeholder="item.bookingMode === 'depreciation_bundle' ? 'z. B. IT-Hardware 2026' : 'z. B. Notebook Fuhrpark' "
/>
</UFormField>
</div>
<div class="col-span-12">
<UAlert
color="primary"
variant="soft"
icon="i-heroicons-calendar-days"
title="Nur die monatliche Abschreibung erscheint in der BWA"
:description="item.bookingMode === 'depreciation_bundle'
? 'Diese Position wird nicht sofort als Aufwand gezählt, sondern als Sammelposten periodisiert.'
: 'Diese Position wird nicht sofort als Aufwand gezählt, sondern über die gewählte Laufzeit abgeschrieben.'"
/>
</div>
</template>
<div class="col-span-12 md:col-span-3">
<UFormField label="Betrag (Netto)">
<UInput
@@ -624,7 +722,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
icon="i-heroicons-plus"
variant="soft"
block
@click="itemInfo.accounts.push({account:null, amountNet: null, amountTax:0, amountGross: null, taxType: '19'})"
@click="itemInfo.accounts.push(createIncomingInvoiceAccount({ depreciationStartDate: itemInfo.date || null }))"
>
Weitere Position hinzufügen
</UButton>