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; +}