256 lines
8.3 KiB
PHP
256 lines
8.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Belevo\EnelixEMS;
|
|
|
|
use DateTimeImmutable;
|
|
use InvalidArgumentException;
|
|
use Throwable;
|
|
|
|
final class Lizenzpruefung
|
|
{
|
|
public const MANAGER_STANDARD = 'manager_standard';
|
|
public const MANAGER_PEAK = 'manager_peak';
|
|
public const PROGNOSE_PV = 'forecast_pv';
|
|
public const PROGNOSE_VERBRAUCH = 'forecast_load';
|
|
public const NETZFAHRPLAN = 'grid_schedule';
|
|
|
|
public static function normalisiereCode(string $code): string
|
|
{
|
|
return strtoupper((string) preg_replace('/[^A-Z0-9]/i', '', $code));
|
|
}
|
|
|
|
public static function istLizenzcodeGueltig(string $code): bool
|
|
{
|
|
return preg_match(
|
|
'/^ENX[A-HJ-NP-Z2-9]{16}$/',
|
|
self::normalisiereCode($code)
|
|
) === 1;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
public static function pruefeLease(
|
|
array $lease,
|
|
string $installationID,
|
|
int $lastspitzenmodus,
|
|
?int $zeitpunkt = null
|
|
): void {
|
|
if (($lease['status'] ?? null) !== 'active') {
|
|
throw new InvalidArgumentException('Die Lizenz ist nicht aktiv.');
|
|
}
|
|
if (($lease['installationId'] ?? null) !== $installationID) {
|
|
throw new InvalidArgumentException('Die Lizenz gehoert zu einer anderen Installation.');
|
|
}
|
|
if (isset($lease['development']) && !is_bool($lease['development'])) {
|
|
throw new InvalidArgumentException('Der Entwicklungsstatus der Lizenz ist ungueltig.');
|
|
}
|
|
|
|
$ausgestelltAm = self::zeitstempel($lease, 'issuedAt');
|
|
$erneuernAb = self::zeitstempel($lease, 'refreshAfter');
|
|
$offlineBis = self::zeitstempel($lease, 'offlineUntil');
|
|
if ($ausgestelltAm > $erneuernAb || $erneuernAb > $offlineBis) {
|
|
throw new InvalidArgumentException('Die Lizenzzeitpunkte sind widerspruechlich.');
|
|
}
|
|
if (($zeitpunkt ?? time()) >= $offlineBis) {
|
|
throw new InvalidArgumentException('Die Offline-Freigabe ist abgelaufen.');
|
|
}
|
|
|
|
$berechtigungen = $lease['entitlements'] ?? null;
|
|
if (!is_array($berechtigungen)) {
|
|
throw new InvalidArgumentException('Die Lizenz enthaelt keine Berechtigungen.');
|
|
}
|
|
$standard = false;
|
|
$peak = false;
|
|
foreach ($berechtigungen as $berechtigung) {
|
|
if (!is_array($berechtigung)
|
|
|| ($berechtigung['status'] ?? null) !== 'active'
|
|
|| !is_int($berechtigung['quantity'] ?? null)
|
|
|| $berechtigung['quantity'] < 1
|
|
) {
|
|
continue;
|
|
}
|
|
$kennung = $berechtigung['catalog_key'] ?? $berechtigung['sku'] ?? null;
|
|
$standard = $standard || $kennung === self::MANAGER_STANDARD;
|
|
$peak = $peak || $kennung === self::MANAGER_PEAK;
|
|
}
|
|
|
|
if (!$standard && !$peak) {
|
|
throw new InvalidArgumentException('Die Manager-Berechtigung fehlt.');
|
|
}
|
|
if ($lastspitzenmodus !== 0 && !$peak) {
|
|
throw new InvalidArgumentException('Peak Shaving ist nicht lizenziert.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Liefert die aufsummierte aktive Berechtigung fuer einen Katalogeintrag.
|
|
*
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
public static function berechtigungsMenge(array $lease, string $kennung): int
|
|
{
|
|
$berechtigungen = $lease['entitlements'] ?? null;
|
|
if (!is_array($berechtigungen)) {
|
|
return 0;
|
|
}
|
|
|
|
$menge = 0;
|
|
foreach ($berechtigungen as $berechtigung) {
|
|
if (!is_array($berechtigung)
|
|
|| ($berechtigung['status'] ?? null) !== 'active'
|
|
|| !is_int($berechtigung['quantity'] ?? null)
|
|
|| $berechtigung['quantity'] < 1
|
|
) {
|
|
continue;
|
|
}
|
|
$berechtigungsKennung = $berechtigung['catalog_key']
|
|
?? $berechtigung['sku']
|
|
?? null;
|
|
if ($berechtigungsKennung === $kennung) {
|
|
$menge += $berechtigung['quantity'];
|
|
}
|
|
}
|
|
|
|
return $menge;
|
|
}
|
|
|
|
/**
|
|
* Eine Prognose ist entweder durch den Netzfahrplan oder durch beide
|
|
* Einzelberechtigungen fuer PV und Verbrauch freigeschaltet.
|
|
*
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
public static function hatPrognoseBerechtigung(array $lease): bool
|
|
{
|
|
return self::berechtigungsMenge($lease, self::NETZFAHRPLAN) > 0
|
|
|| (self::berechtigungsMenge($lease, self::PROGNOSE_PV) > 0
|
|
&& self::berechtigungsMenge($lease, self::PROGNOSE_VERBRAUCH) > 0);
|
|
}
|
|
|
|
/**
|
|
* Rechnet freie, bestehende Einzellizenzen auf nicht separat lizenzierte Waermepumpen an.
|
|
*
|
|
* @param array<string, mixed> $lease
|
|
* @param array<string, int> $verwendeteMengen
|
|
* @return array<string, int>
|
|
*/
|
|
public static function beruecksichtigeBestandslizenzFuerWaermepumpen(
|
|
array $lease,
|
|
array $verwendeteMengen
|
|
): array {
|
|
$waermepumpen = $verwendeteMengen['heat_pump'] ?? 0;
|
|
if ($waermepumpen <= 0) {
|
|
return $verwendeteMengen;
|
|
}
|
|
|
|
$nichtSeparatLizenziert = max(
|
|
0,
|
|
$waermepumpen - self::berechtigungsMenge($lease, 'heat_pump')
|
|
);
|
|
$verbraucher = $verwendeteMengen['consumer_single'] ?? 0;
|
|
$freieBestandslizenzen = max(
|
|
0,
|
|
self::berechtigungsMenge($lease, 'consumer_single') - $verbraucher
|
|
);
|
|
$angerechnet = min($nichtSeparatLizenziert, $freieBestandslizenzen);
|
|
if ($angerechnet === 0) {
|
|
return $verwendeteMengen;
|
|
}
|
|
|
|
$verwendeteMengen['heat_pump'] = $waermepumpen - $angerechnet;
|
|
$verwendeteMengen['consumer_single'] = $verbraucher + $angerechnet;
|
|
|
|
return $verwendeteMengen;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $lease
|
|
* @param array<string, int> $verwendeteMengen
|
|
*/
|
|
public static function pruefeVerbrauchermengen(array $lease, array $verwendeteMengen): void
|
|
{
|
|
foreach ($verwendeteMengen as $kennung => $verwendet) {
|
|
if ($verwendet < 0) {
|
|
throw new InvalidArgumentException(
|
|
'Die verwendete Menge fuer ' . $kennung . ' darf nicht negativ sein.'
|
|
);
|
|
}
|
|
$lizenziert = self::berechtigungsMenge($lease, $kennung);
|
|
if ($verwendet > $lizenziert) {
|
|
throw new InvalidArgumentException(sprintf(
|
|
'Lizenzkontingent fuer %s ueberschritten: %d verwendet, %d lizenziert.',
|
|
$kennung,
|
|
$verwendet,
|
|
$lizenziert
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
public static function istLeaseGueltig(
|
|
array $lease,
|
|
string $installationID,
|
|
int $lastspitzenmodus,
|
|
?int $zeitpunkt = null
|
|
): bool {
|
|
try {
|
|
self::pruefeLease($lease, $installationID, $lastspitzenmodus, $zeitpunkt);
|
|
|
|
return true;
|
|
} catch (Throwable $fehler) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
public static function erneuerungFaellig(array $lease, ?int $zeitpunkt = null): bool
|
|
{
|
|
try {
|
|
return ($zeitpunkt ?? time()) >= self::zeitstempel($lease, 'refreshAfter');
|
|
} catch (Throwable $fehler) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
public static function offlineBis(array $lease): int
|
|
{
|
|
return self::zeitstempel($lease, 'offlineUntil');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
public static function erneuernAb(array $lease): int
|
|
{
|
|
return self::zeitstempel($lease, 'refreshAfter');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $lease
|
|
*/
|
|
private static function zeitstempel(array $lease, string $feld): int
|
|
{
|
|
$wert = $lease[$feld] ?? null;
|
|
if (!is_string($wert) || trim($wert) === '') {
|
|
throw new InvalidArgumentException($feld . ' fehlt in der Lizenz.');
|
|
}
|
|
|
|
try {
|
|
return (new DateTimeImmutable($wert))->getTimestamp();
|
|
} catch (Throwable $fehler) {
|
|
throw new InvalidArgumentException($feld . ' ist kein gueltiger ISO-8601-Zeitpunkt.', 0, $fehler);
|
|
}
|
|
}
|
|
}
|