diff --git a/NEXT.md b/NEXT.md index 29d2ad1..8c64061 100644 --- a/NEXT.md +++ b/NEXT.md @@ -1,6 +1,7 @@ # NEXT_STEPS ## Aktive Aufgaben (priorisiert) +- [x] [#26] patchfelder haben natürlich auf den gleichen port eine feste verdrahtung und dann ein patchkabel zum switch, bei wand buchsen muss das auch erlaubt sein - [x] [#24] infrastruktur stockerkkarte zoomen wird die grundrisskarten overlay nicht mitgezoomt - [x] [#23] netzwerkdosen haben nur port 1 und brauche in den auswahlen nicht mit port 1 angezeigt zu werden - [x] [#22] für neue verbindungen nur ports anbieten die noch keine verbingung haben diff --git a/app/api/connections.php b/app/api/connections.php index d2c2ff5..c67d792 100644 --- a/app/api/connections.php +++ b/app/api/connections.php @@ -90,6 +90,87 @@ function isTopologyPairAllowed(string $typeA, string $typeB): bool return true; } +function buildEndpointUsageMap($sql, int $excludeConnectionId = 0): array +{ + $usage = [ + 'device' => [], + 'module' => [], + 'outlet' => [], + 'patchpanel' => [], + ]; + + $rows = $sql->get( + "SELECT id, port_a_type, port_a_id, port_b_type, port_b_id + FROM connections + WHERE id <> ?", + 'i', + [$excludeConnectionId] + ); + + $track = static function (string $endpointType, int $endpointId, string $otherType) use (&$usage): void { + if ($endpointId <= 0 || !isset($usage[$endpointType])) { + return; + } + if (!isset($usage[$endpointType][$endpointId])) { + $usage[$endpointType][$endpointId] = [ + 'total' => 0, + 'fixed' => 0, + 'patch' => 0, + ]; + } + + $usage[$endpointType][$endpointId]['total']++; + if (in_array($endpointType, ['outlet', 'patchpanel'], true)) { + if (in_array($otherType, ['outlet', 'patchpanel'], true)) { + $usage[$endpointType][$endpointId]['fixed']++; + } elseif (in_array($otherType, ['device', 'module'], true)) { + $usage[$endpointType][$endpointId]['patch']++; + } + } + }; + + foreach ((array)$rows as $row) { + $typeA = normalizeEndpointType((string)($row['port_a_type'] ?? '')); + $typeB = normalizeEndpointType((string)($row['port_b_type'] ?? '')); + $idA = (int)($row['port_a_id'] ?? 0); + $idB = (int)($row['port_b_id'] ?? 0); + if ($typeA === null || $typeB === null) { + continue; + } + $track($typeA, $idA, $typeB); + $track($typeB, $idB, $typeA); + } + + return $usage; +} + +function validateEndpointCapacity(array $usage, string $endpointType, int $endpointId, string $otherType, string $label): ?string +{ + if ($endpointId <= 0) { + return null; + } + + $stats = $usage[$endpointType][$endpointId] ?? ['total' => 0, 'fixed' => 0, 'patch' => 0]; + if ((int)$stats['total'] <= 0) { + return null; + } + + if (in_array($endpointType, ['outlet', 'patchpanel'], true)) { + if ((int)$stats['total'] >= 2) { + return $label . ' hat bereits die maximale Anzahl von 2 Verbindungen'; + } + if (in_array($otherType, ['outlet', 'patchpanel'], true) && (int)$stats['fixed'] >= 1) { + return $label . ' hat bereits eine feste Verdrahtung'; + } + if (in_array($otherType, ['device', 'module'], true) && (int)$stats['patch'] >= 1) { + return $label . ' hat bereits ein Patchkabel'; + } + return null; + } + + return $label . ' ist bereits in Verwendung'; +} + function loadConnections($sql): void { $contextType = strtolower(trim((string)($_GET['context_type'] ?? 'all'))); @@ -226,9 +307,20 @@ function saveConnection($sql): void $mode = isset($data['mode']) ? (string)$data['mode'] : null; $comment = isset($data['comment']) ? (string)$data['comment'] : null; - if (!empty($data['id'])) { - $id = (int)$data['id']; - $existing = $sql->single('SELECT id FROM connections WHERE id = ?', 'i', [$id]); + $connectionId = !empty($data['id']) ? (int)$data['id'] : 0; + $usage = buildEndpointUsageMap($sql, $connectionId); + $capacityErrorA = validateEndpointCapacity($usage, $portAType, $portAId, $portBType, 'Port an Endpunkt A'); + if ($capacityErrorA !== null) { + jsonError($capacityErrorA, 409); + } + $capacityErrorB = validateEndpointCapacity($usage, $portBType, $portBId, $portAType, 'Port an Endpunkt B'); + if ($capacityErrorB !== null) { + jsonError($capacityErrorB, 409); + } + + if ($connectionId > 0) { + $id = $connectionId; + $existing = $sql->single('SELECT id FROM connections WHERE id = ?', 'i', [$connectionId]); if (!$existing) { jsonError('Verbindung existiert nicht', 404); } diff --git a/app/modules/connections/edit.php b/app/modules/connections/edit.php index 68006a6..84a14a3 100644 --- a/app/modules/connections/edit.php +++ b/app/modules/connections/edit.php @@ -46,7 +46,7 @@ $endpointOptions = [ 'patchpanel' => [], ]; -$occupiedByType = [ +$occupiedStatsByType = [ 'device' => [], 'module' => [], 'outlet' => [], @@ -62,18 +62,24 @@ $occupiedRows = $sql->get( foreach ((array)$occupiedRows as $row) { $typeA = $normalizePortType((string)($row['port_a_type'] ?? '')); $idA = (int)($row['port_a_id'] ?? 0); - if ($idA > 0 && isset($occupiedByType[$typeA])) { - $occupiedByType[$typeA][$idA] = true; + if ($idA > 0 && isset($occupiedStatsByType[$typeA])) { + if (!isset($occupiedStatsByType[$typeA][$idA])) { + $occupiedStatsByType[$typeA][$idA] = ['total' => 0]; + } + $occupiedStatsByType[$typeA][$idA]['total']++; } $typeB = $normalizePortType((string)($row['port_b_type'] ?? '')); $idB = (int)($row['port_b_id'] ?? 0); - if ($idB > 0 && isset($occupiedByType[$typeB])) { - $occupiedByType[$typeB][$idB] = true; + if ($idB > 0 && isset($occupiedStatsByType[$typeB])) { + if (!isset($occupiedStatsByType[$typeB][$idB])) { + $occupiedStatsByType[$typeB][$idB] = ['total' => 0]; + } + $occupiedStatsByType[$typeB][$idB]['total']++; } } -$isEndpointAllowed = static function (string $type, int $id) use ($occupiedByType, $portAType, $portAId, $portBType, $portBId): bool { +$isEndpointAllowed = static function (string $type, int $id) use ($occupiedStatsByType, $portAType, $portAId, $portBType, $portBId): bool { if ($id <= 0) { return false; } @@ -83,7 +89,10 @@ $isEndpointAllowed = static function (string $type, int $id) use ($occupiedByTyp if ($type === $portBType && $id === $portBId) { return true; } - return empty($occupiedByType[$type][$id]); + + $stats = $occupiedStatsByType[$type][$id] ?? ['total' => 0]; + $maxConnections = in_array($type, ['outlet', 'patchpanel'], true) ? 2 : 1; + return (int)($stats['total'] ?? 0) < $maxConnections; }; // Auto-heal: ensure each outlet has at least one selectable port. diff --git a/app/modules/connections/save.php b/app/modules/connections/save.php index 68faebb..e5daf5d 100644 --- a/app/modules/connections/save.php +++ b/app/modules/connections/save.php @@ -75,16 +75,26 @@ $otherConnections = $sql->get( ); $endpointUsage = []; -$trackUsage = static function (string $endpointType, int $endpointId) use (&$endpointUsage): void { +$trackUsage = static function (string $endpointType, int $endpointId, string $otherType) use (&$endpointUsage): void { if ($endpointId <= 0) { return; } if (!isset($endpointUsage[$endpointType][$endpointId])) { $endpointUsage[$endpointType][$endpointId] = [ 'total' => 0, + 'fixed' => 0, + 'patch' => 0, ]; } $endpointUsage[$endpointType][$endpointId]['total']++; + + if (in_array($endpointType, ['outlet', 'patchpanel'], true)) { + if (in_array($otherType, ['outlet', 'patchpanel'], true)) { + $endpointUsage[$endpointType][$endpointId]['fixed']++; + } elseif (in_array($otherType, ['device', 'module'], true)) { + $endpointUsage[$endpointType][$endpointId]['patch']++; + } + } }; foreach ((array)$otherConnections as $row) { @@ -93,29 +103,42 @@ foreach ((array)$otherConnections as $row) { $idA = (int)($row['port_a_id'] ?? 0); $idB = (int)($row['port_b_id'] ?? 0); - $trackUsage($typeA, $idA); - $trackUsage($typeB, $idB); + $trackUsage($typeA, $idA, $typeB); + $trackUsage($typeB, $idB, $typeA); } -$validateEndpointUsage = static function (string $endpointType, int $endpointId, string $label) use ($endpointUsage): ?string { +$validateEndpointUsage = static function (string $endpointType, int $endpointId, string $otherType, string $label) use ($endpointUsage): ?string { if ($endpointId <= 0) { return null; } - $stats = $endpointUsage[$endpointType][$endpointId] ?? ['total' => 0]; + $stats = $endpointUsage[$endpointType][$endpointId] ?? ['total' => 0, 'fixed' => 0, 'patch' => 0]; if ((int)$stats['total'] <= 0) { return null; } + if (in_array($endpointType, ['outlet', 'patchpanel'], true)) { + if ((int)$stats['total'] >= 2) { + return $label . " hat bereits die maximale Anzahl von 2 Verbindungen"; + } + if (in_array($otherType, ['outlet', 'patchpanel'], true) && (int)$stats['fixed'] >= 1) { + return $label . " hat bereits eine feste Verdrahtung"; + } + if (in_array($otherType, ['device', 'module'], true) && (int)$stats['patch'] >= 1) { + return $label . " hat bereits ein Patchkabel"; + } + return null; + } + return $label . " ist bereits in Verwendung"; }; -$errorA = $validateEndpointUsage($portAType, $portAId, 'Port an Endpunkt A'); +$errorA = $validateEndpointUsage($portAType, $portAId, $portBType, 'Port an Endpunkt A'); if ($errorA !== null) { $errors[] = $errorA; } -$errorB = $validateEndpointUsage($portBType, $portBId, 'Port an Endpunkt B'); +$errorB = $validateEndpointUsage($portBType, $portBId, $portAType, 'Port an Endpunkt B'); if ($errorB !== null) { $errors[] = $errorB; }