kundenkarte/ajax/pwa_api.php
data 844e6060c6 feat(pwa): Offline-fähige Progressive Web App für Elektriker
PWA Mobile App für Schaltschrank-Dokumentation vor Ort:
- Token-basierte Authentifizierung (15 Tage gültig)
- Kundensuche mit Offline-Cache
- Anlagen-Auswahl und Offline-Laden
- Felder/Hutschienen/Automaten erfassen
- Automatische Synchronisierung wenn wieder online
- Installierbar auf dem Smartphone Home Screen
- Touch-optimiertes Dark Mode Design
- Quick-Select für Automaten-Werte (B16, C32, etc.)

Schaltplan-Editor Verbesserungen:
- Block Hover-Tooltip mit show_in_hover Feldern
- Produktinfo mit Icon im Tooltip
- Position und Breite in TE

Neue Dateien:
- pwa.php, pwa_auth.php - PWA Einstieg & Auth
- ajax/pwa_api.php - PWA AJAX API
- js/pwa.js, css/pwa.css - PWA App & Styles
- sw.js, manifest.json - Service Worker & Manifest
- img/pwa-icon-192.png, img/pwa-icon-512.png

Version: 5.2.0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-23 15:27:06 +01:00

333 lines
8.8 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']);
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';
$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
// ============================================
case 'get_anlagen':
$customerId = GETPOSTINT('customer_id');
if ($customerId <= 0) {
$response['error'] = 'Keine Kunden-ID';
break;
}
$anlage = new Anlage($db);
$anlagen = $anlage->fetchAll('ASC', 'label', 0, 0, array('fk_soc' => $customerId));
$result = array();
if (is_array($anlagen)) {
foreach ($anlagen as $a) {
$result[] = array(
'id' => $a->id,
'label' => $a->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,
'field_values' => $eq->getFieldValues()
);
}
}
// 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['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();