- 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>
414 lines
12 KiB
PHP
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();
|