diff --git a/backend/db/migrations/0063_skr03_output_tax_account.sql b/backend/db/migrations/0063_skr03_output_tax_account.sql new file mode 100644 index 0000000..4515a2d --- /dev/null +++ b/backend/db/migrations/0063_skr03_output_tax_account.sql @@ -0,0 +1,20 @@ +UPDATE "accounts" +SET "label" = 'Umsatzsteuer 19 %', + "description" = NULL +WHERE "accountChart" = 'skr03' + AND "number" = '1776'; +--> statement-breakpoint +INSERT INTO "accounts" ("number", "label", "description", "accountChart") +SELECT '1776', 'Umsatzsteuer 19 %', NULL, 'skr03' +WHERE NOT EXISTS ( + SELECT 1 + FROM "accounts" + WHERE "accountChart" = 'skr03' + AND "number" = '1776' +); +--> statement-breakpoint +SELECT setval( + pg_get_serial_sequence('accounts', 'id'), + GREATEST(COALESCE((SELECT MAX("id") FROM "accounts"), 1), 1), + true +); diff --git a/backend/db/migrations/meta/_journal.json b/backend/db/migrations/meta/_journal.json index 596952c..c707a25 100644 --- a/backend/db/migrations/meta/_journal.json +++ b/backend/db/migrations/meta/_journal.json @@ -421,6 +421,13 @@ "when": 1786284000000, "tag": "0062_incoming_invoice_tax_override", "breakpoints": true + }, + { + "idx": 60, + "version": "7", + "when": 1786287600000, + "tag": "0063_skr03_output_tax_account", + "breakpoints": true } ] } diff --git a/frontend/components/displayTaxSummary.vue b/frontend/components/displayTaxSummary.vue index db99e5b..add4274 100644 --- a/frontend/components/displayTaxSummary.vue +++ b/frontend/components/displayTaxSummary.vue @@ -6,6 +6,7 @@ import { formatTaxEvaluationPeriodRange, getCreatedDocumentTaxBreakdown, getIncomingInvoiceTaxBreakdown, + getManualBookingTaxBreakdown, getTaxEvaluationPeriodBounds, normalizeTaxEvaluationPeriod } from "~/composables/useTaxEvaluation" @@ -40,7 +41,10 @@ const loadSummary = async () => { const periodType = normalizeTaxEvaluationPeriod(auth.activeTenantData?.taxEvaluationPeriod) const bounds = getTaxEvaluationPeriodBounds(dayjs(), periodType) - const { createdDocuments: docs, incomingInvoices: incoming } = await loadCoreData() + const [{ createdDocuments: docs, incomingInvoices: incoming }, manualBookings] = await Promise.all([ + loadCoreData(), + useNuxtApp().$api("/api/banking/manual-bookings") as Promise + ]) const outputDocs = (docs || []).filter((doc: any) => { if (doc?.state !== "Gebucht") return false @@ -67,12 +71,28 @@ const loadSummary = async () => { return sum + breakdown.tax19 + breakdown.tax7 }, 0) + const manualTax = (manualBookings || []) + .filter((booking: any) => { + const date = dayjs(booking.manualBookingDate) + return date.isValid() && !date.isBefore(bounds.start, "day") && !date.isAfter(bounds.end, "day") + }) + .reduce((sum: { outputTax19: number; inputTax19: number }, booking: any) => { + const breakdown = getManualBookingTaxBreakdown(booking) + return { + outputTax19: sum.outputTax19 + breakdown.outputTax19, + inputTax19: sum.inputTax19 + breakdown.inputTax19, + } + }, { outputTax19: 0, inputTax19: 0 }) + + const totalOutputTax = outputTax + manualTax.outputTax19 + const totalInputTax = inputTax + manualTax.inputTax19 + summary.value = { label: formatTaxEvaluationPeriodLabel(bounds.start, periodType), range: formatTaxEvaluationPeriodRange(bounds.start, periodType), - outputTax: Number(outputTax.toFixed(2)), - inputTax: Number(inputTax.toFixed(2)), - balance: Number((outputTax - inputTax).toFixed(2)), + outputTax: Number(totalOutputTax.toFixed(2)), + inputTax: Number(totalInputTax.toFixed(2)), + balance: Number((totalOutputTax - totalInputTax).toFixed(2)), outputCount: outputDocs.length, inputCount: inputDocs.length, } diff --git a/frontend/composables/useTaxEvaluation.ts b/frontend/composables/useTaxEvaluation.ts index c141804..d5d26a7 100644 --- a/frontend/composables/useTaxEvaluation.ts +++ b/frontend/composables/useTaxEvaluation.ts @@ -166,3 +166,33 @@ export const getIncomingInvoiceTaxBreakdown = (invoice: any) => { net0: Number(breakdown.net0.toFixed(2)), } } + +const getSkr03AccountNumber = (account: any) => { + if (!account || String(account.accountChart || "").toLowerCase() !== "skr03") return null + return String(account.number || "") +} + +export const getManualBookingTaxBreakdown = (booking: any) => { + const amount = Number(booking?.amount || 0) + const breakdown = { outputTax19: 0, inputTax19: 0 } + + if (!Number.isFinite(amount) || amount === 0) return breakdown + + const applySide = (account: any, side: "debit" | "credit") => { + const accountNumber = getSkr03AccountNumber(account) + + if (accountNumber === "1576") { + breakdown.inputTax19 += side === "debit" ? amount : -amount + } else if (accountNumber === "1776") { + breakdown.outputTax19 += side === "credit" ? amount : -amount + } + } + + applySide(booking.account, "debit") + applySide(booking.contraAccount, "credit") + + return { + outputTax19: Number(breakdown.outputTax19.toFixed(2)), + inputTax19: Number(breakdown.inputTax19.toFixed(2)), + } +} diff --git a/frontend/pages/accounting/tax.vue b/frontend/pages/accounting/tax.vue index 6aa9713..47f06a7 100644 --- a/frontend/pages/accounting/tax.vue +++ b/frontend/pages/accounting/tax.vue @@ -6,6 +6,7 @@ import { formatTaxEvaluationPeriodRange, getCreatedDocumentTaxBreakdown, getIncomingInvoiceTaxBreakdown, + getManualBookingTaxBreakdown, getTaxEvaluationPeriodBounds, normalizeTaxEvaluationPeriod, shiftTaxEvaluationPeriodStart @@ -18,6 +19,7 @@ const auth = useAuthStore() const loading = ref(true) const createdDocuments = ref([]) const incomingInvoices = ref([]) +const manualBookings = ref([]) const periodType = computed(() => normalizeTaxEvaluationPeriod(auth.activeTenantData?.taxEvaluationPeriod)) @@ -40,13 +42,15 @@ const loadData = async () => { loading.value = true try { - const [docs, incoming] = await Promise.all([ + const [docs, incoming, manual] = await Promise.all([ useEntities("createddocuments").select(), - useEntities("incominginvoices").select() + useEntities("incominginvoices").select(), + useNuxtApp().$api("/api/banking/manual-bookings") ]) createdDocuments.value = (docs || []).filter(isRelevantOutputDocument) incomingInvoices.value = (incoming || []).filter(isRelevantInputInvoice) + manualBookings.value = (manual as any[]) || [] } finally { loading.value = false } @@ -69,6 +73,19 @@ const periods = computed(() => { return date.isValid() && !date.isBefore(bounds.start, "day") && !date.isAfter(bounds.end, "day") }) + const manualTax = manualBookings.value + .filter((booking) => { + const date = dayjs(booking.manualBookingDate) + return date.isValid() && !date.isBefore(bounds.start, "day") && !date.isAfter(bounds.end, "day") + }) + .reduce((sum, booking) => { + const breakdown = getManualBookingTaxBreakdown(booking) + return { + outputTax19: sum.outputTax19 + breakdown.outputTax19, + inputTax19: sum.inputTax19 + breakdown.inputTax19, + } + }, { outputTax19: 0, inputTax19: 0 }) + const output = outputDocs.reduce((sum, doc) => { const breakdown = getCreatedDocumentTaxBreakdown(doc) return { @@ -91,6 +108,9 @@ const periods = computed(() => { } }, { net19: 0, tax19: 0, net7: 0, tax7: 0, net0: 0 }) + output.tax19 = Number((output.tax19 + manualTax.outputTax19).toFixed(2)) + input.tax19 = Number((input.tax19 + manualTax.inputTax19).toFixed(2)) + const outputTax = Number((output.tax19 + output.tax7).toFixed(2)) const inputTax = Number((input.tax19 + input.tax7).toFixed(2)) const balance = Number((outputTax - inputTax).toFixed(2)) @@ -146,7 +166,7 @@ onMounted(loadData)

Intervall: {{ periodType === "monthly" ? "monatlich" : periodType === "quarterly" ? "quartalsweise" : "jährlich" }}. - Berücksichtigt werden gebuchte Ausgangsrechnungen, Abschlags- und Stornorechnungen sowie gebuchte Eingangsbelege mit Datum. + Berücksichtigt werden gebuchte Ausgangsrechnungen, Abschlags- und Stornorechnungen, gebuchte Eingangsbelege sowie manuelle Buchungen auf SKR03 1576 und 1776.

{{ currentPeriod.range }}