kundenkarte/ajax/pwa_api.php
data 619d14e8d5 feat(pwa): FI-Schutzgruppen, gebündelte Terminals, Terminal-Konfiguration
- Schutzgruppen-Zuordnung: Equipment kann FI/RCD zugeordnet werden
  - Farbliche Markierung der Schutzgruppen im Schaltplan
  - Dropdown zur Auswahl des Schutzgeräts im Equipment-Dialog
- Gebündelte Terminals: Multi-Phasen-Abgänge (E-Herd, Durchlauferhitzer)
  - "Alle bündeln" Option im Abgang-Dialog
  - Zentriertes Label über alle Terminals des Equipment
- Terminal-Anzahl aus terminals_config statt TE-Breite
  - Neozed 3F zeigt korrekt 3 statt 4 Terminals
  - Neue getTerminalCount() Hilfsfunktion
- Zuletzt bearbeitete Kunden (max. 5) auf Search-Screen
- Medium-Typen dynamisch aus DB mit Spezifikationen-Dropdown
- Terminal-Labels anklickbar zum direkten Bearbeiten
- Kontextmenü für leere Terminals (Input/Output Auswahl)
- Block-Label mit Einheiten (40A 30mA statt 40A30mA)
- Online-Status-Anzeige entfernt (funktionierte nicht zuverlässig)
- Service Worker v5.2: Versionierte Assets nicht cachen

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-02 14:34:54 +01:00

1043 lines
32 KiB
PHP

