$name) { check(str_starts_with($ident, 'NetzfahrplanV4'), 'stable existing identifier retained'); check(!str_contains($name, 'V4'), 'technical version removed from display name'); } check(str_contains($anzeigen['NetzfahrplanV4Aktivtest'], 'Testbetrieb'), 'test limitation remains visible'); $module = file_get_contents(__DIR__ . '/../../Manager/module.php'); check(str_contains($module, 'ManagerPrognoseFormular::laufzeitAnzeige()'), 'runtime display migration is applied'); check(str_contains($module, 'IPS_SetHidden($objektID, true)'), 'legacy diagnostics are hidden, not deleted'); $base = json_decode(file_get_contents(__DIR__ . '/../../Manager/form.json'), true, 512, JSON_THROW_ON_ERROR); $state = [ 'prognoseAktiv' => true, 'lizenziert' => true, 'senderAktiv' => true, 'empfangAktiv' => true, 'messdatenAktiv' => true, 'legacyAktiv' => false, 'managerAktiv' => true, 'testErlaubt' => true, 'aktivtestErlaubt' => true, 'watchdogVerzichtErlaubt' => true, 'aktivtestAktiv' => false, 'ende' => 1791387487, 'jetzt' => 1791300000, ]; $form = ManagerPrognoseFormular::integrieren($base, $state); $f = fields($form); check(count($form['elements']) === count($base['elements']), 'no additional panel'); check(!str_contains(json_encode($form), 'V4 Planempfang'), 'separate V4 tab removed'); foreach (['PrognoseAktiv', 'NetzfahrplanAktiv', 'PrognoseSendeintervall', 'NetzfahrplanV4EmpfangAktiv', 'PrognoseNetzfahrplanStart', 'PrognoseNetzfahrplanStop', 'PrognoseNetzfahrplanPruefen', 'PrognoseLizenzVerwalten', 'PrognoseNetzfahrplanStatus'] as $name) { check(isset($f[$name]), 'one forecast panel contains ' . $name); } foreach ($base['elements'] as $index => $element) { if (($element['name'] ?? '') !== 'PrognoseForecast') { check($form['elements'][$index] === $element, 'unrelated panel unchanged: ' . $element['caption']); } } foreach (['PrognoseAktiv', 'PrognoseSendeintervall', 'PrognoseLizenzInformation'] as $name) { check($f[$name] === fields($base)[$name], 'existing forecast field unchanged: ' . $name); } check($f['NetzfahrplanAktiv']['visible'] === false, 'inactive legacy control hidden for configured V4'); check($f['PrognoseNetzfahrplanStart']['enabled'], 'licensed existing consent enables explicit start'); check(str_contains($f['PrognoseNetzfahrplanStart']['onClick'], "'FormNetzfahrplanSchalten', true"), 'start uses guarded manager action'); check(str_contains($f['PrognoseNetzfahrplanStop']['onClick'], "'FormNetzfahrplanSchalten', false"), 'stop uses existing stop path'); check(str_contains($f['PrognoseNetzfahrplanPruefen']['onClick'], "'NetzfahrplanV4Empfangen'"), 'plan check uses existing throttled receiver'); check(str_contains($f['PrognoseLizenzVerwalten']['onClick'], 'https://license.enelix.ch'), 'existing license portal retained'); check(str_contains($f['PrognoseNetzfahrplanStatus']['caption'], '07.10.2026 15:38:07 UTC'), 'original expiry displayed without extension'); check(str_contains($f['PrognoseNetzfahrplanStatus']['caption'], 'Kein freigegebener Dauerbetrieb'), 'test limitation remains explicit'); foreach (['prognoseAktiv', 'lizenziert', 'senderAktiv', 'empfangAktiv', 'managerAktiv', 'testErlaubt', 'aktivtestErlaubt', 'watchdogVerzichtErlaubt'] as $key) { $blocked = array_replace($state, [$key => false]); $b = fields(ManagerPrognoseFormular::integrieren($base, $blocked)); check(!$b['PrognoseNetzfahrplanStart']['enabled'], 'start blocked without ' . $key); check($b['PrognoseNetzfahrplanStop']['enabled'], 'stop remains possible without ' . $key); } foreach ([['legacyAktiv' => true], ['aktivtestAktiv' => true], ['ende' => 1791300000], ['ende' => -1]] as $change) { check(!ManagerPrognoseFormular::startErlaubt(array_replace($state, $change)), 'conflict, replay or expiry blocks start: ' . json_encode($change)); } check(!ManagerPrognoseFormular::startErlaubt([]), 'missing state fails closed'); check(ManagerPrognoseFormular::startErlaubt(array_replace($state, ['ende' => 0])), 'first start still uses existing backend consent procedure'); $b = fields(ManagerPrognoseFormular::integrieren($base, array_replace($state, ['legacyAktiv' => true, 'lizenziert' => false]))); check($b['NetzfahrplanAktiv']['visible'] && $b['NetzfahrplanAktiv']['enabled'], 'active legacy controller can be disabled despite lost license'); check(str_contains($b['PrognoseNetzfahrplanStatus']['caption'], 'konkurrier') === false && str_contains($b['PrognoseNetzfahrplanStatus']['caption'], 'gesperrt'), 'controller conflict is visible'); $b = fields(ManagerPrognoseFormular::integrieren($base, array_replace($state, ['lizenziert' => false]))); check($b['NetzfahrplanV4EmpfangAktiv']['enabled'], 'active receiver remains switchable off without license'); $b = fields(ManagerPrognoseFormular::integrieren($base, array_replace($state, ['lizenziert' => false, 'empfangAktiv' => false]))); check(!$b['NetzfahrplanV4EmpfangAktiv']['enabled'], 'unlicensed receiver cannot be enabled through form'); $b = fields(ManagerPrognoseFormular::integrieren($base, array_replace($state, [ 'senderAktiv' => false, 'empfangAktiv' => false, 'messdatenAktiv' => false, ]))); check($b['NetzfahrplanAktiv'] === fields($base)['NetzfahrplanAktiv'], 'non-migrated installation retains legacy control'); $missing = $base; $missing['elements'] = array_values(array_filter($missing['elements'], fn($e) => ($e['name'] ?? '') !== 'PrognoseForecast')); try { ManagerPrognoseFormular::integrieren($missing, $state); check(false, 'missing forecast anchor rejected'); } catch (LogicException $e) { check(true, 'missing forecast anchor rejected'); } $duplicate = $base; foreach ($base['elements'] as $element) { if (($element['name'] ?? '') === 'PrognoseForecast') { $duplicate['elements'][] = $element; } } try { ManagerPrognoseFormular::integrieren($duplicate, $state); check(false, 'duplicate forecast anchor rejected'); } catch (LogicException $e) { check(true, 'duplicate forecast anchor rejected'); } check($form === ManagerPrognoseFormular::integrieren($base, $state), 'repeated rendering is deterministic and does not modify the base form'); echo "Total: $count checks. Form generation only; no IPS or device calls.\n";