From d7eced3e77859f8ecfd4d327b3fd9a555788bf5f Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Fri, 8 May 2026 20:09:24 +0200 Subject: [PATCH] =?UTF-8?q?Vertragstypen=20f=C3=BCr=20=C3=84nderungsanfrag?= =?UTF-8?q?en=20pflegen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0036_allowed_contracttypes.sql | 1 + backend/db/migrations/meta/_journal.json | 7 ++++ backend/db/schema/contracts.ts | 1 + backend/src/routes/portal/contracts.ts | 9 ++++ frontend/pages/customer-portal.vue | 42 ++++++++++++++++++- frontend/stores/data.js | 9 ++++ 6 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 backend/db/migrations/0036_allowed_contracttypes.sql diff --git a/backend/db/migrations/0036_allowed_contracttypes.sql b/backend/db/migrations/0036_allowed_contracttypes.sql new file mode 100644 index 0000000..3254325 --- /dev/null +++ b/backend/db/migrations/0036_allowed_contracttypes.sql @@ -0,0 +1 @@ +ALTER TABLE "contracts" ADD COLUMN "allowedContracttypes" jsonb DEFAULT '[]'::jsonb NOT NULL; diff --git a/backend/db/migrations/meta/_journal.json b/backend/db/migrations/meta/_journal.json index 8c4e6d3..5279f08 100644 --- a/backend/db/migrations/meta/_journal.json +++ b/backend/db/migrations/meta/_journal.json @@ -246,6 +246,13 @@ "when": 1778191200000, "tag": "0035_contract_history", "breakpoints": true + }, + { + "idx": 35, + "version": "7", + "when": 1778194800000, + "tag": "0036_allowed_contracttypes", + "breakpoints": true } ] } diff --git a/backend/db/schema/contracts.ts b/backend/db/schema/contracts.ts index 2a32e67..c9dc973 100644 --- a/backend/db/schema/contracts.ts +++ b/backend/db/schema/contracts.ts @@ -52,6 +52,7 @@ export const contracts = pgTable( contracttype: bigint("contracttype", { mode: "number" }).references( () => contracttypes.id ), + allowedContracttypes: jsonb("allowedContracttypes").notNull().default([]), bankingIban: text("bankingIban"), bankingBIC: text("bankingBIC"), diff --git a/backend/src/routes/portal/contracts.ts b/backend/src/routes/portal/contracts.ts index 2a10745..d049dd6 100644 --- a/backend/src/routes/portal/contracts.ts +++ b/backend/src/routes/portal/contracts.ts @@ -32,6 +32,7 @@ async function getPortalContract(server: FastifyInstance, req: any, contractId: tenant: contracts.tenant, customer: contracts.customer, contracttype: contracts.contracttype, + allowedContracttypes: contracts.allowedContracttypes, archived: contracts.archived, }) .from(contracts) @@ -121,6 +122,14 @@ export default async function portalContractRoutes(server: FastifyInstance) { return reply.code(400).send({ error: "Ungültiger Vertragstyp" }) } + const allowedContracttypes = Array.isArray(contract.allowedContracttypes) + ? contract.allowedContracttypes.map((id) => Number(id)).filter((id) => Number.isInteger(id)) + : [] + + if (!allowedContracttypes.includes(requestedContracttype.id)) { + return reply.code(400).send({ error: "Dieser Vertragstyp steht für diesen Vertrag nicht zur Auswahl" }) + } + const [currentContracttype] = contract.contracttype ? await server.db .select({ diff --git a/frontend/pages/customer-portal.vue b/frontend/pages/customer-portal.vue index 263bf23..e5a0008 100644 --- a/frontend/pages/customer-portal.vue +++ b/frontend/pages/customer-portal.vue @@ -165,9 +165,27 @@ function getContactLabel(contact: any) { return contact.fullName || [contact.firstName, contact.lastName].filter(Boolean).join(" ") || contact.email || contact.id || "-" } +function getAllowedContracttypeIds(contract: any) { + if (!Array.isArray(contract?.allowedContracttypes)) return [] + return contract.allowedContracttypes.map((id: any) => Number(id)).filter((id: number) => Number.isInteger(id)) +} + +function getAllowedContracttypes(contract: any) { + const allowedIds = getAllowedContracttypeIds(contract) + return contracttypes.value.filter((item: any) => allowedIds.includes(Number(item.id))) +} + +const selectedContractAllowedContracttypes = computed(() => { + if (!selectedContract.value) return [] + return getAllowedContracttypes(selectedContract.value) +}) + function openContractChangeRequest(contract: any) { + const allowedTypes = getAllowedContracttypes(contract) selectedContract.value = contract - contractChangeForm.contracttype = contract.contracttype?.id || null + contractChangeForm.contracttype = allowedTypes.some((item: any) => item.id === contract.contracttype?.id) + ? contract.contracttype.id + : allowedTypes[0]?.id || null contractChangeForm.message = "" contractChangeModalOpen.value = true } @@ -589,6 +607,7 @@ onMounted(async () => { variant="soft" icon="i-heroicons-pencil-square" class="justify-center" + :disabled="getAllowedContracttypes(contract).length === 0" @click="openContractChangeRequest(contract)" > Änderung anfragen @@ -611,6 +630,22 @@ onMounted(async () => { {{ contract.contracttype?.name || "Nicht hinterlegt" }}

+
+

Änderung möglich zu

+
+ + {{ type.name }} + +
+

+ Keine Änderungstypen freigegeben +

+

Ansprechpartner

@@ -751,12 +786,15 @@ onMounted(async () => { +

+ Für diesen Vertrag sind aktuell keine Änderungstypen freigegeben. +

diff --git a/frontend/stores/data.js b/frontend/stores/data.js index 6abe4db..03d2493 100644 --- a/frontend/stores/data.js +++ b/frontend/stores/data.js @@ -853,6 +853,15 @@ export const useDataStore = defineStore('data', () => { item.billingInterval = selectedContractType.billingInterval || null }, inputColumn: "Allgemeines" + },{ + key: "allowedContracttypes", + label: "Vertragstypen für Änderungsanfragen", + inputType: "select", + selectDataType: "contracttypes", + selectOptionAttribute: "name", + selectSearchAttributes: ["name"], + selectMultiple: true, + inputColumn: "Allgemeines" },{ key: "active", label: "Aktiv",