From f17adb60954eb435cb568d9796faf6e691920fc1 Mon Sep 17 00:00:00 2001
From: duffyduck
Date: Thu, 23 Apr 2026 10:19:04 +0200
Subject: [PATCH] Typspezifische Zusatzinfos in Vertragslisten
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Jede Vertragszeile zeigt jetzt eine kontextspezifische Zusatzinfo an:
- Strom/Gas: "Lieferadresse: Musterstr. 12, 12345 Berlin"
- DSL/Glasfaser/Kabel: "Anschlussadresse: ..."
- Mobilfunk: "Rufnummer: 0171 1234567" (Hauptkarte bevorzugt)
- KFZ: "Kennzeichen: HB-AB 123"
Sichtbar in:
- Admin-Vertragsliste (/contracts)
- Portal-Vertragsliste (Baumansicht)
- Kunden-Detail → Verträge-Tab
Backend: getAllContracts + getContractTreeForCustomer liefern
mobileDetails (mit simCards), carInsuranceDetails und address mit.
Frontend: Neuer Helper utils/contractInfo.ts mit getContractTypeInfo,
aus dem sowohl Label als auch Wert pro Typ kommt.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
backend/src/services/contract.service.ts | 23 +++
backend/todo.md | 132 +++++++-----------
frontend/src/pages/contracts/ContractList.tsx | 21 ++-
.../src/pages/customers/CustomerDetail.tsx | 9 ++
frontend/src/services/api.ts | 7 +
frontend/src/utils/contractInfo.ts | 54 +++++++
6 files changed, 165 insertions(+), 81 deletions(-)
create mode 100644 frontend/src/utils/contractInfo.ts
diff --git a/backend/src/services/contract.service.ts b/backend/src/services/contract.service.ts
index 2ba1354b..fb144efe 100644
--- a/backend/src/services/contract.service.ts
+++ b/backend/src/services/contract.service.ts
@@ -95,6 +95,13 @@ export async function getAllContracts(filters: ContractFilters) {
provider: true,
tariff: true,
contractCategory: true,
+ mobileDetails: {
+ select: {
+ phoneNumber: true,
+ simCards: { select: { phoneNumber: true, isMain: true } },
+ },
+ },
+ carInsuranceDetails: { select: { licensePlate: true } },
},
}),
prisma.contract.count({ where }),
@@ -845,6 +852,13 @@ export interface ContractTreeNode {
provider?: { id: number; name: string } | null;
tariff?: { id: number; name: string } | null;
contractCategory?: { id: number; name: string } | null;
+ customer?: { id: number; firstName: string; lastName: string; companyName: string | null; customerNumber: string } | null;
+ address?: { street: string; houseNumber: string; postalCode: string; city: string } | null;
+ mobileDetails?: {
+ phoneNumber: string | null;
+ simCards: { phoneNumber: string | null; isMain: boolean }[];
+ } | null;
+ carInsuranceDetails?: { licensePlate: string | null } | null;
};
predecessors: ContractTreeNode[];
hasHistory: boolean;
@@ -875,6 +889,15 @@ export async function getContractTreeForCustomer(customerId: number): Promise = {
@@ -306,6 +307,14 @@ export default function ContractList() {
)}
+ {(() => {
+ const typeInfo = getContractTypeInfo(contract as any);
+ return typeInfo ? (
+
+ {typeInfo.label}: {typeInfo.value}
+
+ ) : null;
+ })()}
{contract.startDate && (
Beginn: {formatDate(contract.startDate)}
@@ -455,12 +464,19 @@ export default function ContractList() {
- {data.data.map((contract) => (
+ {data.data.map((contract) => {
+ const typeInfo = getContractTypeInfo(contract as any);
+ return (
|
{contract.contractNumber}
+ {typeInfo && (
+
+ {typeInfo.label}: {typeInfo.value}
+
+ )}
|
{!isCustomer && (
@@ -529,7 +545,8 @@ export default function ContractList() {
|
- ))}
+ );
+ })}
diff --git a/frontend/src/pages/customers/CustomerDetail.tsx b/frontend/src/pages/customers/CustomerDetail.tsx
index d98b033f..5ed554e8 100644
--- a/frontend/src/pages/customers/CustomerDetail.tsx
+++ b/frontend/src/pages/customers/CustomerDetail.tsx
@@ -16,6 +16,7 @@ import FileUpload from '../../components/ui/FileUpload';
import { Edit, Plus, Trash2, MapPin, CreditCard, FileText, Gauge, Eye, EyeOff, Download, Globe, UserPlus, X, Search, Mail, Copy, Check, ChevronDown, ChevronRight, Info, Shield, ShieldCheck, ShieldX, ShieldAlert, Lock, ArrowLeft } from 'lucide-react';
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
import { formatDate } from '../../utils/dateFormat';
+import { getContractTypeInfo } from '../../utils/contractInfo';
import type { Address, BankCard, IdentityDocument, Meter, Customer, CustomerRepresentative, CustomerSummary, CustomerConsent, ConsentType, ConsentStatus, RepresentativeAuthorization } from '../../types';
export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?: number } = {}) {
@@ -1671,6 +1672,14 @@ function ContractsTab({
)}
+ {(() => {
+ const typeInfo = getContractTypeInfo(contract as any);
+ return typeInfo ? (
+
+ {typeInfo.label}: {typeInfo.value}
+
+ ) : null;
+ })()}
{contract.startDate && (
Beginn: {formatDate(contract.startDate)}
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
index 684401ba..dbe9c8f6 100644
--- a/frontend/src/services/api.ts
+++ b/frontend/src/services/api.ts
@@ -601,6 +601,13 @@ export interface ContractTreeNodeContract {
provider?: { id: number; name: string } | null;
tariff?: { id: number; name: string } | null;
contractCategory?: { id: number; name: string } | null;
+ customer?: { id: number; firstName: string; lastName: string; companyName: string | null; customerNumber: string } | null;
+ address?: { street: string; houseNumber: string; postalCode: string; city: string } | null;
+ mobileDetails?: {
+ phoneNumber: string | null;
+ simCards: { phoneNumber: string | null; isMain: boolean }[];
+ } | null;
+ carInsuranceDetails?: { licensePlate: string | null } | null;
}
export interface ContractTreeNode {
diff --git a/frontend/src/utils/contractInfo.ts b/frontend/src/utils/contractInfo.ts
new file mode 100644
index 00000000..e38d2e91
--- /dev/null
+++ b/frontend/src/utils/contractInfo.ts
@@ -0,0 +1,54 @@
+interface ContractInfoData {
+ type: string;
+ address?: { street: string; houseNumber: string; postalCode: string; city: string } | null;
+ mobileDetails?: {
+ phoneNumber: string | null;
+ simCards: { phoneNumber: string | null; isMain: boolean }[];
+ } | null;
+ carInsuranceDetails?: { licensePlate: string | null } | null;
+}
+
+export interface ContractTypeInfo {
+ label: string;
+ value: string;
+}
+
+export function getContractTypeInfo(contract: ContractInfoData): ContractTypeInfo | null {
+ const { type } = contract;
+
+ if (type === 'ELECTRICITY' || type === 'GAS') {
+ const a = contract.address;
+ if (!a) return null;
+ return {
+ label: 'Lieferadresse',
+ value: `${a.street} ${a.houseNumber}, ${a.postalCode} ${a.city}`,
+ };
+ }
+
+ if (type === 'DSL' || type === 'FIBER' || type === 'CABLE') {
+ const a = contract.address;
+ if (!a) return null;
+ return {
+ label: 'Anschlussadresse',
+ value: `${a.street} ${a.houseNumber}, ${a.postalCode} ${a.city}`,
+ };
+ }
+
+ if (type === 'MOBILE') {
+ const md = contract.mobileDetails;
+ if (!md) return null;
+ const mainSim = md.simCards?.find((s) => s.isMain && s.phoneNumber);
+ const anySim = md.simCards?.find((s) => s.phoneNumber);
+ const phone = mainSim?.phoneNumber || anySim?.phoneNumber || md.phoneNumber;
+ if (!phone) return null;
+ return { label: 'Rufnummer', value: phone };
+ }
+
+ if (type === 'CAR_INSURANCE') {
+ const plate = contract.carInsuranceDetails?.licensePlate;
+ if (!plate) return null;
+ return { label: 'Kennzeichen', value: plate };
+ }
+
+ return null;
+}