Ausgehende SEPA-Mandate einführen #183
This commit is contained in:
@@ -170,7 +170,7 @@ const setupQuery = () => {
|
||||
|
||||
Object.keys(data).forEach(key => {
|
||||
if (dataType.templateColumns.find(i => i.key === key)) {
|
||||
if (["customer", "contract", "plant", "contact", "project"].includes(key)) {
|
||||
if (["customer", "contract", "plant", "contact", "project", "outgoingsepamandate", "bankaccount"].includes(key)) {
|
||||
item.value[key] = Number(data[key])
|
||||
} else {
|
||||
item.value[key] = data[key]
|
||||
|
||||
@@ -146,6 +146,11 @@ const links = computed(() => {
|
||||
to: "/incomingInvoices",
|
||||
icon: "i-heroicons-document-text",
|
||||
} : null,
|
||||
featureEnabled("outgoingsepamandates") ? {
|
||||
label: "SEPA-Mandate",
|
||||
to: "/standardEntity/outgoingsepamandates",
|
||||
icon: "i-heroicons-identification",
|
||||
} : null,
|
||||
(featureEnabled("incomingInvoices") || featureEnabled("banking")) ? {
|
||||
label: "Abschreibungen",
|
||||
to: "/accounting/depreciation",
|
||||
|
||||
16
frontend/components/columnRenderings/bankAccount.vue
Normal file
16
frontend/components/columnRenderings/bankAccount.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.bankaccount">
|
||||
{{ props.row.bankaccount.displayLabel || props.row.bankaccount.description || props.row.bankaccount.id }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
26
frontend/components/columnRenderings/outgoingSepaMandate.vue
Normal file
26
frontend/components/columnRenderings/outgoingSepaMandate.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.row.outgoingsepamandate">
|
||||
<nuxt-link
|
||||
v-if="props.inShow"
|
||||
:to="`/standardEntity/outgoingsepamandates/show/${props.row.outgoingsepamandate.id}`"
|
||||
>
|
||||
{{ props.row.outgoingsepamandate.reference }}
|
||||
</nuxt-link>
|
||||
<span v-else>{{ props.row.outgoingsepamandate.reference }}</span>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
@@ -51,6 +51,7 @@ const itemInfo = ref({
|
||||
dateOfPerformance: null,
|
||||
paymentDays: auth.activeTenantData.standardPaymentDays,
|
||||
payment_type: "transfer",
|
||||
outgoingsepamandate: null,
|
||||
availableInPortal: false,
|
||||
customSurchargePercentage: 0,
|
||||
created_by: auth.user.id,
|
||||
@@ -90,6 +91,7 @@ const selectedServicecategorie = ref(null)
|
||||
const customers = ref([])
|
||||
const contacts = ref([])
|
||||
const contracts = ref([])
|
||||
const outgoingsepamandates = ref([])
|
||||
const texttemplates = ref([])
|
||||
const units = ref([])
|
||||
const tenantUsers = ref([])
|
||||
@@ -204,6 +206,7 @@ const setupData = async () => {
|
||||
customers.value = await useEntities("customers").select("*", "customerNumber")
|
||||
contacts.value = await useEntities("contacts").select("*")
|
||||
contracts.value = await useEntities("contracts").select("*")
|
||||
outgoingsepamandates.value = await useEntities("outgoingsepamandates").select("*")
|
||||
texttemplates.value = await useEntities("texttemplates").select("*")
|
||||
units.value = await useEntities("units").selectSpecial("*")
|
||||
tenantUsers.value = (await useNuxtApp().$api(`/api/tenant/users`, {
|
||||
@@ -211,6 +214,43 @@ const setupData = async () => {
|
||||
})).users
|
||||
}
|
||||
|
||||
const getMandateCustomerId = (mandate) => mandate?.customer?.id || mandate?.customer
|
||||
|
||||
const availableSepaMandates = computed(() => {
|
||||
return outgoingsepamandates.value.filter((mandate) => {
|
||||
return !mandate.archived
|
||||
&& mandate.status === "Aktiv"
|
||||
&& (!itemInfo.value.customer || getMandateCustomerId(mandate) === itemInfo.value.customer)
|
||||
})
|
||||
})
|
||||
|
||||
const applyDefaultSepaMandate = () => {
|
||||
if (itemInfo.value.payment_type !== "direct-debit") {
|
||||
itemInfo.value.outgoingsepamandate = null
|
||||
return
|
||||
}
|
||||
|
||||
const selectedContract = contracts.value.find((contract) => contract.id === itemInfo.value.contract)
|
||||
const contractMandateId = selectedContract?.outgoingsepamandate?.id || selectedContract?.outgoingsepamandate
|
||||
if (contractMandateId && availableSepaMandates.value.find((mandate) => mandate.id === contractMandateId)) {
|
||||
itemInfo.value.outgoingsepamandate = contractMandateId
|
||||
return
|
||||
}
|
||||
|
||||
if (itemInfo.value.outgoingsepamandate && availableSepaMandates.value.find((mandate) => mandate.id === itemInfo.value.outgoingsepamandate)) {
|
||||
return
|
||||
}
|
||||
|
||||
const defaultMandate = availableSepaMandates.value.find((mandate) => mandate.defaultMandate)
|
||||
|| availableSepaMandates.value[0]
|
||||
itemInfo.value.outgoingsepamandate = defaultMandate?.id || null
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [itemInfo.value.customer, itemInfo.value.contract, itemInfo.value.payment_type, outgoingsepamandates.value.length],
|
||||
() => applyDefaultSepaMandate()
|
||||
)
|
||||
|
||||
const loaded = ref(false)
|
||||
const normalizeEntityId = (value) => {
|
||||
if (value === null || typeof value === "undefined") return null
|
||||
@@ -1476,6 +1516,7 @@ const saveSerialInvoice = async () => {
|
||||
project: itemInfo.value.project,
|
||||
paymentDays: itemInfo.value.paymentDays,
|
||||
payment_type: itemInfo.value.payment_type,
|
||||
outgoingsepamandate: itemInfo.value.outgoingsepamandate,
|
||||
deliveryDateType: "Leistungszeitraum",
|
||||
createdBy: itemInfo.value.createdBy,
|
||||
created_by: itemInfo.value.created_by,
|
||||
@@ -1563,6 +1604,7 @@ const saveDocument = async (state, resetup = false) => {
|
||||
deliveryDateEnd: itemInfo.value.deliveryDateEnd,
|
||||
paymentDays: itemInfo.value.paymentDays,
|
||||
payment_type: itemInfo.value.payment_type,
|
||||
outgoingsepamandate: itemInfo.value.outgoingsepamandate,
|
||||
deliveryDateType: itemInfo.value.deliveryDateType,
|
||||
info: {},
|
||||
createdBy: itemInfo.value.createdBy,
|
||||
@@ -2242,6 +2284,29 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
v-if="itemInfo.payment_type === 'direct-debit'"
|
||||
class="min-w-0 flex-1"
|
||||
label="SEPA-Mandat:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.outgoingsepamandate"
|
||||
value-key="id"
|
||||
label-key="reference"
|
||||
:items="availableSepaMandates"
|
||||
:search-input="{ placeholder: 'Suche...' }"
|
||||
:filter-fields="['reference', 'status']"
|
||||
class="w-full"
|
||||
:disabled="!itemInfo.customer"
|
||||
>
|
||||
<template #default>
|
||||
{{ itemInfo.outgoingsepamandate ? availableSepaMandates.find(i => i.id === itemInfo.outgoingsepamandate)?.reference : "Kein Mandat ausgewählt" }}
|
||||
</template>
|
||||
<template #item="{ item: mandate }">
|
||||
{{ mandate.reference }} - {{ mandate.bankaccount?.displayLabel || "Bankverbindung" }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
class="min-w-0 flex-1"
|
||||
label="Individueller Aufschlag:"
|
||||
|
||||
@@ -35,6 +35,7 @@ const defaultFeatures = {
|
||||
createDocument: true,
|
||||
serialInvoice: true,
|
||||
incomingInvoices: true,
|
||||
outgoingsepamandates: true,
|
||||
costcentres: true,
|
||||
branches: true,
|
||||
teams: true,
|
||||
@@ -82,6 +83,7 @@ const featureOptions = [
|
||||
{ key: "createDocument", label: "Buchhaltung: Ausgangsbelege" },
|
||||
{ key: "serialInvoice", label: "Buchhaltung: Serienvorlagen" },
|
||||
{ key: "incomingInvoices", label: "Buchhaltung: Eingangsbelege" },
|
||||
{ key: "outgoingsepamandates", label: "Buchhaltung: Ausgehende SEPA-Mandate" },
|
||||
{ key: "costcentres", label: "Buchhaltung: Kostenstellen" },
|
||||
{ key: "branches", label: "Stammdaten: Niederlassungen" },
|
||||
{ key: "teams", label: "Mitarbeiter: Teams" },
|
||||
|
||||
@@ -50,6 +50,8 @@ import {useFunctions} from "~/composables/useFunctions.js";
|
||||
import signDate from "~/components/columnRenderings/signDate.vue";
|
||||
import sepaDate from "~/components/columnRenderings/sepaDate.vue";
|
||||
import bankAccounts from "~/components/columnRenderings/bankAccounts.vue";
|
||||
import bankAccount from "~/components/columnRenderings/bankAccount.vue";
|
||||
import outgoingSepaMandate from "~/components/columnRenderings/outgoingSepaMandate.vue";
|
||||
|
||||
// @ts-ignore
|
||||
export const useDataStore = defineStore('data', () => {
|
||||
@@ -173,7 +175,7 @@ export const useDataStore = defineStore('data', () => {
|
||||
numberRangeHolder: "customerNumber",
|
||||
historyItemHolder: "customer",
|
||||
sortColumn: "customerNumber",
|
||||
selectWithInformation: "*, projects(*), plants(*), contracts(*), contacts(*), createddocuments(*, statementallocations(*)), files(*), events(*), customerinventoryitems(*), customerspaces(*)",
|
||||
selectWithInformation: "*, projects(*), plants(*), contracts(*), outgoingsepamandates(*, bankaccount(*)), contacts(*), createddocuments(*, statementallocations(*)), files(*), events(*), customerinventoryitems(*), customerspaces(*)",
|
||||
filters: [{
|
||||
name: "Archivierte ausblenden",
|
||||
default: true,
|
||||
@@ -457,7 +459,7 @@ export const useDataStore = defineStore('data', () => {
|
||||
inputColumn: "Allgemeines"
|
||||
},*/
|
||||
],
|
||||
showTabs: [{label: 'Informationen'},{label: 'Ansprechpartner'},{label: 'Dateien'},{label: 'Ausgangsbelege'},{label: 'Projekte'},{label: 'Objekte'},{label: 'Termine'},{label: 'Verträge'},{label: 'Kundeninventar', key: 'customerinventoryitems'},{label: 'Kundenlagerplätze', key: 'customerspaces'},{label: 'Wiki'}]
|
||||
showTabs: [{label: 'Informationen'},{label: 'Ansprechpartner'},{label: 'Dateien'},{label: 'Ausgangsbelege'},{label: 'Projekte'},{label: 'Objekte'},{label: 'Termine'},{label: 'Verträge'},{label: 'Ausgehende SEPA-Mandate', key: 'outgoingsepamandates', type: 'outgoingsepamandates'},{label: 'Kundeninventar', key: 'customerinventoryitems'},{label: 'Kundenlagerplätze', key: 'customerspaces'},{label: 'Wiki'}]
|
||||
},
|
||||
members: {
|
||||
isArchivable: true,
|
||||
@@ -822,7 +824,7 @@ export const useDataStore = defineStore('data', () => {
|
||||
"Allgemeines",
|
||||
"Abrechnung"
|
||||
],
|
||||
selectWithInformation: "*, customer(*), contracttype(*), files(*)",
|
||||
selectWithInformation: "*, customer(*), contracttype(*), outgoingsepamandate(*, bankaccount(*)), files(*)",
|
||||
templateColumns: [
|
||||
{
|
||||
key: 'contractNumber',
|
||||
@@ -936,6 +938,22 @@ export const useDataStore = defineStore('data', () => {
|
||||
{label:'Überweisung'}
|
||||
],
|
||||
inputColumn: "Abrechnung"
|
||||
},{
|
||||
key: 'outgoingsepamandate',
|
||||
label: "SEPA-Mandat",
|
||||
component: outgoingSepaMandate,
|
||||
inputType: "select",
|
||||
selectDataType: "outgoingsepamandates",
|
||||
selectOptionAttribute: "reference",
|
||||
selectSearchAttributes: ["reference"],
|
||||
selectDataTypeFilter: function (i, item) {
|
||||
const mandateCustomer = i.customer?.id || i.customer
|
||||
return !item.customer || mandateCustomer === item.customer
|
||||
},
|
||||
showFunction: function (item) {
|
||||
return item.paymentType === "Einzug"
|
||||
},
|
||||
inputColumn: "Abrechnung"
|
||||
},{
|
||||
key: "billingInterval",
|
||||
label: "Abrechnungsintervall",
|
||||
@@ -1129,6 +1147,145 @@ export const useDataStore = defineStore('data', () => {
|
||||
],
|
||||
showTabs: [{ label: "Informationen" }]
|
||||
},
|
||||
outgoingsepamandates: {
|
||||
isArchivable: true,
|
||||
label: "Ausgehende SEPA-Mandate",
|
||||
labelSingle: "Ausgehendes SEPA-Mandat",
|
||||
isStandardEntity: true,
|
||||
redirect: true,
|
||||
numberRangeHolder: "reference",
|
||||
historyItemHolder: "outgoingsepamandate",
|
||||
sortColumn: "reference",
|
||||
selectWithInformation: "*, customer(*), bankaccount(*)",
|
||||
filters: [{
|
||||
name: "Archivierte ausblenden",
|
||||
default: true,
|
||||
"filterFunction": function (row) {
|
||||
return !row.archived
|
||||
}
|
||||
}],
|
||||
inputColumns: [
|
||||
"Allgemeines",
|
||||
"Mandat"
|
||||
],
|
||||
templateColumns: [
|
||||
{
|
||||
key: "reference",
|
||||
label: "Mandatsreferenz",
|
||||
title: true,
|
||||
required: true,
|
||||
inputIsNumberRange: true,
|
||||
inputType: "text",
|
||||
inputColumn: "Allgemeines",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "customer",
|
||||
label: "Kunde",
|
||||
component: customer,
|
||||
required: true,
|
||||
inputType: "select",
|
||||
selectDataType: "customers",
|
||||
selectOptionAttribute: "name",
|
||||
selectSearchAttributes: ["name", "customerNumber"],
|
||||
inputColumn: "Allgemeines",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "bankaccount",
|
||||
label: "Bankverbindung",
|
||||
component: bankAccount,
|
||||
required: true,
|
||||
inputType: "select",
|
||||
selectDataType: "entitybankaccounts",
|
||||
selectOptionAttribute: "displayLabel",
|
||||
selectSearchAttributes: ["displayLabel", "iban", "bankName"],
|
||||
inputColumn: "Mandat"
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
label: "Status",
|
||||
required: true,
|
||||
defaultValue: "Entwurf",
|
||||
inputType: "select",
|
||||
selectValueAttribute: "label",
|
||||
selectManualOptions: [
|
||||
{ label: "Entwurf" },
|
||||
{ label: "Aktiv" },
|
||||
{ label: "Widerrufen" },
|
||||
{ label: "Abgelaufen" }
|
||||
],
|
||||
inputColumn: "Allgemeines",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "mandateType",
|
||||
label: "Mandatstyp",
|
||||
required: true,
|
||||
defaultValue: "CORE",
|
||||
inputType: "select",
|
||||
selectValueAttribute: "label",
|
||||
selectManualOptions: [
|
||||
{ label: "CORE" },
|
||||
{ label: "B2B" }
|
||||
],
|
||||
inputColumn: "Mandat",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "sequenceType",
|
||||
label: "Sequenz",
|
||||
required: true,
|
||||
defaultValue: "RCUR",
|
||||
inputType: "select",
|
||||
selectValueAttribute: "label",
|
||||
selectManualOptions: [
|
||||
{ label: "RCUR" },
|
||||
{ label: "OOFF" },
|
||||
{ label: "FRST" },
|
||||
{ label: "FNAL" }
|
||||
],
|
||||
inputColumn: "Mandat",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "signedAt",
|
||||
label: "Unterschrieben am",
|
||||
inputType: "date",
|
||||
inputColumn: "Mandat",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "validFrom",
|
||||
label: "Gültig ab",
|
||||
inputType: "date",
|
||||
inputColumn: "Mandat",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "validUntil",
|
||||
label: "Gültig bis",
|
||||
inputType: "date",
|
||||
inputColumn: "Mandat",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "defaultMandate",
|
||||
label: "Standardmandat",
|
||||
inputType: "bool",
|
||||
defaultValue: false,
|
||||
inputColumn: "Allgemeines",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
label: "Notizen",
|
||||
inputType: "textarea",
|
||||
inputColumn: "Allgemeines"
|
||||
}
|
||||
],
|
||||
showTabs: [{ label: "Informationen" }, { label: "Wiki" }]
|
||||
},
|
||||
absencerequests: {
|
||||
isArchivable: true,
|
||||
label: "Abwesenheiten",
|
||||
|
||||
Reference in New Issue
Block a user