From 4acfd9de1caf9c8fa289d341ab3b9beff4385e8b Mon Sep 17 00:00:00 2001 From: duffyduck Date: Mon, 1 Jun 2026 08:10:16 +0200 Subject: [PATCH] =?UTF-8?q?SIM-Karten:=20Feld=20"Kartennutzer"=20f=C3=BCr?= =?UTF-8?q?=20Firmen-/Familienvertr=C3=A4ge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bei Firmenverträgen (Vertragsinhaber = Firma, Nutzer = Mitarbeiter) und Familienverträgen (Inhaber = Eltern, Nutzer = Kind) brauchten wir ein Feld, das den tatsächlichen Nutzer der SIM-Karte erfasst. Backend: SimCard.cardUser (String?, optional), Migration 20260601100000_sim_card_user mit IF NOT EXISTS. Im Service durch Create + Update propagiert. Frontend: Input "Kartennutzer" pro SIM-Karte in ContractForm (eigene Zeile oberhalb der technischen Felder Rufnummer/SIM-Nr/ PIN/PUK). In ContractDetail wird der Nutzer als "Nutzer: " neben den Hauptkarte/Multisim-Badges angezeigt. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../20260601100000_sim_card_user/migration.sql | 9 +++++++++ backend/prisma/schema.prisma | 3 +++ backend/src/services/contract.service.ts | 4 ++++ frontend/src/pages/contracts/ContractDetail.tsx | 8 +++++++- frontend/src/pages/contracts/ContractForm.tsx | 16 ++++++++++++++++ frontend/src/types/index.ts | 3 +++ 6 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 backend/prisma/migrations/20260601100000_sim_card_user/migration.sql diff --git a/backend/prisma/migrations/20260601100000_sim_card_user/migration.sql b/backend/prisma/migrations/20260601100000_sim_card_user/migration.sql new file mode 100644 index 00000000..513bcb8a --- /dev/null +++ b/backend/prisma/migrations/20260601100000_sim_card_user/migration.sql @@ -0,0 +1,9 @@ +-- SIM-Karte bekommt einen optionalen "Kartennutzer" – relevant bei Firmen- +-- und Familienverträgen, wo der Vertragsinhaber (Firma/Eltern) nicht +-- gleich dem tatsächlichen Nutzer (Mitarbeiter/Kind) ist. +-- +-- IF NOT EXISTS macht den Re-Deploy auf Prod sicher, falls jemand schon +-- `prisma db push` gefahren hat. + +ALTER TABLE `SimCard` + ADD COLUMN IF NOT EXISTS `cardUser` VARCHAR(191) NULL; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 8fcc8ab6..79115da0 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -928,6 +928,9 @@ model SimCard { puk String? // PUK (verschlüsselt gespeichert) isMultisim Boolean @default(false) // Ist dies eine Multisim-Karte? isMain Boolean @default(false) // Ist dies die Hauptkarte? + // Tatsächlicher Nutzer der SIM-Karte (z.B. Mitarbeiter bei Firmenverträgen, + // Kind bei Eltern-Vertrag) – kann vom Vertragsinhaber abweichen. + cardUser String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/backend/src/services/contract.service.ts b/backend/src/services/contract.service.ts index f25dcbf0..13e6bd71 100644 --- a/backend/src/services/contract.service.ts +++ b/backend/src/services/contract.service.ts @@ -275,6 +275,7 @@ interface ContractCreateData { puk?: string; isMultisim?: boolean; isMain?: boolean; + cardUser?: string; }[]; }; tvDetails?: { @@ -378,6 +379,7 @@ export async function createContract(data: ContractCreateData) { puk: sc.puk ? encrypt(sc.puk) : undefined, isMultisim: sc.isMultisim ?? false, isMain: sc.isMain ?? false, + cardUser: sc.cardUser, })), } : undefined, @@ -614,6 +616,7 @@ export async function updateContract( puk: sc.puk ? encrypt(sc.puk) : (existingSc?.puk ?? undefined), isMultisim: sc.isMultisim ?? false, isMain: sc.isMain ?? false, + cardUser: sc.cardUser, }; }), }); @@ -632,6 +635,7 @@ export async function updateContract( puk: sc.puk ? encrypt(sc.puk) : undefined, isMultisim: sc.isMultisim ?? false, isMain: sc.isMain ?? false, + cardUser: sc.cardUser, })), } : undefined, diff --git a/frontend/src/pages/contracts/ContractDetail.tsx b/frontend/src/pages/contracts/ContractDetail.tsx index 1ecd5258..3e3d9ba3 100644 --- a/frontend/src/pages/contracts/ContractDetail.tsx +++ b/frontend/src/pages/contracts/ContractDetail.tsx @@ -134,9 +134,15 @@ function SimCardDisplay({ simCard }: { simCard: SimCard }) { return (
-
+
{simCard.isMain && Hauptkarte} {simCard.isMultisim && Multisim} + {simCard.cardUser && ( + + Nutzer:{' '} + {simCard.cardUser} + + )}
{simCard.phoneNumber && ( diff --git a/frontend/src/pages/contracts/ContractForm.tsx b/frontend/src/pages/contracts/ContractForm.tsx index 95eb6a7c..a4b7ae9d 100644 --- a/frontend/src/pages/contracts/ContractForm.tsx +++ b/frontend/src/pages/contracts/ContractForm.tsx @@ -192,6 +192,7 @@ export default function ContractForm() { hasExistingPuk?: boolean; // Zeigt an ob PUK bereits in DB vorhanden isMultisim: boolean; isMain: boolean; + cardUser: string; } const [simCards, setSimCards] = useState([]); @@ -379,6 +380,7 @@ export default function ContractForm() { hasExistingPuk: !!sc.puk, // true wenn verschlüsselter Wert vorhanden isMultisim: sc.isMultisim, isMain: sc.isMain, + cardUser: sc.cardUser || '', }))); } else { setSimCards([]); @@ -610,6 +612,7 @@ export default function ContractForm() { puk: sc.puk || undefined, // Passwort: undefined = nicht ändern isMultisim: sc.isMultisim, isMain: sc.isMain, + cardUser: emptyToNull(sc.cardUser), })) : undefined, }; } @@ -1490,6 +1493,18 @@ export default function ContractForm() {
+
+ { + const updated = [...simCards]; + updated[index].cardUser = e.target.value; + setSimCards(updated); + }} + placeholder="z.B. Mitarbeiter-/Kind-Name (optional, kann vom Vertragsinhaber abweichen)" + /> +
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 67045355..ee0859e2 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -557,6 +557,9 @@ export interface SimCard { puk?: string; // verschlüsselt isMultisim: boolean; isMain: boolean; + // Tatsächlicher Nutzer der Karte (z.B. Mitarbeiter bei Firmenvertrag, + // Kind bei Eltern-Vertrag) – optional, kann vom Vertragsinhaber abweichen. + cardUser?: string; createdAt?: string; updatedAt?: string; }