SIM-Karten: Checkbox "eSIM" zwischen Hauptkarte und Multisim
Hardware-Plastikkarte vs. eSIM-Profil ist eigene Eigenschaft – eSIM kann sowohl Hauptkarte als auch Multisim sein, deshalb dritter Toggle statt entweder/oder. - Schema: SimCard.isEsim Boolean default false, Migration mit IF NOT EXISTS. - Backend: vier SimCard-Schreibpfade in contract.service.ts (Create, Update, Follow-Up, Renewal). - UI: dritte Checkbox in ContractForm zwischen Hauptkarte und Multisim. ContractDetail zeigt blauen eSIM-Badge.
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
-- SIM-Karte bekommt ein optionales `isEsim`-Flag – Hardware-Plastikkarte
|
||||||
|
-- vs. eSIM-Profil. UI-Position: zwischen "Hauptkarte" und "Multisim".
|
||||||
|
--
|
||||||
|
-- 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 `isEsim` BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -934,6 +934,7 @@ 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?
|
||||||
|
isEsim Boolean @default(false) // Ist dies eine eSIM?
|
||||||
// Tatsächlicher Nutzer der SIM-Karte (z.B. Mitarbeiter bei Firmenverträgen,
|
// Tatsächlicher Nutzer der SIM-Karte (z.B. Mitarbeiter bei Firmenverträgen,
|
||||||
// Kind bei Eltern-Vertrag) – kann vom Vertragsinhaber abweichen.
|
// Kind bei Eltern-Vertrag) – kann vom Vertragsinhaber abweichen.
|
||||||
cardUser String?
|
cardUser String?
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ interface ContractCreateData {
|
|||||||
puk?: string;
|
puk?: string;
|
||||||
isMultisim?: boolean;
|
isMultisim?: boolean;
|
||||||
isMain?: boolean;
|
isMain?: boolean;
|
||||||
|
isEsim?: boolean;
|
||||||
cardUser?: string;
|
cardUser?: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
@@ -381,6 +382,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,
|
||||||
|
isEsim: sc.isEsim ?? false,
|
||||||
cardUser: sc.cardUser,
|
cardUser: sc.cardUser,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
@@ -620,6 +622,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,
|
||||||
|
isEsim: sc.isEsim ?? false,
|
||||||
cardUser: sc.cardUser,
|
cardUser: sc.cardUser,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -639,6 +642,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,
|
||||||
|
isEsim: sc.isEsim ?? false,
|
||||||
cardUser: sc.cardUser,
|
cardUser: sc.cardUser,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
@@ -766,6 +770,7 @@ export async function createFollowUpContract(previousContractId: number) {
|
|||||||
simCardNumber: sc.simCardNumber ?? undefined,
|
simCardNumber: sc.simCardNumber ?? undefined,
|
||||||
isMultisim: sc.isMultisim,
|
isMultisim: sc.isMultisim,
|
||||||
isMain: sc.isMain,
|
isMain: sc.isMain,
|
||||||
|
isEsim: sc.isEsim,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -990,6 +995,7 @@ export async function createRenewalContract(previousContractId: number) {
|
|||||||
simCardNumber: sc.simCardNumber,
|
simCardNumber: sc.simCardNumber,
|
||||||
isMultisim: sc.isMultisim,
|
isMultisim: sc.isMultisim,
|
||||||
isMain: sc.isMain,
|
isMain: sc.isMain,
|
||||||
|
isEsim: sc.isEsim,
|
||||||
pin: sc.pin,
|
pin: sc.pin,
|
||||||
puk: sc.puk,
|
puk: sc.puk,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -97,6 +97,17 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ Erledigt
|
||||||
|
|
||||||
|
- [x] **🆕 SIM-Karten: Checkbox „eSIM" zwischen „Hauptkarte" und „Multisim"**
|
||||||
|
- Hardware-Plastikkarte vs. eSIM-Profil ist eine eigene Eigenschaft –
|
||||||
|
eSIM kann sowohl Hauptkarte als auch Multisim sein, also zusätzlich
|
||||||
|
statt entweder/oder.
|
||||||
|
- Schema: `SimCard.isEsim Boolean @default(false)`, Migration
|
||||||
|
`20260603100000_sim_card_esim` mit `IF NOT EXISTS`.
|
||||||
|
- Backend: alle vier SimCard-Schreibpfade in `contract.service.ts`
|
||||||
|
(Create + Update + Follow-Up + Renewal) plus FE-Type-Definition.
|
||||||
|
- UI: dritte Checkbox in `ContractForm` zwischen Hauptkarte und
|
||||||
|
Multisim. ContractDetail zeigt blauen `eSIM`-Badge neben Hauptkarte.
|
||||||
|
|
||||||
- [x] **🆕 JpgToPdfModal: PDF-Größe drastisch reduziert (Original-Bytes + Quality 0.95)**
|
- [x] **🆕 JpgToPdfModal: PDF-Größe drastisch reduziert (Original-Bytes + Quality 0.95)**
|
||||||
- Stage-Bug: 2 Handy-JPGs à 2 MB → PDF >10 MB → Multer 413. Ursache:
|
- Stage-Bug: 2 Handy-JPGs à 2 MB → PDF >10 MB → Multer 413. Ursache:
|
||||||
Canvas-Re-Encode mit JPEG-Quality 1.0 blies jedes Bild auf 8-15 MB
|
Canvas-Re-Encode mit JPEG-Quality 1.0 blies jedes Bild auf 8-15 MB
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ function SimCardDisplay({ simCard }: { simCard: SimCard }) {
|
|||||||
<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 flex-wrap">
|
<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.isEsim && <Badge variant="info">eSIM</Badge>}
|
||||||
{simCard.isMultisim && <Badge variant="warning">Multisim</Badge>}
|
{simCard.isMultisim && <Badge variant="warning">Multisim</Badge>}
|
||||||
{simCard.cardUser && (
|
{simCard.cardUser && (
|
||||||
<span className="text-sm text-gray-700">
|
<span className="text-sm text-gray-700">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
isEsim: boolean;
|
||||||
cardUser: string;
|
cardUser: string;
|
||||||
}
|
}
|
||||||
const [simCards, setSimCards] = useState<SimCardInput[]>([]);
|
const [simCards, setSimCards] = useState<SimCardInput[]>([]);
|
||||||
@@ -384,6 +385,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,
|
||||||
|
isEsim: sc.isEsim ?? false,
|
||||||
cardUser: sc.cardUser || '',
|
cardUser: sc.cardUser || '',
|
||||||
})));
|
})));
|
||||||
} else {
|
} else {
|
||||||
@@ -650,6 +652,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,
|
||||||
|
isEsim: sc.isEsim,
|
||||||
cardUser: emptyToNull(sc.cardUser),
|
cardUser: emptyToNull(sc.cardUser),
|
||||||
})) : undefined,
|
})) : undefined,
|
||||||
};
|
};
|
||||||
@@ -1517,6 +1520,19 @@ export default function ContractForm() {
|
|||||||
/>
|
/>
|
||||||
Hauptkarte
|
Hauptkarte
|
||||||
</label>
|
</label>
|
||||||
|
<label className="flex items-center gap-1 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={card.isEsim}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updated = [...simCards];
|
||||||
|
updated[index].isEsim = e.target.checked;
|
||||||
|
setSimCards(updated);
|
||||||
|
}}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
eSIM
|
||||||
|
</label>
|
||||||
<label className="flex items-center gap-1 text-sm">
|
<label className="flex items-center gap-1 text-sm">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -1642,6 +1658,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
|
||||||
|
isEsim: false,
|
||||||
cardUser: '',
|
cardUser: '',
|
||||||
}]);
|
}]);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -561,6 +561,7 @@ export interface SimCard {
|
|||||||
puk?: string; // verschlüsselt
|
puk?: string; // verschlüsselt
|
||||||
isMultisim: boolean;
|
isMultisim: boolean;
|
||||||
isMain: boolean;
|
isMain: boolean;
|
||||||
|
isEsim: boolean;
|
||||||
// Tatsächlicher Nutzer der Karte (z.B. Mitarbeiter bei Firmenvertrag,
|
// Tatsächlicher Nutzer der Karte (z.B. Mitarbeiter bei Firmenvertrag,
|
||||||
// Kind bei Eltern-Vertrag) – optional, kann vom Vertragsinhaber abweichen.
|
// Kind bei Eltern-Vertrag) – optional, kann vom Vertragsinhaber abweichen.
|
||||||
cardUser?: string;
|
cardUser?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user