SIM-Karten: Feld "Kartennutzer" für Firmen-/Familienverträge
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: <Name>" neben den Hauptkarte/Multisim-Badges angezeigt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||||
@@ -928,6 +928,9 @@ model SimCard {
|
|||||||
puk String? // PUK (verschlüsselt gespeichert)
|
puk String? // PUK (verschlüsselt gespeichert)
|
||||||
isMultisim Boolean @default(false) // Ist dies eine Multisim-Karte?
|
isMultisim Boolean @default(false) // Ist dies eine Multisim-Karte?
|
||||||
isMain Boolean @default(false) // Ist dies die Hauptkarte?
|
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())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,6 +275,7 @@ interface ContractCreateData {
|
|||||||
puk?: string;
|
puk?: string;
|
||||||
isMultisim?: boolean;
|
isMultisim?: boolean;
|
||||||
isMain?: boolean;
|
isMain?: boolean;
|
||||||
|
cardUser?: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
tvDetails?: {
|
tvDetails?: {
|
||||||
@@ -378,6 +379,7 @@ export async function createContract(data: ContractCreateData) {
|
|||||||
puk: sc.puk ? encrypt(sc.puk) : undefined,
|
puk: sc.puk ? encrypt(sc.puk) : undefined,
|
||||||
isMultisim: sc.isMultisim ?? false,
|
isMultisim: sc.isMultisim ?? false,
|
||||||
isMain: sc.isMain ?? false,
|
isMain: sc.isMain ?? false,
|
||||||
|
cardUser: sc.cardUser,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -614,6 +616,7 @@ export async function updateContract(
|
|||||||
puk: sc.puk ? encrypt(sc.puk) : (existingSc?.puk ?? undefined),
|
puk: sc.puk ? encrypt(sc.puk) : (existingSc?.puk ?? undefined),
|
||||||
isMultisim: sc.isMultisim ?? false,
|
isMultisim: sc.isMultisim ?? false,
|
||||||
isMain: sc.isMain ?? false,
|
isMain: sc.isMain ?? false,
|
||||||
|
cardUser: sc.cardUser,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -632,6 +635,7 @@ export async function updateContract(
|
|||||||
puk: sc.puk ? encrypt(sc.puk) : undefined,
|
puk: sc.puk ? encrypt(sc.puk) : undefined,
|
||||||
isMultisim: sc.isMultisim ?? false,
|
isMultisim: sc.isMultisim ?? false,
|
||||||
isMain: sc.isMain ?? false,
|
isMain: sc.isMain ?? false,
|
||||||
|
cardUser: sc.cardUser,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -134,9 +134,15 @@ function SimCardDisplay({ simCard }: { simCard: SimCard }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-3 bg-gray-50 rounded-lg border">
|
<div className="p-3 bg-gray-50 rounded-lg border">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
||||||
{simCard.isMain && <Badge variant="success">Hauptkarte</Badge>}
|
{simCard.isMain && <Badge variant="success">Hauptkarte</Badge>}
|
||||||
{simCard.isMultisim && <Badge variant="warning">Multisim</Badge>}
|
{simCard.isMultisim && <Badge variant="warning">Multisim</Badge>}
|
||||||
|
{simCard.cardUser && (
|
||||||
|
<span className="text-sm text-gray-700">
|
||||||
|
<span className="text-gray-500">Nutzer:</span>{' '}
|
||||||
|
<span className="font-medium">{simCard.cardUser}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<dl className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
<dl className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
||||||
{simCard.phoneNumber && (
|
{simCard.phoneNumber && (
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ export default function ContractForm() {
|
|||||||
hasExistingPuk?: boolean; // Zeigt an ob PUK bereits in DB vorhanden
|
hasExistingPuk?: boolean; // Zeigt an ob PUK bereits in DB vorhanden
|
||||||
isMultisim: boolean;
|
isMultisim: boolean;
|
||||||
isMain: boolean;
|
isMain: boolean;
|
||||||
|
cardUser: string;
|
||||||
}
|
}
|
||||||
const [simCards, setSimCards] = useState<SimCardInput[]>([]);
|
const [simCards, setSimCards] = useState<SimCardInput[]>([]);
|
||||||
|
|
||||||
@@ -379,6 +380,7 @@ export default function ContractForm() {
|
|||||||
hasExistingPuk: !!sc.puk, // true wenn verschlüsselter Wert vorhanden
|
hasExistingPuk: !!sc.puk, // true wenn verschlüsselter Wert vorhanden
|
||||||
isMultisim: sc.isMultisim,
|
isMultisim: sc.isMultisim,
|
||||||
isMain: sc.isMain,
|
isMain: sc.isMain,
|
||||||
|
cardUser: sc.cardUser || '',
|
||||||
})));
|
})));
|
||||||
} else {
|
} else {
|
||||||
setSimCards([]);
|
setSimCards([]);
|
||||||
@@ -610,6 +612,7 @@ export default function ContractForm() {
|
|||||||
puk: sc.puk || undefined, // Passwort: undefined = nicht ändern
|
puk: sc.puk || undefined, // Passwort: undefined = nicht ändern
|
||||||
isMultisim: sc.isMultisim,
|
isMultisim: sc.isMultisim,
|
||||||
isMain: sc.isMain,
|
isMain: sc.isMain,
|
||||||
|
cardUser: emptyToNull(sc.cardUser),
|
||||||
})) : undefined,
|
})) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1490,6 +1493,18 @@ export default function ContractForm() {
|
|||||||
<Trash2 className="w-4 h-4 text-red-500" />
|
<Trash2 className="w-4 h-4 text-red-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<Input
|
||||||
|
label="Kartennutzer"
|
||||||
|
value={card.cardUser}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updated = [...simCards];
|
||||||
|
updated[index].cardUser = e.target.value;
|
||||||
|
setSimCards(updated);
|
||||||
|
}}
|
||||||
|
placeholder="z.B. Mitarbeiter-/Kind-Name (optional, kann vom Vertragsinhaber abweichen)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||||
<Input
|
<Input
|
||||||
label="Rufnummer"
|
label="Rufnummer"
|
||||||
@@ -1578,6 +1593,7 @@ export default function ContractForm() {
|
|||||||
puk: '',
|
puk: '',
|
||||||
isMultisim: false,
|
isMultisim: false,
|
||||||
isMain: simCards.length === 0, // Erste Karte ist Hauptkarte
|
isMain: simCards.length === 0, // Erste Karte ist Hauptkarte
|
||||||
|
cardUser: '',
|
||||||
}]);
|
}]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -557,6 +557,9 @@ export interface SimCard {
|
|||||||
puk?: string; // verschlüsselt
|
puk?: string; // verschlüsselt
|
||||||
isMultisim: boolean;
|
isMultisim: boolean;
|
||||||
isMain: 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;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user