feat(accounting): Skontobedingungen auf Belegen und automatische Skonto-Vorschläge
Ausgangs- und Eingangsbelege erhalten Skontosatz und Frist, mit Vorgaben bei Kunden und Lieferanten. Das PDF ergänzt automatisch einen Skontohinweis (oder nutzt neue Textvorlagen-Variablen); bei Schlussrechnungen gilt der Skonto für die Restforderung. Skontobedingungen werden aus E-Rechnungen (#SKONTO# und ZUGFeRD) sowie von der KI-Vorbereitung übernommen. Die Bankzuordnung erkennt Zahlungen abzüglich Skonto innerhalb der Frist und bietet sie in der Vorschlagsübersicht direkt an. BWA-Erlöse und -Aufwand berücksichtigen gebuchten Skonto im Zeitraum der Zahlung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {useFunctions} from "~/composables/useFunctions.js";
|
||||
import EntityModalButtons from "~/components/EntityModalButtons.vue";
|
||||
import { documentTemplateHandlebars } from "~/utils/handlebars";
|
||||
import { getTitleSectionMove, moveTitleSection } from "~/utils/documentTitleSections.js";
|
||||
import { appendCashDiscountText, buildCashDiscountTerms, formatCashDiscountText, getCashDiscountTemplateContext } from "~/utils/cashDiscount";
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
@@ -57,6 +58,8 @@ const itemInfo = ref({
|
||||
deliveryDateType: "Lieferdatum",
|
||||
dateOfPerformance: null,
|
||||
paymentDays: auth.activeTenantData.standardPaymentDays,
|
||||
cashDiscountPercent: null,
|
||||
cashDiscountDays: null,
|
||||
payment_type: "transfer",
|
||||
outgoingsepamandate: null,
|
||||
availableInPortal: false,
|
||||
@@ -635,7 +638,11 @@ const setupPage = async () => {
|
||||
if (optionsToImport.deliveryDate) itemInfo.value.deliveryDate = linkedDocument.deliveryDate
|
||||
if (optionsToImport.deliveryDateEnd) itemInfo.value.deliveryDateEnd = linkedDocument.deliveryDateEnd
|
||||
if (optionsToImport.documentDate) itemInfo.value.documentDate = linkedDocument.documentDate
|
||||
if (optionsToImport.paymentDays) itemInfo.value.paymentDays = linkedDocument.paymentDays
|
||||
if (optionsToImport.paymentDays) {
|
||||
itemInfo.value.paymentDays = linkedDocument.paymentDays
|
||||
itemInfo.value.cashDiscountPercent = linkedDocument.cashDiscountPercent ?? null
|
||||
itemInfo.value.cashDiscountDays = linkedDocument.cashDiscountDays ?? null
|
||||
}
|
||||
if (optionsToImport.payment_type) itemInfo.value.payment_type = linkedDocument.payment_type
|
||||
if (optionsToImport.customSurchargePercentage) itemInfo.value.customSurchargePercentage = linkedDocument.customSurchargePercentage
|
||||
if (optionsToImport.contactPerson) itemInfo.value.contactPerson = linkedDocument.contactPerson
|
||||
@@ -663,6 +670,8 @@ const setupPage = async () => {
|
||||
itemInfo.value.deliveryDateEnd = linkedDocument.deliveryDateEnd
|
||||
itemInfo.value.documentDate = linkedDocument.documentDate
|
||||
itemInfo.value.paymentDays = linkedDocument.paymentDays
|
||||
itemInfo.value.cashDiscountPercent = linkedDocument.cashDiscountPercent ?? null
|
||||
itemInfo.value.cashDiscountDays = linkedDocument.cashDiscountDays ?? null
|
||||
itemInfo.value.payment_type = linkedDocument.payment_type
|
||||
itemInfo.value.customSurchargePercentage = linkedDocument.customSurchargePercentage
|
||||
itemInfo.value.contactPerson = linkedDocument.contactPerson
|
||||
@@ -833,6 +842,10 @@ const setCustomerData = async (customerId, loadOnlyAdress = false) => {
|
||||
itemInfo.value.address.country = customer.infoData.country
|
||||
|
||||
if (!loadOnlyAdress && customer.customPaymentDays) itemInfo.value.paymentDays = customer.customPaymentDays
|
||||
if (!loadOnlyAdress && customer.customCashDiscountPercent) {
|
||||
itemInfo.value.cashDiscountPercent = customer.customCashDiscountPercent
|
||||
itemInfo.value.cashDiscountDays = customer.customCashDiscountDays ?? null
|
||||
}
|
||||
if (!loadOnlyAdress && customer.custom_payment_type) itemInfo.value.payment_type = customer.custom_payment_type
|
||||
if (!loadOnlyAdress) {
|
||||
itemInfo.value.taxType = normalizeTaxTypeValue(customer.customTaxType)
|
||||
@@ -1357,6 +1370,17 @@ const documentTotal = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// Skonto bezieht sich bei Schlussrechnungen auf die Restforderung nach Abzug der Abschläge.
|
||||
const cashDiscountTerms = computed(() => {
|
||||
if (!["invoices", "advanceInvoices", "serialInvoices"].includes(itemInfo.value.type)) return null
|
||||
return buildCashDiscountTerms({
|
||||
percent: itemInfo.value.cashDiscountPercent,
|
||||
days: itemInfo.value.cashDiscountDays,
|
||||
documentDate: itemInfo.value.documentDate ? dayjs(itemInfo.value.documentDate).format("YYYY-MM-DD") : null,
|
||||
amount: itemInfo.value.type === "invoices" ? documentTotal.value.totalSumToPay : documentTotal.value.totalGross
|
||||
})
|
||||
})
|
||||
|
||||
const documentReport = computed(() => {
|
||||
|
||||
let totalProductsPurchasePrice = 0
|
||||
@@ -1574,7 +1598,8 @@ const getDocumentData = async () => {
|
||||
kundenname: customerData && customerData.name,
|
||||
zahlungsziel_in_tagen: itemInfo.paymentDays,
|
||||
zahlungsart: itemInfo.payment_type === "transfer" ? "Überweisung" : "Lastschrift",
|
||||
diesel_gesamtverbrauch: (itemInfo.agriculture && itemInfo.agriculture.dieselUsageTotal) && itemInfo.agriculture.dieselUsageTotal
|
||||
diesel_gesamtverbrauch: (itemInfo.agriculture && itemInfo.agriculture.dieselUsageTotal) && itemInfo.agriculture.dieselUsageTotal,
|
||||
...getCashDiscountTemplateContext(cashDiscountTerms.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1684,7 +1709,7 @@ const getDocumentData = async () => {
|
||||
],
|
||||
title: itemInfo.value.title,
|
||||
description: itemInfo.value.description,
|
||||
endText: templateEndText(generateContext(itemInfo.value, contactData)),
|
||||
endText: appendCashDiscountText(templateEndText(generateContext(itemInfo.value, contactData)), itemInfo.value.endText, cashDiscountTerms.value),
|
||||
startText: templateStartText(generateContext(itemInfo.value, contactData)),
|
||||
rows: rows,
|
||||
totalArray: [
|
||||
@@ -1819,6 +1844,8 @@ const saveSerialInvoice = async () => {
|
||||
project: normalizeEntityId(itemInfo.value.project),
|
||||
costcentre: itemInfo.value.costcentre,
|
||||
paymentDays: itemInfo.value.paymentDays,
|
||||
cashDiscountPercent: itemInfo.value.cashDiscountPercent === "" || itemInfo.value.cashDiscountPercent === null ? null : Number(itemInfo.value.cashDiscountPercent),
|
||||
cashDiscountDays: itemInfo.value.cashDiscountDays === "" || itemInfo.value.cashDiscountDays === null ? null : Number(itemInfo.value.cashDiscountDays),
|
||||
payment_type: itemInfo.value.payment_type,
|
||||
outgoingsepamandate: normalizeEntityId(itemInfo.value.outgoingsepamandate),
|
||||
deliveryDateType: "Leistungszeitraum",
|
||||
@@ -1935,6 +1962,8 @@ const saveDocument = async (state, resetup = false) => {
|
||||
deliveryDate: itemInfo.value.deliveryDate,
|
||||
deliveryDateEnd: itemInfo.value.deliveryDateEnd,
|
||||
paymentDays: itemInfo.value.paymentDays,
|
||||
cashDiscountPercent: itemInfo.value.cashDiscountPercent === "" || itemInfo.value.cashDiscountPercent === null ? null : Number(itemInfo.value.cashDiscountPercent),
|
||||
cashDiscountDays: itemInfo.value.cashDiscountDays === "" || itemInfo.value.cashDiscountDays === null ? null : Number(itemInfo.value.cashDiscountDays),
|
||||
payment_type: itemInfo.value.payment_type,
|
||||
outgoingsepamandate: normalizeEntityId(itemInfo.value.outgoingsepamandate),
|
||||
deliveryDateType: itemInfo.value.deliveryDateType,
|
||||
@@ -2690,6 +2719,39 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
||||
class="w-full"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
v-if="['invoices', 'advanceInvoices', 'serialInvoices'].includes(itemInfo.type)"
|
||||
class="min-w-0 flex-1"
|
||||
label="Skonto:"
|
||||
:help="cashDiscountTerms ? formatCashDiscountText(cashDiscountTerms) : undefined"
|
||||
>
|
||||
<div class="flex gap-1">
|
||||
<UInput
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
max="10"
|
||||
placeholder="%"
|
||||
v-model="itemInfo.cashDiscountPercent"
|
||||
class="w-full"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
|
||||
</template>
|
||||
</UInput>
|
||||
<UInput
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="Tage"
|
||||
v-model="itemInfo.cashDiscountDays"
|
||||
class="w-full"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">Tage</span>
|
||||
</template>
|
||||
</UInput>
|
||||
</div>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
class="min-w-0 flex-1"
|
||||
label="Individueller Aufschlag:"
|
||||
|
||||
Reference in New Issue
Block a user