kundenkarte/ajax/pwa_api.php
data 6e88f0eb87 feat(pwa): Kontakt-Adressen, Grid-Layout, Abgang-Labels, jQuery
- Kontakt-Adressen als aufklappbare Gruppen in Anlagen-Übersicht
- Equipment-Blöcke als CSS Grid (TE-basiert) statt Flex-Wrap
- Abgang-Labels (Outputs) über/unter Automaten, Toggle-Button
- jQuery statt eigener ElementCollection, aus Dolibarr geladen
- Design-System auf Dolibarr Dark Theme Variablen umgestellt
- Session-State-Wiederherstellung bei Refresh
- Browser-History Support (Hardware-Zurück)
- Quick-Select erweitert: AFDD, FI/LS-Kombi
- Intelligente Positionsberechnung mit Lücken-Erkennung
- Hutschiene zeigt belegt/gesamt TE
- $user->getrights() nach Token-Validierung
- Doku aktualisiert

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:37:17 +01:00

414 lines
12 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 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';
$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;
}
// Root-Anlagen ohne Kontaktzuweisung (Kunden-Ebene)
$anlage = new Anlage($db);
$anlagen = $anlage->fetchChildren(0, $customerId);
$result = array();
foreach ($anlagen as $a) {
$result[] = array(
'id' => $a->id,
'label' => $a->label,
'type' => $a->type_label,
'has_editor' => !empty($a->schematic_editor_enabled)
);
}
// 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;
}
$anlage = new Anlage($db);
$anlagen = $anlage->fetchChildrenByContact(0, $customerId, $contactId);
$result = array();
foreach ($anlagen as $a) {
$result[] = array(
'id' => $a->id,
'label' => $a->label,
'type' => $a->type_label,
'has_editor' => !empty($a->schematic_editor_enabled)
);
}
$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()
);
}
}
// 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, connection_type";
$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)) {
$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,
'connection_type' => $obj->connection_type
);
}
}
}
// Load equipment types
$eqType = new EquipmentType($db);
$types = $eqType->fetchAllBySystem(1, 1); // System 1 = Elektro, nur aktive
$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
);
}
$response['success'] = true;
$response['panels'] = $panelsData;
$response['carriers'] = $carriersData;
$response['equipment'] = $equipmentData;
$response['outputs'] = $outputsData;
$response['types'] = $typesData;
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;
}
$carrier = new EquipmentCarrier($db);
$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;
// ============================================
// 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');
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;
$result = $equipment->create($user);
if ($result > 0) {
$response['success'] = true;
$response['equipment_id'] = $result;
} else {
$response['error'] = $equipment->error ?: 'Fehler beim Anlegen';
}
break;
default:
$response['error'] = 'Unbekannte Aktion: ' . $action;
}
echo json_encode($response);
$db->close();