Compare commits
2
Commits
bc5d639703
...
8eb3790e7b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eb3790e7b | ||
|
|
8508bdc38e |
@@ -0,0 +1,6 @@
|
||||
-- Mobilfunknetz pro Mobilfunkvertrag (TELEKOM | VODAFONE | TELEFONICA,
|
||||
-- NULL = nicht gewählt). String statt Enum, damit weitere Netze ohne
|
||||
-- Migration ergänzbar sind.
|
||||
|
||||
ALTER TABLE `MobileContractDetails`
|
||||
ADD COLUMN IF NOT EXISTS `mobileNetwork` VARCHAR(191) NULL;
|
||||
@@ -970,6 +970,10 @@ model MobileContractDetails {
|
||||
contractId Int @unique
|
||||
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||||
requiresMultisim Boolean @default(false) // Multisim erforderlich?
|
||||
// Physisches Mobilfunknetz, auf dem der Tarif läuft: TELEKOM | VODAFONE
|
||||
// | TELEFONICA (null = nicht gewählt). Als String statt Enum gehalten,
|
||||
// damit weitere Netze ohne Migration ergänzbar sind.
|
||||
mobileNetwork String?
|
||||
dataVolume Float?
|
||||
includedMinutes Int?
|
||||
includedSMS Int?
|
||||
|
||||
@@ -279,6 +279,7 @@ interface ContractCreateData {
|
||||
};
|
||||
mobileDetails?: {
|
||||
requiresMultisim?: boolean;
|
||||
mobileNetwork?: string | null;
|
||||
dataVolume?: number;
|
||||
includedMinutes?: number;
|
||||
includedSMS?: number;
|
||||
@@ -386,6 +387,7 @@ export async function createContract(data: ContractCreateData) {
|
||||
mobileDetails: {
|
||||
create: {
|
||||
requiresMultisim: mobileDetails.requiresMultisim,
|
||||
mobileNetwork: mobileDetails.mobileNetwork ?? null,
|
||||
dataVolume: mobileDetails.dataVolume,
|
||||
includedMinutes: mobileDetails.includedMinutes,
|
||||
includedSMS: mobileDetails.includedSMS,
|
||||
|
||||
@@ -144,7 +144,14 @@ export async function withContractDocumentLock<T>(
|
||||
* Wird nach einem ContractDocument-Upload aufgerufen. Wenn der Typ eine
|
||||
* Lieferbestätigung ist:
|
||||
* - Contract.status von DRAFT auf ACTIVE setzen (falls DRAFT)
|
||||
* - Contract.startDate auf deliveryDate (oder heute) setzen, falls noch leer
|
||||
* - Contract.startDate auf das Lieferdatum setzen:
|
||||
* * Explizit eingegebenes deliveryDate → IMMER als Vertragsbeginn
|
||||
* übernehmen, auch wenn schon ein Datum gesetzt war. Die
|
||||
* Lieferbestätigung ist das maßgebliche tatsächliche Startdatum
|
||||
* und korrigiert ein evtl. vorher geschätztes Beginndatum.
|
||||
* * Kein deliveryDate angegeben → Fallback "heute", aber NUR wenn
|
||||
* startDate noch leer ist. Ein bestehendes (echtes) Datum darf
|
||||
* nicht versehentlich mit "heute" überschrieben werden.
|
||||
*
|
||||
* Schreibweise "Lieferbestätigung" stammt aus dem Frontend-Dropdown
|
||||
* (SaveAttachmentModal / ContractDetail). Vergleich case-insensitive +
|
||||
@@ -165,13 +172,12 @@ export async function maybeActivateOnDeliveryConfirmation(
|
||||
});
|
||||
if (!contract) return;
|
||||
|
||||
// deliveryDate parsen, Fallback auf heute
|
||||
// Explizit eingegebenes Lieferdatum parsen (null = keins angegeben).
|
||||
let parsedDate: Date | null = null;
|
||||
if (deliveryDate) {
|
||||
const parsed = new Date(deliveryDate);
|
||||
if (!isNaN(parsed.getTime())) parsedDate = parsed;
|
||||
}
|
||||
const effectiveDate = parsedDate || new Date();
|
||||
|
||||
const updateData: Record<string, unknown> = {};
|
||||
const changes: Record<string, { vorher: unknown; nachher: unknown }> = {};
|
||||
@@ -181,9 +187,21 @@ export async function maybeActivateOnDeliveryConfirmation(
|
||||
changes.status = { vorher: 'DRAFT', nachher: 'ACTIVE' };
|
||||
}
|
||||
|
||||
if (!contract.startDate) {
|
||||
updateData.startDate = effectiveDate;
|
||||
changes.startDate = { vorher: null, nachher: effectiveDate.toISOString().split('T')[0] };
|
||||
const asDay = (d: Date | null | undefined) =>
|
||||
d ? new Date(d).toISOString().split('T')[0] : null;
|
||||
|
||||
if (parsedDate) {
|
||||
// Explizites Lieferdatum: als Vertragsbeginn übernehmen, auch überschreibend.
|
||||
// No-op vermeiden, wenn der Tag schon exakt passt.
|
||||
if (asDay(contract.startDate) !== asDay(parsedDate)) {
|
||||
updateData.startDate = parsedDate;
|
||||
changes.startDate = { vorher: asDay(contract.startDate), nachher: asDay(parsedDate) };
|
||||
}
|
||||
} else if (!contract.startDate) {
|
||||
// Kein Datum angegeben → Fallback heute, nur bei leerem Startdatum.
|
||||
const today = new Date();
|
||||
updateData.startDate = today;
|
||||
changes.startDate = { vorher: null, nachher: asDay(today) };
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) return;
|
||||
|
||||
@@ -97,6 +97,30 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **📱 Mobilfunk: Feld „Mobilfunknetz" unter Anbieter & Tarif**
|
||||
- Neues Dropdown „Mobilfunknetz" in der Anbieter-&-Tarif-Karte, nur
|
||||
sichtbar wenn Vertragstyp = Mobilfunk. Optionen: Bitte auswählen
|
||||
(leer), Telekom, Vodafone, Telefónica.
|
||||
- Neues Feld `MobileContractDetails.mobileNetwork` (String, nullable,
|
||||
speichert TELEKOM/VODAFONE/TELEFONICA) + Migration
|
||||
`20260727100000_mobile_network` (`ADD COLUMN IF NOT EXISTS`).
|
||||
String statt Enum, damit weitere Netze ohne Migration möglich sind.
|
||||
- Anzeige in der Vertragsansicht (Mobilfunk-Details) mit lesbarem
|
||||
Netz-Namen.
|
||||
|
||||
- [x] **🐞 Lieferbestätigung: eingegebenes Datum ändert Vertragsbeginn nicht**
|
||||
- Beim Upload einer Lieferbestätigung mit Datum wurde `startDate` nur
|
||||
gesetzt, wenn es noch LEER war (`if (!contract.startDate)`). Hatte
|
||||
der Vertrag schon ein (geschätztes) Beginndatum, blieb es trotz
|
||||
eingetragenem Lieferdatum stehen.
|
||||
- Fix in `maybeActivateOnDeliveryConfirmation`: ein explizit
|
||||
eingegebenes Lieferdatum überschreibt den Vertragsbeginn jetzt IMMER
|
||||
(die Lieferbestätigung ist das maßgebliche tatsächliche Startdatum).
|
||||
Der Fallback „heute" (kein Datum eingegeben) füllt weiterhin nur ein
|
||||
leeres Feld, um ein echtes Datum nicht versehentlich zu überschreiben.
|
||||
No-op + Audit-Log unverändert. Frontend schickte das Datum bereits
|
||||
mit und lädt den Vertrag nach Upload neu – kein FE-Change nötig.
|
||||
|
||||
- [x] **🎂 Anstehende Geburtstage auch im Dashboard**
|
||||
- Die Geburtstags-Sektion gab es bisher nur im Vertrags-Cockpit.
|
||||
Jetzt 1:1 auch auf dem Dashboard (nur Mitarbeiter/Admin,
|
||||
|
||||
@@ -3077,6 +3077,12 @@ export default function ContractDetail() {
|
||||
{c.mobileDetails && (
|
||||
<Card className="mb-6" title="Mobilfunk-Details">
|
||||
<dl className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{c.mobileDetails.mobileNetwork && (
|
||||
<div>
|
||||
<dt className="text-sm text-gray-500">Mobilfunknetz</dt>
|
||||
<dd>{({ TELEKOM: 'Telekom', VODAFONE: 'Vodafone', TELEFONICA: 'Telefónica' } as Record<string, string>)[c.mobileDetails.mobileNetwork] || c.mobileDetails.mobileNetwork}</dd>
|
||||
</div>
|
||||
)}
|
||||
{c.mobileDetails.dataVolume && (
|
||||
<div>
|
||||
<dt className="text-sm text-gray-500">Datenvolumen</dt>
|
||||
|
||||
@@ -345,6 +345,7 @@ export default function ContractForm() {
|
||||
activationCode: c.internetDetails?.activationCode || '',
|
||||
// Mobile details
|
||||
requiresMultisim: c.mobileDetails?.requiresMultisim || false,
|
||||
mobileNetwork: c.mobileDetails?.mobileNetwork || '',
|
||||
dataVolume: c.mobileDetails?.dataVolume || '',
|
||||
includedMinutes: c.mobileDetails?.includedMinutes || '',
|
||||
includedSMS: c.mobileDetails?.includedSMS || '',
|
||||
@@ -656,6 +657,7 @@ export default function ContractForm() {
|
||||
if (data.type === 'MOBILE') {
|
||||
contractData.mobileDetails = {
|
||||
requiresMultisim: data.requiresMultisim || false,
|
||||
mobileNetwork: emptyToNull(data.mobileNetwork),
|
||||
dataVolume: data.dataVolume ? parseFloat(data.dataVolume) : null,
|
||||
includedMinutes: safeParseInt(data.includedMinutes) ?? null,
|
||||
includedSMS: safeParseInt(data.includedSMS) ?? null,
|
||||
@@ -1019,6 +1021,18 @@ export default function ContractForm() {
|
||||
options={availableTariffs.map((t) => ({ value: t.id, label: t.name }))}
|
||||
disabled={!selectedProviderId}
|
||||
/>
|
||||
{contractType === 'MOBILE' && (
|
||||
<Select
|
||||
label="Mobilfunknetz"
|
||||
{...register('mobileNetwork')}
|
||||
options={[
|
||||
{ value: 'TELEKOM', label: 'Telekom' },
|
||||
{ value: 'VODAFONE', label: 'Vodafone' },
|
||||
{ value: 'TELEFONICA', label: 'Telefónica' },
|
||||
]}
|
||||
placeholder="Bitte auswählen"
|
||||
/>
|
||||
)}
|
||||
<Input label="Kundennummer beim Anbieter" maxLength={100} {...register('customerNumberAtProvider')} />
|
||||
<Input label="Vertragsnummer beim Anbieter" maxLength={100} {...register('contractNumberAtProvider')} />
|
||||
<Input label="Auftragsnummer bei Vertriebsplattform" maxLength={100} {...register('orderNumberAtSalesPlatform')} />
|
||||
|
||||
@@ -588,6 +588,8 @@ export interface MobileContractDetails {
|
||||
id: number;
|
||||
contractId: number;
|
||||
requiresMultisim?: boolean;
|
||||
/** Physisches Mobilfunknetz: TELEKOM | VODAFONE | TELEFONICA (oder null). */
|
||||
mobileNetwork?: string | null;
|
||||
dataVolume?: number;
|
||||
includedMinutes?: number;
|
||||
includedSMS?: number;
|
||||
|
||||
Reference in New Issue
Block a user