MaLo-ID an die Lieferadresse (Strom/Gas) + Address-owner-Regression gefixt
MaLo-ID (Marktlokation) gehoert zur (Liefer-)Adresse, nicht zum Vertrag.
Address bekommt maloIdElectricity + maloIdGas (getrennte Marktlokationen je
Sparte), pflegbar im AddressModal (nur Lieferadresse). Im Vertrag ist die
MaLo-ID jetzt ein Lesefeld, das je nach Vertragstyp die MaLo der gewaehlten
Lieferadresse zeigt; ContractDetail/-Modal ebenso.
Schema + Migration 20260814100000: 2 Spalten (idempotent) + Daten-Migration
(bestehende EnergyContractDetails.maloId -> jeweilige Lieferadresse,
ELECTRICITY->maloIdElectricity / GAS->maloIdGas). Migrationslogik verifiziert.
Dabei einen selbst verursachten Regressions-Bug gefixt: beim R156-Umbau waren
die 10 owner*-Adressfelder aus der Address-Whitelist gefallen -> Eigentuemer-
Sektion speicherte seit cb21a2c nicht mehr. Address-Whitelist jetzt via
Pick-Helper, programmatisch gegen alle DB-Spalten abgeglichen (owner* + MaLo
drin, id/customerId/Timestamps raus). BankCard/Document gegengeprueft: ok
(nur documentPath bewusst upload-only ausgeschlossen).
Verifiziert: tsc+build gruen; owner + maloId speichern wieder, Injection
(id/customerId) blockiert; Daten-Migration Strom->Strom / Gas->Gas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -134,15 +134,19 @@ export default function ContractDetailModal({ contractId, isOpen, onClose }: Con
|
||||
<CopyButton value={c.energyDetails.meter.meterNumber} />
|
||||
</dd>
|
||||
</div>
|
||||
{c.energyDetails.maloId && (
|
||||
<div>
|
||||
<dt className="text-sm text-gray-500">MaLo-ID</dt>
|
||||
<dd className="font-mono flex items-center gap-1">
|
||||
{c.energyDetails.maloId}
|
||||
<CopyButton value={c.energyDetails.maloId} />
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{(() => {
|
||||
// MaLo-ID kommt jetzt aus der Lieferadresse (je Sparte).
|
||||
const malo = c.type === 'ELECTRICITY' ? c.address?.maloIdElectricity : c.address?.maloIdGas;
|
||||
return malo ? (
|
||||
<div>
|
||||
<dt className="text-sm text-gray-500">MaLo-ID ({c.type === 'ELECTRICITY' ? 'Strom' : 'Gas'})</dt>
|
||||
<dd className="font-mono flex items-center gap-1">
|
||||
{malo}
|
||||
<CopyButton value={malo} />
|
||||
</dd>
|
||||
</div>
|
||||
) : null;
|
||||
})()}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -2812,15 +2812,19 @@ export default function ContractDetail() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{c.energyDetails.maloId && (
|
||||
<div>
|
||||
<dt className="text-sm text-gray-500">MaLo-ID</dt>
|
||||
<dd className="font-mono flex items-center gap-1">
|
||||
{c.energyDetails.maloId}
|
||||
<CopyButton value={c.energyDetails.maloId} />
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{(() => {
|
||||
// MaLo-ID kommt jetzt aus der Lieferadresse (je Sparte).
|
||||
const malo = c.type === 'ELECTRICITY' ? c.address?.maloIdElectricity : c.address?.maloIdGas;
|
||||
return malo ? (
|
||||
<div>
|
||||
<dt className="text-sm text-gray-500">MaLo-ID ({c.type === 'ELECTRICITY' ? 'Strom' : 'Gas'})</dt>
|
||||
<dd className="font-mono flex items-center gap-1">
|
||||
{malo}
|
||||
<CopyButton value={malo} />
|
||||
</dd>
|
||||
</div>
|
||||
) : null;
|
||||
})()}
|
||||
{c.energyDetails.annualConsumption ? (
|
||||
<div>
|
||||
<dt className="text-sm text-gray-500">
|
||||
|
||||
@@ -349,7 +349,6 @@ export default function ContractForm() {
|
||||
notes: c.notes || '',
|
||||
// Energy details
|
||||
meterId: c.energyDetails?.meterId?.toString() || '',
|
||||
maloId: c.energyDetails?.maloId || '',
|
||||
annualConsumption: c.energyDetails?.annualConsumption || '',
|
||||
annualConsumptionKwh: c.energyDetails?.annualConsumptionKwh || '',
|
||||
basePrice: c.energyDetails?.basePrice || '',
|
||||
@@ -672,7 +671,7 @@ export default function ContractForm() {
|
||||
if (['ELECTRICITY', 'GAS'].includes(data.type)) {
|
||||
contractData.energyDetails = {
|
||||
meterId: safeParseInt(data.meterId) ?? null,
|
||||
maloId: emptyToNull(data.maloId),
|
||||
// maloId wird nicht mehr am Vertrag gepflegt – sie gehört zur Lieferadresse.
|
||||
annualConsumption: data.annualConsumption ? parseFloat(data.annualConsumption) : null,
|
||||
annualConsumptionKwh: data.annualConsumptionKwh ? parseFloat(data.annualConsumptionKwh) : null,
|
||||
basePrice: data.basePrice ? parseFloat(data.basePrice) : null,
|
||||
@@ -1448,10 +1447,46 @@ export default function ContractForm() {
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
<Input
|
||||
label="MaLo-ID (Marktlokations-ID)"
|
||||
{...register('maloId')}
|
||||
/>
|
||||
{(() => {
|
||||
// MaLo-ID gehört zur Lieferadresse (nicht zum Vertrag) → Lesefeld,
|
||||
// Wert je nach Sparte aus der gewählten Lieferadresse.
|
||||
const selAddr = addresses.find((a) => String(a.id) === watch('addressId'));
|
||||
const isElectricity = contractType === 'ELECTRICITY';
|
||||
const maloValue = isElectricity ? selAddr?.maloIdElectricity : selAddr?.maloIdGas;
|
||||
const sparte = isElectricity ? 'Strom' : 'Gas';
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
MaLo-ID (Marktlokations-ID {sparte})
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={maloValue || ''}
|
||||
readOnly
|
||||
placeholder={!watch('addressId')
|
||||
? 'Erst Lieferadresse wählen…'
|
||||
: `Keine MaLo-ID ${sparte} an der Lieferadresse hinterlegt`}
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
/>
|
||||
{maloValue && <CopyButton value={maloValue} />}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Wird aus der Lieferadresse übernommen –{' '}
|
||||
{customerId ? (
|
||||
<a
|
||||
href={`/customers/${customerId}?tab=addresses`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
dort pflegen
|
||||
</a>
|
||||
) : 'in der Kundenakte an der Adresse pflegen'}.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div>
|
||||
<Input
|
||||
label={`Jahresverbrauch (${contractType === 'ELECTRICITY' ? 'kWh' : 'm³'})`}
|
||||
|
||||
@@ -2490,6 +2490,8 @@ function AddressModal({
|
||||
city: address?.city || '',
|
||||
country: address?.country || 'Deutschland',
|
||||
isDefault: address?.isDefault || false,
|
||||
maloIdElectricity: address?.maloIdElectricity || '',
|
||||
maloIdGas: address?.maloIdGas || '',
|
||||
ownerCompany: address?.ownerCompany || '',
|
||||
ownerFirstName: address?.ownerFirstName || '',
|
||||
ownerLastName: address?.ownerLastName || '',
|
||||
@@ -2605,6 +2607,31 @@ function AddressModal({
|
||||
Als Standard setzen
|
||||
</label>
|
||||
|
||||
{/* Marktlokations-IDs (nur Lieferadresse) – gehören zur Adresse, nicht
|
||||
zum Vertrag; im Vertrag werden sie je Sparte nur noch angezeigt. */}
|
||||
{formData.type === 'DELIVERY_RESIDENCE' && (
|
||||
<div className="pt-4 border-t">
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-1">Marktlokations-IDs (MaLo)</h4>
|
||||
<p className="text-xs text-gray-500 mb-3">
|
||||
Je Sparte getrennt. Wird im Strom-/Gasvertrag automatisch aus dieser Lieferadresse angezeigt.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Input
|
||||
label="MaLo-ID Strom"
|
||||
value={formData.maloIdElectricity}
|
||||
onChange={(e) => setFormData({ ...formData, maloIdElectricity: e.target.value })}
|
||||
placeholder="z.B. 11XXXXXXXXXXX"
|
||||
/>
|
||||
<Input
|
||||
label="MaLo-ID Gas"
|
||||
value={formData.maloIdGas}
|
||||
onChange={(e) => setFormData({ ...formData, maloIdGas: e.target.value })}
|
||||
placeholder="z.B. 11XXXXXXXXXXX"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Eigentümer (optional, nur bei Liefer-/Meldeadresse) */}
|
||||
{formData.type === 'DELIVERY_RESIDENCE' && (
|
||||
<div className="pt-4 border-t">
|
||||
|
||||
@@ -260,6 +260,9 @@ export interface Address {
|
||||
city: string;
|
||||
country: string;
|
||||
isDefault: boolean;
|
||||
// Marktlokations-IDs (MaLo) je Sparte – gehören zur (Liefer-)Adresse
|
||||
maloIdElectricity?: string;
|
||||
maloIdGas?: string;
|
||||
// Eigentümer (leer = Kunde ist selbst Eigentümer)
|
||||
ownerCompany?: string;
|
||||
ownerFirstName?: string;
|
||||
|
||||
Reference in New Issue
Block a user