<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* PWA API - AJAX Endpoints für die mobile App
* Token-basierte Authentifizierung
*/
if (!defined('NOLOGIN')) {
define('NOLOGIN', '1');
}
if (!defined('NOREQUIREMENU')) {
define('NOREQUIREMENU', '1');
}
if (!defined('NOREQUIREHTML')) {
define('NOREQUIREHTML', '1');
}
if (!defined('NOREQUIREAJAX')) {
define('NOREQUIREAJAX', '1');
}
// Load Dolibarr environment
$res = 0;
if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php";
}
if (!$res && file_exists("../../../main.inc.php")) {
$res = @include "../../../main.inc.php";
}
if (!$res) {
die(json_encode(array('success' => false, 'error' => 'Dolibarr not loaded')));
}
header('Content-Type: application/json; charset=UTF-8');
$response = array('success' => false);
// Verify token
$token = GETPOST('token', 'none');
if (empty($token)) {
echo json_encode(array('success' => false, 'error' => 'Nicht authentifiziert'));
exit;
}
$tokenData = json_decode(base64_decode($token), true);
if (!$tokenData || empty($tokenData['user_id']) || empty($tokenData['expires']) || $tokenData['expires'] < time()) {
echo json_encode(array('success' => false, 'error' => 'Token ungültig oder abgelaufen'));
exit;
}
// Verify hash
$expectedHash = md5($tokenData['user_id'] . $tokenData['login'] . getDolGlobalString('MAIN_SECURITY_SALT', 'defaultsalt'));
if ($tokenData['hash'] !== $expectedHash) {
echo json_encode(array('success' => false, 'error' => 'Token manipuliert'));
exit;
}
// Load user
require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
$user = new User($db);
$user->fetch($tokenData['user_id']);
$user->getrights();
if ($user->id <= 0 || $user->statut != 1) {
echo json_encode(array('success' => false, 'error' => 'Benutzer nicht mehr aktiv'));
exit;
}
// Check permission
if (!$user->hasRight('kundenkarte', 'read')) {
echo json_encode(array('success' => false, 'error' => 'Keine Berechtigung'));
exit;
}
// Load language file for labels
$langs->loadLangs(array('kundenkarte@kundenkarte'));
// Load required classes
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/anlage.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentpanel.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipment.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmenttype.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentconnection.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/mediumtype.class.php';
$action = GETPOST('action', 'aZ09');
switch ($action) {
// ============================================
// CUSTOMER SEARCH
// ============================================
case 'search_customers':
$query = GETPOST('query', 'alphanohtml');
if (strlen($query) < 2) {
$response['error'] = 'Mindestens 2 Zeichen';
break;
}
$sql = "SELECT s.rowid, s.nom as name, s.town";
$sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
$sql .= " WHERE s.entity IN (".getEntity('societe').")";
$sql .= " AND s.status = 1";
$sql .= " AND (s.nom LIKE '%".$db->escape($query)."%'";
$sql .= " OR s.name_alias LIKE '%".$db->escape($query)."%')";
$sql .= " ORDER BY s.nom ASC";
$sql .= " LIMIT 30";
$resql = $db->query($sql);
$customers = array();
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$customers[] = array(
'id' => $obj->rowid,
'name' => $obj->name,
'town' => $obj->town
);
}
}
$response['success'] = true;
$response['customers'] = $customers;
break;
// ============================================
// GET ANLAGEN FOR CUSTOMER (inkl. Kontakt-Adressen)
// ============================================
case 'get_anlagen':
$customerId = GETPOSTINT('customer_id');
if ($customerId <= 0) {
$response['error'] = 'Keine Kunden-ID';
break;
}
// Feld-Metadaten laden (show_in_tree Felder für Anzeige)
$fieldMeta = array();
$sqlFields = "SELECT fk_anlage_type, field_code, field_label, tree_display_mode, badge_color, show_in_tree, field_type";
$sqlFields .= " FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field";
$sqlFields .= " WHERE active = 1 AND show_in_tree = 1";
$sqlFields .= " ORDER BY position";
$resFields = $db->query($sqlFields);
if ($resFields) {
while ($fObj = $db->fetch_object($resFields)) {
if ($fObj->field_type === 'header') continue;
$fieldMeta[(int)$fObj->fk_anlage_type][$fObj->field_code] = array(
'label' => $fObj->field_label,
'display' => $fObj->tree_display_mode ?: 'badge',
'color' => $fObj->badge_color ?: '',
);
}
$db->free($resFields);
}
// Kompletter Baum ohne Kontaktzuweisung (Kunden-Ebene)
$anlage = new Anlage($db);
$tree = $anlage->fetchTree($customerId, 0);
$result = pwaTreeToArray($tree, $fieldMeta);
// Kontakt-Adressen mit Anlagen laden
$contacts = array();
$sql = "SELECT c.rowid, c.lastname, c.firstname, c.address, c.town,";
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage a WHERE a.fk_contact = c.rowid AND a.status = 1) as anlage_count";
$sql .= " FROM ".MAIN_DB_PREFIX."socpeople as c";
$sql .= " WHERE c.fk_soc = ".((int) $customerId);
$sql .= " AND c.statut = 1";
$sql .= " ORDER BY c.lastname ASC";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$contactName = trim($obj->lastname.' '.$obj->firstname);
$contacts[] = array(
'id' => $obj->rowid,
'name' => $contactName,
'address' => $obj->address,
'town' => $obj->town,
'anlage_count' => (int) $obj->anlage_count
);
}
}
$response['success'] = true;
$response['anlagen'] = $result;
$response['contacts'] = $contacts;
break;
// ============================================
// GET ANLAGEN FOR CONTACT ADDRESS
// ============================================
case 'get_contact_anlagen':
$customerId = GETPOSTINT('customer_id');
$contactId = GETPOSTINT('contact_id');
if ($customerId <= 0 || $contactId <= 0) {
$response['error'] = 'Kunden-ID und Kontakt-ID erforderlich';
break;
}
// Feld-Metadaten laden falls nicht schon vorhanden
if (!isset($fieldMeta)) {
$fieldMeta = array();
$sqlFields = "SELECT fk_anlage_type, field_code, field_label, tree_display_mode, badge_color, show_in_tree, field_type";
$sqlFields .= " FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field";
$sqlFields .= " WHERE active = 1 AND show_in_tree = 1";
$sqlFields .= " ORDER BY position";
$resFields = $db->query($sqlFields);
if ($resFields) {
while ($fObj = $db->fetch_object($resFields)) {
if ($fObj->field_type === 'header') continue;
$fieldMeta[(int)$fObj->fk_anlage_type][$fObj->field_code] = array(
'label' => $fObj->field_label,
'display' => $fObj->tree_display_mode ?: 'badge',
'color' => $fObj->badge_color ?: '',
);
}
$db->free($resFields);
}
}
$anlage = new Anlage($db);
$tree = $anlage->fetchTreeByContact($customerId, $contactId, 0);
$result = pwaTreeToArray($tree, $fieldMeta);
$response['success'] = true;
$response['anlagen'] = $result;
break;
// ============================================
// GET ANLAGE DATA (Panels, Carriers, Equipment)
// ============================================
case 'get_anlage_data':
$anlageId = GETPOSTINT('anlage_id');
if ($anlageId <= 0) {
$response['error'] = 'Keine Anlagen-ID';
break;
}
// Load panels
$panel = new EquipmentPanel($db);
$panels = $panel->fetchByAnlage($anlageId);
$panelsData = array();
foreach ($panels as $p) {
$panelsData[] = array(
'id' => $p->id,
'label' => $p->label,
'position' => $p->position
);
}
// Load carriers
$carrier = new EquipmentCarrier($db);
$carriersData = array();
foreach ($panels as $p) {
$p->fetchCarriers();
foreach ($p->carriers as $c) {
$carriersData[] = array(
'id' => $c->id,
'fk_panel' => $c->fk_panel,
'label' => $c->label,
'total_te' => $c->total_te,
'position' => $c->position
);
}
}
// Load equipment
$equipment = new Equipment($db);
$equipmentData = array();
foreach ($carriersData as $c) {
$items = $equipment->fetchByCarrier($c['id']);
foreach ($items as $eq) {
$equipmentData[] = array(
'id' => $eq->id,
'fk_carrier' => $eq->fk_carrier,
'fk_equipment_type' => $eq->fk_equipment_type,
'label' => $eq->label,
'position_te' => $eq->position_te,
'width_te' => $eq->width_te,
'block_label' => $eq->getBlockLabel(),
'block_color' => $eq->getBlockColor(),
'field_values' => $eq->getFieldValues(),
'fk_protection' => $eq->fk_protection > 0 ? (int) $eq->fk_protection : null
);
}
}
// Equipment-Typen laden (benötigt für Terminal-Position-Auflösung + Typ-Auswahl)
$eqType = new EquipmentType($db);
$types = $eqType->fetchAllBySystem(0, 1); // Alle Systeme, nur aktive
// Abgänge laden (Connections mit fk_target IS NULL = Ausgänge)
$outputsData = array();
if (!empty($equipmentData)) {
$equipmentIds = array_map(function($e) { return (int) $e['id']; }, $equipmentData);
$sql = "SELECT rowid, fk_source, output_label, medium_type, medium_spec, medium_length, connection_type, color, source_terminal, source_terminal_id, bundled_terminals";
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
$sql .= " WHERE fk_source IN (".implode(',', $equipmentIds).")";
$sql .= " AND fk_target IS NULL";
$sql .= " AND status = 1";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
// Position bestimmen wie Website: source_terminal_id → Terminal-Config
$isTop = false;
if ($obj->source_terminal === 'top') {
// PWA-erstellte Verbindung mit expliziter Top-Angabe
$isTop = true;
} elseif (!empty($obj->source_terminal_id)) {
// Website-erstellte Verbindung: Terminal-Position aus Equipment-Typ ermitteln
$termId = $obj->source_terminal_id;
$eqTypeId = null;
foreach ($equipmentData as $e) {
if ($e['id'] == $obj->fk_source) {
$eqTypeId = $e['fk_equipment_type'];
break;
}
}
if ($eqTypeId) {
// Terminal-Config des Typs prüfen
$termResolved = false;
foreach ($types as $t) {
if ($t->id == $eqTypeId && !empty($t->terminals_config)) {
$config = json_decode($t->terminals_config, true);
$terms = $config['terminals'] ?? ($config['inputs'] ?? []);
if (!empty($config['terminals'])) {
foreach ($config['terminals'] as $term) {
if ($term['id'] === $termId) {
$isTop = ($term['pos'] === 'top');
$termResolved = true;
break;
}
}
}
break;
}
}
// Fallback: t1=oben, t2=unten (Standard LS)
if (!$termResolved) {
$isTop = ($termId === 't1');
}
}
}
$outputsData[] = array(
'id' => $obj->rowid,
'fk_source' => $obj->fk_source,
'output_label' => $obj->output_label,
'medium_type' => $obj->medium_type,
'medium_spec' => $obj->medium_spec,
'medium_length' => $obj->medium_length,
'connection_type' => $obj->connection_type,
'color' => $obj->color,
'source_terminal_id' => $obj->source_terminal_id ?: '',
'bundled_terminals' => $obj->bundled_terminals ?: '',
'is_top' => $isTop
);
}
}
}
// Einspeisungen laden (Connections mit fk_source IS NULL = Inputs)
$inputsData = array();
if (!empty($equipmentData)) {
$sql = "SELECT rowid, fk_target, output_label, connection_type, color";
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
$sql .= " WHERE fk_target IN (".implode(',', $equipmentIds).")";
$sql .= " AND fk_source IS NULL";
$sql .= " AND status = 1";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$inputsData[] = array(
'id' => $obj->rowid,
'fk_target' => $obj->fk_target,
'output_label' => $obj->output_label,
'connection_type' => $obj->connection_type,
'color' => $obj->color
);
}
}
}
// Verbindungen zwischen Equipment laden (mit path_data für Linien-Anzeige)
$connectionsData = array();
if (!empty($equipmentData)) {
$sql = "SELECT rowid, fk_source, fk_target, source_terminal_id, target_terminal_id, connection_type, color, path_data";
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
$sql .= " WHERE fk_source IN (".implode(',', $equipmentIds).")";
$sql .= " AND fk_target IS NOT NULL";
$sql .= " AND fk_target IN (".implode(',', $equipmentIds).")";
$sql .= " AND status = 1";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
// Nur Verbindungen mit gezeichnetem Pfad laden
if (!empty($obj->path_data)) {
$connectionsData[] = array(
'id' => $obj->rowid,
'fk_source' => $obj->fk_source,
'fk_target' => $obj->fk_target,
'source_terminal_id' => $obj->source_terminal_id,
'target_terminal_id' => $obj->target_terminal_id,
'connection_type' => $obj->connection_type,
'color' => $obj->color,
'path_data' => $obj->path_data
);
}
}
}
}
// Equipment-Typen für Response aufbereiten (bereits oben geladen)
$typesData = array();
foreach ($types as $t) {
$typesData[] = array(
'id' => $t->id,
'ref' => $t->ref,
'label' => $t->label,
'label_short' => $t->label_short,
'width_te' => $t->width_te,
'color' => $t->color,
'category' => $t->category,
'terminals_config' => $t->terminals_config ?: null
);
}
// Feld-Metadaten pro Typ laden (Labels, Typ, Optionen)
$fieldMetaData = array();
$usedTypeIds = array_unique(array_map(function($e) { return (int) $e['fk_equipment_type']; }, $equipmentData));
if (!empty($usedTypeIds)) {
$sql = "SELECT fk_equipment_type, field_code, field_label, field_type, field_options, show_on_block";
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field";
$sql .= " WHERE fk_equipment_type IN (".implode(',', $usedTypeIds).")";
$sql .= " AND active = 1";
$sql .= " ORDER BY position ASC";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$typeId = (int) $obj->fk_equipment_type;
if (!isset($fieldMetaData[$typeId])) {
$fieldMetaData[$typeId] = array();
}
$fieldMetaData[$typeId][] = array(
'code' => $obj->field_code,
'label' => $obj->field_label,
'type' => $obj->field_type,
'options' => $obj->field_options,
'show_on_block' => (int) $obj->show_on_block
);
}
$db->free($resql);
}
}
$response['success'] = true;
$response['panels'] = $panelsData;
$response['carriers'] = $carriersData;
$response['equipment'] = $equipmentData;
$response['outputs'] = $outputsData;
$response['inputs'] = $inputsData;
$response['connections'] = $connectionsData;
$response['types'] = $typesData;
$response['field_meta'] = $fieldMetaData;
break;
// ============================================
// CREATE PANEL
// ============================================
case 'create_panel':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$anlageId = GETPOSTINT('anlage_id');
$label = GETPOST('label', 'alphanohtml');
if ($anlageId <= 0) {
$response['error'] = 'Keine Anlagen-ID';
break;
}
$panel = new EquipmentPanel($db);
$panel->fk_anlage = $anlageId;
$panel->label = $label ?: 'Feld';
$result = $panel->create($user);
if ($result > 0) {
$response['success'] = true;
$response['panel_id'] = $result;
} else {
$response['error'] = $panel->error ?: 'Fehler beim Anlegen';
}
break;
// ============================================
// CREATE CARRIER
// ============================================
case 'create_carrier':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$panelId = GETPOSTINT('panel_id');
$totalTe = GETPOSTINT('total_te') ?: 12;
$label = GETPOST('label', 'alphanohtml');
if ($panelId <= 0) {
$response['error'] = 'Keine Panel-ID';
break;
}
// Anlage-ID vom Panel holen (benötigt für Carrier-Erstellung)
$panelObj = new EquipmentPanel($db);
if ($panelObj->fetch($panelId) <= 0) {
$response['error'] = 'Panel nicht gefunden';
break;
}
$carrier = new EquipmentCarrier($db);
$carrier->fk_anlage = $panelObj->fk_anlage;
$carrier->fk_panel = $panelId;
$carrier->label = $label ?: 'Hutschiene';
$carrier->total_te = $totalTe;
$result = $carrier->create($user);
if ($result > 0) {
$response['success'] = true;
$response['carrier_id'] = $result;
} else {
$response['error'] = $carrier->error ?: 'Fehler beim Anlegen';
}
break;
// ============================================
// UPDATE CARRIER
// ============================================
case 'update_carrier':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$carrierId = GETPOSTINT('carrier_id');
if ($carrierId <= 0) {
$response['error'] = 'Keine Carrier-ID';
break;
}
$carrier = new EquipmentCarrier($db);
if ($carrier->fetch($carrierId) <= 0) {
$response['error'] = 'Hutschiene nicht gefunden';
break;
}
$carrier->label = GETPOST('label', 'alphanohtml') ?: $carrier->label;
$newTe = GETPOSTINT('total_te');
if ($newTe > 0) {
$carrier->total_te = $newTe;
}
$result = $carrier->update($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $carrier->error ?: 'Fehler beim Aktualisieren';
}
break;
// ============================================
// DELETE CARRIER
// ============================================
case 'delete_carrier':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$carrierId = GETPOSTINT('carrier_id');
if ($carrierId <= 0) {
$response['error'] = 'Keine Carrier-ID';
break;
}
$carrier = new EquipmentCarrier($db);
if ($carrier->fetch($carrierId) <= 0) {
$response['error'] = 'Hutschiene nicht gefunden';
break;
}
$result = $carrier->delete($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $carrier->error ?: 'Fehler beim Löschen';
}
break;
// ============================================
// GET PROTECTION DEVICES (FI/RCD für Anlage)
// ============================================
case 'get_protection_devices':
$anlageId = GETPOSTINT('anlage_id');
if ($anlageId <= 0) {
$response['error'] = 'Keine Anlage-ID';
break;
}
$equipment = new Equipment($db);
$devices = $equipment->fetchProtectionDevices($anlageId);
$result = array();
foreach ($devices as $d) {
$result[] = array(
'id' => $d->id,
'label' => $d->label ?: $d->type_label,
'type_label' => $d->type_label,
'type_label_short' => $d->type_label_short,
'display_label' => ($d->label ?: $d->type_label_short ?: $d->type_label).' (Pos. '.$d->position_te.')'
);
}
$response['success'] = true;
$response['devices'] = $result;
break;
// ============================================
// CREATE EQUIPMENT
// ============================================
case 'create_equipment':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$carrierId = GETPOSTINT('carrier_id');
$typeId = GETPOSTINT('type_id');
$label = GETPOST('label', 'alphanohtml');
$positionTe = GETPOSTINT('position_te') ?: 1;
$fieldValues = GETPOST('field_values', 'nohtml');
$fkProtection = GETPOSTINT('fk_protection');
if ($carrierId <= 0 || $typeId <= 0) {
$response['error'] = 'Carrier-ID und Typ-ID erforderlich';
break;
}
// Load type for width
$eqType = new EquipmentType($db);
$eqType->fetch($typeId);
$equipment = new Equipment($db);
$equipment->fk_carrier = $carrierId;
$equipment->fk_equipment_type = $typeId;
$equipment->label = $label;
$equipment->position_te = $positionTe;
$equipment->width_te = $eqType->width_te ?: 1;
$equipment->field_values = $fieldValues;
$equipment->fk_protection = $fkProtection > 0 ? $fkProtection : null;
// Bezeichnung automatisch generieren wenn leer (wie Website)
if (empty(trim($equipment->label ?? ''))) {
$carrier = new EquipmentCarrier($db);
if ($carrier->fetch($carrierId) > 0) {
$carrierLabel = $carrier->label ?: ('R'.$carrier->id);
$posStart = $equipment->position_te;
$posEnd = $posStart + $equipment->width_te - 1;
if ($equipment->width_te > 1) {
$equipment->label = $carrierLabel.'.'.$posStart.'-'.$posEnd;
} else {
$equipment->label = $carrierLabel.'.'.$posStart;
}
}
}
$result = $equipment->create($user);
if ($result > 0) {
$response['success'] = true;
$response['equipment_id'] = $result;
$response['label'] = $equipment->label;
$response['block_label'] = $equipment->getBlockLabel();
$response['block_color'] = $equipment->getBlockColor();
} else {
$response['error'] = $equipment->error ?: 'Fehler beim Anlegen';
}
break;
// ============================================
// GET TYPE FIELDS (Felder pro Equipment-Typ)
// ============================================
case 'get_type_fields':
$typeId = GETPOSTINT('type_id');
$equipmentId = GETPOSTINT('equipment_id');
if ($typeId <= 0) {
$response['error'] = 'Keine Typ-ID';
break;
}
$type = new EquipmentType($db);
if ($type->fetch($typeId) <= 0) {
$response['error'] = 'Typ nicht gefunden';
break;
}
$fields = $type->fetchFields(1);
// Bestehende Werte laden (bei Bearbeitung)
$existingValues = array();
if ($equipmentId > 0) {
$eq = new Equipment($db);
if ($eq->fetch($equipmentId) > 0) {
$existingValues = $eq->getFieldValues();
}
}
$result = array();
foreach ($fields as $field) {
$value = isset($existingValues[$field->field_code]) ? $existingValues[$field->field_code] : $field->field_default;
$result[] = array(
'code' => $field->field_code,
'label' => $field->field_label,
'type' => $field->field_type,
'options' => $field->field_options,
'required' => (int) $field->required,
'show_on_block' => (int) $field->show_on_block,
'value' => $value
);
}
$response['success'] = true;
$response['fields'] = $result;
break;
// ============================================
// UPDATE EQUIPMENT
// ============================================
case 'update_equipment':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$equipmentId = GETPOSTINT('equipment_id');
$label = GETPOST('label', 'alphanohtml');
$fieldValues = GETPOST('field_values', 'nohtml');
$fkProtection = GETPOSTINT('fk_protection');
if ($equipmentId <= 0) {
$response['error'] = 'Keine Equipment-ID';
break;
}
$equipment = new Equipment($db);
if ($equipment->fetch($equipmentId) <= 0) {
$response['error'] = 'Automat nicht gefunden';
break;
}
$equipment->label = $label;
$equipment->field_values = $fieldValues;
$equipment->fk_protection = $fkProtection > 0 ? $fkProtection : null;
$result = $equipment->update($user);
if ($result > 0) {
$response['success'] = true;
// Aktualisierte Daten zurückgeben
$response['block_label'] = $equipment->getBlockLabel();
$response['block_color'] = $equipment->getBlockColor();
} else {
$response['error'] = $equipment->error ?: 'Fehler beim Aktualisieren';
}
break;
// ============================================
// DELETE EQUIPMENT
// ============================================
case 'delete_equipment':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$equipmentId = GETPOSTINT('equipment_id');
if ($equipmentId <= 0) {
$response['error'] = 'Keine Equipment-ID';
break;
}
$equipment = new Equipment($db);
if ($equipment->fetch($equipmentId) <= 0) {
$response['error'] = 'Automat nicht gefunden';
break;
}
$result = $equipment->delete($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $equipment->error ?: 'Fehler beim Löschen';
}
break;
// ============================================
// CREATE CONNECTION (Abgang oder Einspeisung)
// ============================================
case 'create_connection':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$equipmentId = GETPOSTINT('equipment_id');
$direction = GETPOST('direction', 'alpha'); // 'output' oder 'input'
$connectionType = GETPOST('connection_type', 'alphanohtml');
$outputLabel = GETPOST('output_label', 'alphanohtml');
$mediumType = GETPOST('medium_type', 'alphanohtml');
$mediumSpec = GETPOST('medium_spec', 'alphanohtml');
$mediumLength = GETPOST('medium_length', 'alphanohtml');
$sourceTerminal = GETPOST('source_terminal', 'alphanohtml') ?: 'output';
$sourceTerminalId = GETPOST('source_terminal_id', 'alphanohtml');
$targetTerminalId = GETPOST('target_terminal_id', 'alphanohtml');
$bundledTerminals = GETPOST('bundled_terminals', 'alphanohtml'); // 'all' oder '0,1,2'
if ($equipmentId <= 0) {
$response['error'] = 'Keine Equipment-ID';
break;
}
// Equipment prüfen und Carrier-ID ermitteln
$eq = new Equipment($db);
if ($eq->fetch($equipmentId) <= 0) {
$response['error'] = 'Automat nicht gefunden';
break;
}
$conn = new EquipmentConnection($db);
$conn->connection_type = $connectionType;
$conn->color = GETPOST('color', 'alphanohtml');
$conn->output_label = $outputLabel;
$conn->fk_carrier = $eq->fk_carrier;
if ($direction === 'input') {
// Einspeisung: fk_source = NULL, fk_target = Equipment
// Terminal-Position: t1=oben, t2=unten (Sicherungsautomaten haben keine feste Richtung!)
$conn->fk_target = $equipmentId;
$conn->fk_source = null;
$conn->target_terminal = 'input';
$conn->target_terminal_id = $targetTerminalId ?: 't2'; // Default: unten
} else {
// Abgang: fk_source = Equipment, fk_target = NULL
// Terminal-Position: t1=oben, t2=unten (Sicherungsautomaten haben keine feste Richtung!)
$conn->fk_source = $equipmentId;
$conn->fk_target = null;
$conn->source_terminal = $sourceTerminal;
$conn->source_terminal_id = $sourceTerminalId ?: ($sourceTerminal === 'top' ? 't1' : 't2');
$conn->bundled_terminals = $bundledTerminals ?: null;
$conn->medium_type = $mediumType;
$conn->medium_spec = $mediumSpec;
$conn->medium_length = $mediumLength;
}
$result = $conn->create($user);
if ($result > 0) {
$response['success'] = true;
$response['connection_id'] = $result;
} else {
$response['error'] = $conn->error ?: 'Fehler beim Anlegen';
}
break;
// ============================================
// UPDATE CONNECTION
// ============================================
case 'update_connection':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$connectionId = GETPOSTINT('connection_id');
if ($connectionId <= 0) {
$response['error'] = 'Keine Verbindungs-ID';
break;
}
$conn = new EquipmentConnection($db);
if ($conn->fetch($connectionId) <= 0) {
$response['error'] = 'Verbindung nicht gefunden';
break;
}
$conn->connection_type = GETPOST('connection_type', 'alphanohtml');
$conn->color = GETPOST('color', 'alphanohtml');
$conn->output_label = GETPOST('output_label', 'alphanohtml');
$conn->medium_type = GETPOST('medium_type', 'alphanohtml');
$conn->medium_spec = GETPOST('medium_spec', 'alphanohtml');
$conn->medium_length = GETPOST('medium_length', 'alphanohtml');
if (GETPOSTISSET('source_terminal')) {
$conn->source_terminal = GETPOST('source_terminal', 'alphanohtml') ?: $conn->source_terminal;
}
if (GETPOSTISSET('source_terminal_id')) {
$conn->source_terminal_id = GETPOST('source_terminal_id', 'alphanohtml') ?: $conn->source_terminal_id;
}
if (GETPOSTISSET('bundled_terminals')) {
$conn->bundled_terminals = GETPOST('bundled_terminals', 'alphanohtml') ?: null;
}
$result = $conn->update($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $conn->error ?: 'Fehler beim Aktualisieren';
}
break;
// ============================================
// DELETE CONNECTION
// ============================================
case 'delete_connection':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Keine Schreibberechtigung';
break;
}
$connectionId = GETPOSTINT('connection_id');
if ($connectionId <= 0) {
$response['error'] = 'Keine Verbindungs-ID';
break;
}
$conn = new EquipmentConnection($db);
if ($conn->fetch($connectionId) <= 0) {
$response['error'] = 'Verbindung nicht gefunden';
break;
}
$result = $conn->delete($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $conn->error ?: 'Fehler beim Löschen';
}
break;
// ============================================
// GET MEDIUM TYPES (Kabeltypen aus DB)
// ============================================
case 'get_medium_types':
$mediumType = new MediumType($db);
$grouped = $mediumType->fetchGroupedByCategory(1); // System 1 = Elektro
$result = array();
foreach ($grouped as $category => $types) {
$catTypes = array();
foreach ($types as $t) {
$catTypes[] = array(
'ref' => $t->ref,
'label' => $t->label,
'default_spec' => $t->default_spec,
'available_specs' => $t->getAvailableSpecsArray(),
);
}
$result[] = array(
'category' => $category,
'category_label' => $types[0]->getCategoryLabel(),
'types' => $catTypes
);
}
$response['success'] = true;
$response['groups'] = $result;
break;
default:
$response['error'] = 'Unbekannte Aktion: ' . $action;
}
echo json_encode($response);
$db->close();
/**
* Feld-Badges für eine Anlage aufbereiten (show_in_tree Felder)
*
* @param Anlage $anlage Anlage-Objekt
* @param array $fieldMeta Feld-Metadaten [typeId][code] = {label, display, color}
* @return array Array von Feld-Objekten mit label, value, color
*/
function pwaGetAnlageFields($anlage, $fieldMeta) {
$result = array();
$values = $anlage->getFieldValues();
if (empty($values)) return $result;
$typeId = (int) $anlage->fk_anlage_type;
$meta = isset($fieldMeta[$typeId]) ? $fieldMeta[$typeId] : array();
if (empty($meta)) return $result;
foreach ($meta as $code => $fm) {
$val = isset($values[$code]) ? $values[$code] : null;
if ($val === '' || $val === null) continue;
$result[] = array(
'label' => $fm['label'],
'value' => $val,
'color' => $fm['color'],
'display' => $fm['display'],
);
}
return $result;
}
/**
* Anlagen-Baum rekursiv in JSON-Array umwandeln
*
* @param array $nodes Array von Anlage-Objekten mit ->children
* @param array $fieldMeta Feld-Metadaten [typeId][code] = {label, display, color}
* @return array JSON-serialisierbares Array mit children
*/
function pwaTreeToArray($nodes, $fieldMeta) {
$result = array();
foreach ($nodes as $node) {
$item = array(
'id' => $node->id,
'label' => $node->label,
'type' => $node->type_label,
'can_have_equipment' => !empty($node->type_can_have_equipment),
'can_have_children' => !empty($node->type_can_have_children),
);
// Feld-Badges
$fields = pwaGetAnlageFields($node, $fieldMeta);
if (!empty($fields)) {
$item['fields'] = $fields;
}
// Kinder rekursiv
if (!empty($node->children)) {
$item['children'] = pwaTreeToArray($node->children, $fieldMeta);
}
$result[] = $item;
}
return $result;
}