diff --git a/Manager/module.php b/Manager/module.php index ee2406b..b4cd7a1 100644 --- a/Manager/module.php +++ b/Manager/module.php @@ -65,7 +65,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle 'Name' => 'Verbraucher einstufig', ], '{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}' => [ - 'Katalog' => 'consumer_single', + 'Katalog' => 'heat_pump', 'Name' => 'Waermepumpe', ], '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}' => [ @@ -1127,19 +1127,23 @@ class Manager extends IPSModule implements ManagerSchnittstelle private function pruefeVerbraucherlizenzen(array $ids): void { [$mengen, $namen] = $this->verbraucherLizenzbelegung($ids); + $lease = $this->leseLizenzLease(); + $pruefmengen = Lizenzpruefung::beruecksichtigeBestandslizenzFuerWaermepumpen( + $lease, + $mengen + ); try { - Lizenzpruefung::pruefeVerbrauchermengen($this->leseLizenzLease(), $mengen); + Lizenzpruefung::pruefeVerbrauchermengen($lease, $pruefmengen); } catch (InvalidArgumentException $fehler) { - foreach ($mengen as $kennung => $verwendet) { - $lizenziert = Lizenzpruefung::berechtigungsMenge( - $this->leseLizenzLease(), - $kennung - ); + foreach ($pruefmengen as $kennung => $verwendet) { + $lizenziert = Lizenzpruefung::berechtigungsMenge($lease, $kennung); if ($verwendet > $lizenziert) { + $name = $namen[$kennung] + ?? ($kennung === 'consumer_single' ? 'Waermepumpe (Bestandslizenz)' : $kennung); throw new InvalidArgumentException(sprintf( '%s: %d aktiviert, aber nur %d lizenziert. Bitte Auswahl reduzieren oder Lizenz erweitern.', - $namen[$kennung], + $name, $verwendet, $lizenziert ), 0, $fehler); @@ -2329,7 +2333,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle [$mengen] = $this->verbraucherLizenzbelegung( $this->konfigurierteAktiveVerbraucherIDs() ); - Lizenzpruefung::pruefeVerbrauchermengen($lease, $mengen); + $pruefmengen = Lizenzpruefung::beruecksichtigeBestandslizenzFuerWaermepumpen( + $lease, + $mengen + ); + Lizenzpruefung::pruefeVerbrauchermengen($lease, $pruefmengen); return true; } catch (Throwable $fehler) { diff --git a/libs/Lizenzpruefung.php b/libs/Lizenzpruefung.php index 6c650ba..dcd267e 100644 --- a/libs/Lizenzpruefung.php +++ b/libs/Lizenzpruefung.php @@ -117,6 +117,42 @@ final class Lizenzpruefung return $menge; } + /** + * Rechnet freie, bestehende Einzellizenzen auf nicht separat lizenzierte Waermepumpen an. + * + * @param array $lease + * @param array $verwendeteMengen + * @return array + */ + 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 $lease * @param array $verwendeteMengen diff --git a/tests/LizenzpruefungTest.php b/tests/LizenzpruefungTest.php index 4646463..b9c736f 100644 --- a/tests/LizenzpruefungTest.php +++ b/tests/LizenzpruefungTest.php @@ -173,6 +173,61 @@ final class LizenzpruefungTest extends TestCase self::assertTrue(true); } + public function testSeparateWaermepumpenlizenzBleibtUnveraendert(): void + { + $lease = $this->lease('manager_standard'); + $lease['entitlements'][] = [ + 'catalog_key' => 'heat_pump', + 'quantity' => 1, + 'status' => 'active', + ]; + $verwendung = ['heat_pump' => 1]; + + $pruefmengen = Lizenzpruefung::beruecksichtigeBestandslizenzFuerWaermepumpen( + $lease, + $verwendung + ); + + self::assertSame($verwendung, $pruefmengen); + Lizenzpruefung::pruefeVerbrauchermengen($lease, $pruefmengen); + } + + public function testFreieBestandslizenzDecktEineWaermepumpeAb(): void + { + $lease = $this->lease('manager_standard'); + $lease['entitlements'][] = [ + 'catalog_key' => 'consumer_single', + 'quantity' => 1, + 'status' => 'active', + ]; + + $pruefmengen = Lizenzpruefung::beruecksichtigeBestandslizenzFuerWaermepumpen( + $lease, + ['heat_pump' => 1] + ); + + self::assertSame(['heat_pump' => 0, 'consumer_single' => 1], $pruefmengen); + Lizenzpruefung::pruefeVerbrauchermengen($lease, $pruefmengen); + } + + public function testBestandslizenzDarfNichtDoppeltVerwendetWerden(): void + { + $lease = $this->lease('manager_standard'); + $lease['entitlements'][] = [ + 'catalog_key' => 'consumer_single', + 'quantity' => 1, + 'status' => 'active', + ]; + $pruefmengen = Lizenzpruefung::beruecksichtigeBestandslizenzFuerWaermepumpen( + $lease, + ['consumer_single' => 1, 'heat_pump' => 1] + ); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Lizenzkontingent fuer heat_pump ueberschritten'); + Lizenzpruefung::pruefeVerbrauchermengen($lease, $pruefmengen); + } + public function testLizenzcodeWirdWieAufDemServerNormalisiert(): void { self::assertSame( diff --git a/tests/ManagerModulstrukturTest.php b/tests/ManagerModulstrukturTest.php index fcdd65a..1356df1 100644 --- a/tests/ManagerModulstrukturTest.php +++ b/tests/ManagerModulstrukturTest.php @@ -169,12 +169,13 @@ final class ManagerModulstrukturTest extends TestCase 'Konstant und Monatlich benoetigen eine Peak-Shaving-Lizenz.', $inhalt ); - foreach (['battery', 'buffer_storage', 'ev_charger', 'consumer_single', 'boiler_multi'] as $katalog) { + foreach (['battery', 'buffer_storage', 'ev_charger', 'consumer_single', 'heat_pump', 'boiler_multi'] as $katalog) { self::assertStringContainsString("'Katalog' => '" . $katalog . "'", $inhalt); } foreach ([ '{B7C54AF4-AD7D-4FE4-B75D-203693906251}', '{15879A4E-D0C2-4495-83DE-46E1E462591E}', + '{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}', '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', '{32F65988-BE55-4FD6-9FFF-CF1C4C8628F8}', '{C92D5EEF-9632-47A5-9659-4B02BF40FBE9}',