From 5508d59652333149f1a303d855bdfaee1ef2640b Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 3 Jun 2026 16:13:24 +0200 Subject: [PATCH] SIM-Karten: Checkbox "eSIM" zwischen Hauptkarte und Multisim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../20260603100000_sim_card_esim/migration.sql | 8 ++++++++ backend/prisma/schema.prisma | 1 + backend/src/services/contract.service.ts | 6 ++++++ docs/todo.md | 11 +++++++++++ frontend/src/pages/contracts/ContractDetail.tsx | 1 + frontend/src/pages/contracts/ContractForm.tsx | 17 +++++++++++++++++ frontend/src/types/index.ts | 1 + 7 files changed, 45 insertions(+) create mode 100644 backend/prisma/migrations/20260603100000_sim_card_esim/migration.sql diff --git a/backend/prisma/migrations/20260603100000_sim_card_esim/migration.sql b/backend/prisma/migrations/20260603100000_sim_card_esim/migration.sql new file mode 100644 index 00000000..403f8175 --- /dev/null +++ b/backend/prisma/migrations/20260603100000_sim_card_esim/migration.sql @@ -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; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 7feed462..194f8113 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -934,6 +934,7 @@ 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? + isEsim Boolean @default(false) // Ist dies eine eSIM? // Tatsächlicher Nutzer der SIM-Karte (z.B. Mitarbeiter bei Firmenverträgen, // Kind bei Eltern-Vertrag) – kann vom Vertragsinhaber abweichen. cardUser String? diff --git a/backend/src/services/contract.service.ts b/backend/src/services/contract.service.ts index 2614458b..aa23c0f6 100644 --- a/backend/src/services/contract.service.ts +++ b/backend/src/services/contract.service.ts @@ -276,6 +276,7 @@ interface ContractCreateData { puk?: string; isMultisim?: boolean; isMain?: boolean; + isEsim?: boolean; cardUser?: string; }[]; }; @@ -381,6 +382,7 @@ export async function createContract(data: ContractCreateData) { puk: sc.puk ? encrypt(sc.puk) : undefined, isMultisim: sc.isMultisim ?? false, isMain: sc.isMain ?? false, + isEsim: sc.isEsim ?? false, cardUser: sc.cardUser, })), } @@ -620,6 +622,7 @@ export async function updateContract( puk: sc.puk ? encrypt(sc.puk) : (existingSc?.puk ?? undefined), isMultisim: sc.isMultisim ?? false, isMain: sc.isMain ?? false, + isEsim: sc.isEsim ?? false, cardUser: sc.cardUser, }; }), @@ -639,6 +642,7 @@ export async function updateContract( puk: sc.puk ? encrypt(sc.puk) : undefined, isMultisim: sc.isMultisim ?? false, isMain: sc.isMain ?? false, + isEsim: sc.isEsim ?? false, cardUser: sc.cardUser, })), } @@ -766,6 +770,7 @@ export async function createFollowUpContract(previousContractId: number) { simCardNumber: sc.simCardNumber ?? undefined, isMultisim: sc.isMultisim, isMain: sc.isMain, + isEsim: sc.isEsim, })), }; } @@ -990,6 +995,7 @@ export async function createRenewalContract(previousContractId: number) { simCardNumber: sc.simCardNumber, isMultisim: sc.isMultisim, isMain: sc.isMain, + isEsim: sc.isEsim, pin: sc.pin, puk: sc.puk, }, diff --git a/docs/todo.md b/docs/todo.md index 9510a579..37fed902 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,17 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ 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)** - 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 diff --git a/frontend/src/pages/contracts/ContractDetail.tsx b/frontend/src/pages/contracts/ContractDetail.tsx index 5ebcd4d4..e6925dee 100644 --- a/frontend/src/pages/contracts/ContractDetail.tsx +++ b/frontend/src/pages/contracts/ContractDetail.tsx @@ -138,6 +138,7 @@ function SimCardDisplay({ simCard }: { simCard: SimCard }) {
{simCard.isMain && Hauptkarte} + {simCard.isEsim && eSIM} {simCard.isMultisim && Multisim} {simCard.cardUser && ( diff --git a/frontend/src/pages/contracts/ContractForm.tsx b/frontend/src/pages/contracts/ContractForm.tsx index c91f3437..9b480d1a 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; + isEsim: boolean; cardUser: string; } const [simCards, setSimCards] = useState([]); @@ -384,6 +385,7 @@ export default function ContractForm() { hasExistingPuk: !!sc.puk, // true wenn verschlüsselter Wert vorhanden isMultisim: sc.isMultisim, isMain: sc.isMain, + isEsim: sc.isEsim ?? false, cardUser: sc.cardUser || '', }))); } else { @@ -650,6 +652,7 @@ export default function ContractForm() { puk: sc.puk || undefined, // Passwort: undefined = nicht ändern isMultisim: sc.isMultisim, isMain: sc.isMain, + isEsim: sc.isEsim, cardUser: emptyToNull(sc.cardUser), })) : undefined, }; @@ -1517,6 +1520,19 @@ export default function ContractForm() { /> Hauptkarte +