Compare commits
3 commits
241229659b
...
e269584396
| Author | SHA1 | Date | |
|---|---|---|---|
| e269584396 | |||
| 01626be22d | |||
| 619d14e8d5 |
11 changed files with 1490 additions and 134 deletions
28
CLAUDE.md
28
CLAUDE.md
|
|
@ -135,7 +135,7 @@ Offline-fähige Progressive Web App für Elektriker zur Schaltschrank-Dokumentat
|
|||
- `ajax/pwa_api.php` - Alle AJAX-Endpoints für die PWA
|
||||
- `js/pwa.js` - Komplette App-Logik (jQuery, als IIFE mit jQuery-Parameter)
|
||||
- `css/pwa.css` - Mobile-First Design, Dolibarr Dark Theme Variablen
|
||||
- `sw.js` - Service Worker für Offline-Cache (v2.7)
|
||||
- `sw.js` - Service Worker für Offline-Cache (v6.1)
|
||||
- `manifest.json` - Web App Manifest für Installation
|
||||
|
||||
### Workflow
|
||||
|
|
@ -200,3 +200,29 @@ Offline-fähige Progressive Web App für Elektriker zur Schaltschrank-Dokumentat
|
|||
1. PWA im Browser öffnen: `https://domain/dolibarr/custom/kundenkarte/pwa.php`
|
||||
2. Browser-Menü → "Zum Startbildschirm hinzufügen"
|
||||
3. App öffnet sich als Standalone ohne Browser-UI
|
||||
|
||||
### FI/RCD-Schutzgruppen (v7.5)
|
||||
- Equipment kann einem Schutzgerät (FI/RCD) zugeordnet werden
|
||||
- `fk_protection` in `llx_kundenkarte_equipment` speichert die ID des schützenden Equipment
|
||||
- Im Editor: Farbige Ränder zeigen Schutzgruppen-Zugehörigkeit
|
||||
- `get_protection_devices` API liefert verfügbare Schutzgeräte für Dropdown
|
||||
|
||||
### Gebündelte Terminals (v7.5)
|
||||
- Multi-Phasen-Abgänge für Drehstrom-Verbraucher (E-Herd, DLE)
|
||||
- `bundled_terminals = 'all'` in Connection bedeutet: Alle Terminals belegt
|
||||
- Im Editor: Ein Pfeil spannt über alle Terminals des Equipment
|
||||
- Label wird zentriert über alle Terminals angezeigt
|
||||
- Checkbox "Alle bündeln" nur bei Equipment mit >1 Terminal sichtbar
|
||||
|
||||
### Terminal-Konfiguration (v7.5)
|
||||
- `terminals_config` JSON im Equipment-Typ definiert Terminal-Positionen
|
||||
- Format: `{"terminals":[{"pos":"top"},{"pos":"top"},{"pos":"bottom"}...]}`
|
||||
- `getTerminalCount(type, position, fallback)` zählt Terminals pro Position
|
||||
- Ermöglicht: 4 TE Breite aber nur 3 Terminals (z.B. Neozed 3F)
|
||||
|
||||
### Grid-Layout (5 Zeilen)
|
||||
- Zeile 1: Abgang-Labels oben (terminal-label-cell.label-row-top)
|
||||
- Zeile 2: Terminal-Punkte oben (terminal-point.terminal-row-top)
|
||||
- Zeile 3: Equipment-Blöcke
|
||||
- Zeile 4: Terminal-Punkte unten (terminal-point.terminal-row-bottom)
|
||||
- Zeile 5: Abgang-Labels unten (terminal-label-cell.label-row-bottom)
|
||||
|
|
|
|||
48
ChangeLog.md
48
ChangeLog.md
|
|
@ -1,5 +1,53 @@
|
|||
# CHANGELOG MODULE KUNDENKARTE FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||
|
||||
## 7.5 (2026-03)
|
||||
|
||||
### Neue Features
|
||||
|
||||
- **FI/RCD-Schutzgruppen**: Equipment kann Schutzgeraeten zugeordnet werden
|
||||
- Farbliche Markierung der Schutzgruppen im Schaltplan
|
||||
- Dropdown zur Auswahl des Schutzgeraets im Equipment-Dialog
|
||||
- Visuelle Verbindung durch farbige Raender
|
||||
|
||||
- **Gebuendelte Terminals**: Multi-Phasen-Abgaenge fuer Drehstrom-Verbraucher
|
||||
- "Alle buendeln" Option fuer E-Herd, Durchlauferhitzer etc.
|
||||
- Ein Abgang belegt alle Terminals des Equipment
|
||||
- Zentrierter Pfeil ueber alle Terminals
|
||||
|
||||
- **Terminal-Konfiguration**: Korrekte Terminal-Anzahl aus Typ-Konfiguration
|
||||
- Neozed 3F zeigt 3 statt 4 Terminals (trotz 4 TE Breite)
|
||||
- Neue `getTerminalCount()` Hilfsfunktion
|
||||
|
||||
- **Zuletzt bearbeitete Kunden**: Quick-Access auf Search-Screen
|
||||
- Speichert die letzten 5 bearbeiteten Kunden
|
||||
- Schneller Zugriff ohne Suche
|
||||
|
||||
- **Medium-Typen aus Datenbank**: Dynamisches Kabeltyp-Dropdown
|
||||
- Kategorisierte Auswahl (NYM, NYY, Datenkabel, etc.)
|
||||
- Querschnitt-Spezifikationen als Unter-Dropdown
|
||||
- Offline-Cache fuer die Auswahl
|
||||
|
||||
### Verbesserungen
|
||||
|
||||
- Terminal-Labels anklickbar zum direkten Bearbeiten
|
||||
- Kontextmenue fuer leere Terminals (Wahl Input/Output)
|
||||
- Equipment-Block-Value (B16A) kleiner dargestellt (8px)
|
||||
- Terminals direkt am Equipment-Block ausgerichtet
|
||||
- Block-Label mit Einheiten und Leerzeichen (40A 30mA)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Abgaenge werden nach Positionswechsel (oben/unten) korrekt angezeigt
|
||||
- Bundle-Option nur bei Equipment mit mehr als 1 Terminal sichtbar
|
||||
- Login-Fehler durch doppelte Variable-Deklaration behoben
|
||||
|
||||
### Datenbank-Aenderungen
|
||||
|
||||
- Neue Spalte `bundled_terminals` in `llx_kundenkarte_equipment_connection`
|
||||
- Neue Spalten `fk_protection`, `protection_label` in `llx_kundenkarte_equipment`
|
||||
|
||||
---
|
||||
|
||||
## 5.2.0 (2026-02)
|
||||
|
||||
### Neue Features
|
||||
|
|
|
|||
|
|
@ -403,7 +403,8 @@ print '<br><br>';
|
|||
print '<div class="titre inline-block">'.$langs->trans("PWAMobileApp").'</div>';
|
||||
print '<br><br>';
|
||||
|
||||
$pwaUrl = dol_buildpath('/kundenkarte/pwa.php', 2);
|
||||
// PWA URL mit vollständigem Pfad (ohne dol_buildpath wegen URL-Problemen)
|
||||
$pwaUrl = DOL_URL_ROOT.'/custom/kundenkarte/pwa.php';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<td colspan="2">'.$langs->trans("PWAMobileApp").'</td>';
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ if (!$user->hasRight('kundenkarte', 'read')) {
|
|||
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';
|
||||
|
|
@ -274,7 +277,8 @@ switch ($action) {
|
|||
'width_te' => $eq->width_te,
|
||||
'block_label' => $eq->getBlockLabel(),
|
||||
'block_color' => $eq->getBlockColor(),
|
||||
'field_values' => $eq->getFieldValues()
|
||||
'field_values' => $eq->getFieldValues(),
|
||||
'fk_protection' => $eq->fk_protection > 0 ? (int) $eq->fk_protection : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -287,7 +291,7 @@ switch ($action) {
|
|||
$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";
|
||||
$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";
|
||||
|
|
@ -346,6 +350,7 @@ switch ($action) {
|
|||
'connection_type' => $obj->connection_type,
|
||||
'color' => $obj->color,
|
||||
'source_terminal_id' => $obj->source_terminal_id ?: '',
|
||||
'bundled_terminals' => $obj->bundled_terminals ?: '',
|
||||
'is_top' => $isTop
|
||||
);
|
||||
}
|
||||
|
|
@ -374,6 +379,35 @@ switch ($action) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
@ -384,7 +418,8 @@ switch ($action) {
|
|||
'label_short' => $t->label_short,
|
||||
'width_te' => $t->width_te,
|
||||
'color' => $t->color,
|
||||
'category' => $t->category
|
||||
'category' => $t->category,
|
||||
'terminals_config' => $t->terminals_config ?: null
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -422,6 +457,7 @@ switch ($action) {
|
|||
$response['equipment'] = $equipmentData;
|
||||
$response['outputs'] = $outputsData;
|
||||
$response['inputs'] = $inputsData;
|
||||
$response['connections'] = $connectionsData;
|
||||
$response['types'] = $typesData;
|
||||
$response['field_meta'] = $fieldMetaData;
|
||||
break;
|
||||
|
|
@ -560,6 +596,32 @@ switch ($action) {
|
|||
}
|
||||
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
|
||||
// ============================================
|
||||
|
|
@ -574,6 +636,7 @@ switch ($action) {
|
|||
$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';
|
||||
|
|
@ -591,6 +654,7 @@ switch ($action) {
|
|||
$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 ?? ''))) {
|
||||
|
|
@ -678,6 +742,7 @@ switch ($action) {
|
|||
$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';
|
||||
|
|
@ -692,6 +757,7 @@ switch ($action) {
|
|||
|
||||
$equipment->label = $label;
|
||||
$equipment->field_values = $fieldValues;
|
||||
$equipment->fk_protection = $fkProtection > 0 ? $fkProtection : null;
|
||||
|
||||
$result = $equipment->update($user);
|
||||
if ($result > 0) {
|
||||
|
|
@ -752,6 +818,8 @@ switch ($action) {
|
|||
$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';
|
||||
|
|
@ -773,15 +841,19 @@ switch ($action) {
|
|||
|
||||
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;
|
||||
|
|
@ -829,6 +901,9 @@ switch ($action) {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -508,7 +508,14 @@ class Equipment extends CommonObject
|
|||
|
||||
foreach ($blockFields as $field) {
|
||||
if (isset($values[$field->field_code]) && $values[$field->field_code] !== '') {
|
||||
$parts[] = $values[$field->field_code];
|
||||
$val = $values[$field->field_code];
|
||||
// Einheit hinzufügen für bekannte Felder (wie in Website JS kundenkarte.js:6613-6617)
|
||||
if ($field->field_code === 'ampere') {
|
||||
$val = $val . 'A';
|
||||
} elseif ($field->field_code === 'sensitivity') {
|
||||
$val = $val . 'mA';
|
||||
}
|
||||
$parts[] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -516,7 +523,8 @@ class Equipment extends CommonObject
|
|||
return $this->type_label_short ?: '';
|
||||
}
|
||||
|
||||
return implode('', $parts);
|
||||
// Mit Leerzeichen verbinden für bessere Lesbarkeit (z.B. "40A 30mA" statt "40A30mA")
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class EquipmentConnection extends CommonObject
|
|||
public $fk_source;
|
||||
public $source_terminal = 'output';
|
||||
public $source_terminal_id;
|
||||
public $bundled_terminals; // 'all' = alle Terminals belegt, '0,1,2' = spezifische Indizes, NULL = einzeln
|
||||
public $fk_target;
|
||||
public $target_terminal = 'input';
|
||||
public $target_terminal_id;
|
||||
|
|
@ -86,7 +87,7 @@ class EquipmentConnection extends CommonObject
|
|||
$this->db->begin();
|
||||
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||
$sql .= "entity, fk_source, source_terminal, source_terminal_id, fk_target, target_terminal, target_terminal_id,";
|
||||
$sql .= "entity, fk_source, source_terminal, source_terminal_id, bundled_terminals, fk_target, target_terminal, target_terminal_id,";
|
||||
$sql .= " connection_type, color, output_label,";
|
||||
$sql .= " medium_type, medium_spec, medium_length,";
|
||||
$sql .= " is_rail, rail_start_te, rail_end_te, rail_phases, excluded_te, fk_carrier, position_y, path_data,";
|
||||
|
|
@ -96,6 +97,7 @@ class EquipmentConnection extends CommonObject
|
|||
$sql .= ", ".($this->fk_source > 0 ? ((int) $this->fk_source) : "NULL");
|
||||
$sql .= ", '".$this->db->escape($this->source_terminal ?: 'output')."'";
|
||||
$sql .= ", ".($this->source_terminal_id ? "'".$this->db->escape($this->source_terminal_id)."'" : "NULL");
|
||||
$sql .= ", ".($this->bundled_terminals ? "'".$this->db->escape($this->bundled_terminals)."'" : "NULL");
|
||||
$sql .= ", ".($this->fk_target > 0 ? ((int) $this->fk_target) : "NULL");
|
||||
$sql .= ", '".$this->db->escape($this->target_terminal ?: 'input')."'";
|
||||
$sql .= ", ".($this->target_terminal_id ? "'".$this->db->escape($this->target_terminal_id)."'" : "NULL");
|
||||
|
|
@ -164,6 +166,7 @@ class EquipmentConnection extends CommonObject
|
|||
$this->fk_source = $obj->fk_source;
|
||||
$this->source_terminal = $obj->source_terminal;
|
||||
$this->source_terminal_id = $obj->source_terminal_id;
|
||||
$this->bundled_terminals = isset($obj->bundled_terminals) ? $obj->bundled_terminals : null;
|
||||
$this->fk_target = $obj->fk_target;
|
||||
$this->target_terminal = $obj->target_terminal;
|
||||
$this->target_terminal_id = $obj->target_terminal_id;
|
||||
|
|
@ -219,6 +222,7 @@ class EquipmentConnection extends CommonObject
|
|||
$sql .= " fk_source = ".($this->fk_source > 0 ? ((int) $this->fk_source) : "NULL");
|
||||
$sql .= ", source_terminal = '".$this->db->escape($this->source_terminal ?: 'output')."'";
|
||||
$sql .= ", source_terminal_id = ".($this->source_terminal_id ? "'".$this->db->escape($this->source_terminal_id)."'" : "NULL");
|
||||
$sql .= ", bundled_terminals = ".($this->bundled_terminals ? "'".$this->db->escape($this->bundled_terminals)."'" : "NULL");
|
||||
$sql .= ", fk_target = ".($this->fk_target > 0 ? ((int) $this->fk_target) : "NULL");
|
||||
$sql .= ", target_terminal = '".$this->db->escape($this->target_terminal ?: 'input')."'";
|
||||
$sql .= ", target_terminal_id = ".($this->target_terminal_id ? "'".$this->db->escape($this->target_terminal_id)."'" : "NULL");
|
||||
|
|
@ -315,6 +319,7 @@ class EquipmentConnection extends CommonObject
|
|||
$conn->fk_source = $obj->fk_source;
|
||||
$conn->source_terminal = $obj->source_terminal;
|
||||
$conn->source_terminal_id = $obj->source_terminal_id;
|
||||
$conn->bundled_terminals = isset($obj->bundled_terminals) ? $obj->bundled_terminals : null;
|
||||
$conn->fk_target = $obj->fk_target;
|
||||
$conn->target_terminal = $obj->target_terminal;
|
||||
$conn->target_terminal_id = $obj->target_terminal_id;
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class modKundenKarte extends DolibarrModules
|
|||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte'
|
||||
|
||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||
$this->version = '6.1';
|
||||
$this->version = '8.3';
|
||||
// Url to the file with your last numberversion of this module
|
||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||
|
||||
|
|
@ -621,6 +621,12 @@ class modKundenKarte extends DolibarrModules
|
|||
|
||||
// v5.2.0: Halbe TE-Breiten (4.5 TE für Neozed etc.)
|
||||
$this->migrate_v520_decimal_te();
|
||||
|
||||
// v6.8.0: Gebündelte Terminals für Multi-Phasen-Abgänge
|
||||
$this->migrate_v680_bundled_terminals();
|
||||
|
||||
// v6.8.0: Schutzgruppen-Zuordnung (fk_protection)
|
||||
$this->migrate_v680_protection_groups();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -829,6 +835,61 @@ class modKundenKarte extends DolibarrModules
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration v6.8.0: Gebündelte Terminals für Multi-Phasen-Abgänge
|
||||
* Ermöglicht einen Abgang der alle Terminals eines breiten Equipment belegt
|
||||
* z.B. 3-Phasen B16 Automat mit einem E-Herd-Abgang
|
||||
*/
|
||||
private function migrate_v680_bundled_terminals()
|
||||
{
|
||||
$table = MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
||||
|
||||
// Prüfen ob Tabelle existiert
|
||||
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfen ob Spalte bereits existiert
|
||||
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'bundled_terminals'");
|
||||
if ($resql && $this->db->num_rows($resql) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Spalte hinzufügen: 'all' = alle Terminals, '0,1,2' = spezifische Indizes, NULL = einzeln
|
||||
$this->db->query("ALTER TABLE ".$table." ADD COLUMN bundled_terminals varchar(50) DEFAULT NULL AFTER source_terminal_id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration v6.8.0: Schutzgruppen-Zuordnung für Equipment
|
||||
* Ermöglicht Zuordnung von Equipment zu einem Schutzgerät (FI/RCD)
|
||||
* fk_protection = ID des schützenden Equipment
|
||||
* protection_label = Optionales Label für die Gruppe
|
||||
*/
|
||||
private function migrate_v680_protection_groups()
|
||||
{
|
||||
$table = MAIN_DB_PREFIX."kundenkarte_equipment";
|
||||
|
||||
// Prüfen ob Tabelle existiert
|
||||
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fk_protection Spalte
|
||||
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'fk_protection'");
|
||||
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||
$this->db->query("ALTER TABLE ".$table." ADD COLUMN fk_protection integer DEFAULT NULL AFTER fk_product");
|
||||
$this->db->query("ALTER TABLE ".$table." ADD INDEX idx_equipment_protection (fk_protection)");
|
||||
}
|
||||
|
||||
// protection_label Spalte
|
||||
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'protection_label'");
|
||||
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||
$this->db->query("ALTER TABLE ".$table." ADD COLUMN protection_label varchar(64) DEFAULT NULL AFTER fk_protection");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when module is disabled.
|
||||
* Remove from database constants, boxes and permissions from Dolibarr database.
|
||||
|
|
|
|||
337
css/pwa.css
337
css/pwa.css
|
|
@ -360,6 +360,56 @@ body {
|
|||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ZULETZT BEARBEITET
|
||||
============================================ */
|
||||
|
||||
.recent-section {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.recent-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--colortextmuted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin: 16px 0 10px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.recent-section .list {
|
||||
padding: 0;
|
||||
flex: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.recent-section .list-item {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.recent-section .list-item-icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.recent-section .list-item-icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.recent-section .list-item-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.recent-section .list-item-subtitle {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#recent-customers.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LISTS
|
||||
============================================ */
|
||||
|
|
@ -781,10 +831,10 @@ body {
|
|||
Zeile 3: Output-Terminals (Abgänge) */
|
||||
.carrier-content {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
gap: 0;
|
||||
padding: 5px;
|
||||
grid-template-rows: auto auto auto;
|
||||
align-items: stretch;
|
||||
/* 5 Zeilen: Labels oben, Terminals oben, Equipment, Terminals unten, Labels unten */
|
||||
grid-template-rows: auto auto auto auto auto;
|
||||
}
|
||||
|
||||
/* Equipment Block (Zeile 2) - Sicherungsautomat-Optik */
|
||||
|
|
@ -810,27 +860,29 @@ body {
|
|||
}
|
||||
|
||||
.equipment-block-type {
|
||||
font-size: 9px;
|
||||
font-size: 7px;
|
||||
font-weight: bold;
|
||||
color: rgba(255,255,255,0.8);
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.equipment-block-value {
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.equipment-block-value {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.equipment-block-label {
|
||||
font-size: 8px;
|
||||
color: rgba(255,255,255,0.7);
|
||||
font-size: 7px;
|
||||
color: rgba(255,255,255,0.6);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.1;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Equipment Block Text (einzelner Block-Label wie "B16") */
|
||||
|
|
@ -861,12 +913,18 @@ body {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.terminal-point.terminal-input {
|
||||
align-self: end;
|
||||
/* Zeile 2 (obere Terminals): direkt am Equipment (margin-bottom negativ) */
|
||||
.terminal-point.terminal-row-top {
|
||||
margin-bottom: -2px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.terminal-point.terminal-output {
|
||||
align-self: start;
|
||||
/* Zeile 4 (untere Terminals): direkt am Equipment (margin-top negativ) */
|
||||
.terminal-point.terminal-row-bottom {
|
||||
margin-top: -2px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.terminal-point:active {
|
||||
|
|
@ -915,28 +973,178 @@ body {
|
|||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Abgang-Label Zelle (Zeile 1 und 5) */
|
||||
.terminal-label-cell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* Obere Labels (Zeile 1): am unteren Rand ausrichten (zum Terminal hin) */
|
||||
.terminal-label-cell.label-row-top {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* Untere Labels (Zeile 5): am oberen Rand ausrichten (zum Terminal hin) */
|
||||
.terminal-label-cell.label-row-bottom {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.terminal-label-cell.empty {
|
||||
min-height: 4px;
|
||||
}
|
||||
|
||||
/* Abgang-Label (vertikal) */
|
||||
.terminal-label {
|
||||
writing-mode: vertical-rl;
|
||||
transform: rotate(180deg);
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
line-height: 1.1;
|
||||
max-height: 90px;
|
||||
max-height: 80px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 2px 0;
|
||||
padding: 2px 4px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* Anklickbare Labels */
|
||||
.terminal-label-cell:not(.empty) {
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
.terminal-label-cell:not(.empty):active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.terminal-label-cell:not(.empty):active .terminal-label {
|
||||
background: rgba(173, 140, 79, 0.4);
|
||||
}
|
||||
|
||||
.terminal-label .cable-info {
|
||||
font-weight: normal;
|
||||
font-size: 8px;
|
||||
color: #888;
|
||||
font-size: 7px;
|
||||
color: rgba(255,255,255,0.6);
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Output-Zeile braucht mehr Platz wenn Labels vorhanden */
|
||||
/* ============================================
|
||||
GEBÜNDELTE TERMINALS (Multi-Phasen-Abgänge)
|
||||
============================================ */
|
||||
|
||||
/* Gebündeltes Label: Zentriert über alle Spalten */
|
||||
.terminal-label-cell.bundled-label {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Oben: Am unteren Rand ausrichten (zum Equipment hin) */
|
||||
.terminal-label-cell.bundled-label.label-row-top {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* Unten: Am oberen Rand ausrichten (zum Equipment hin) */
|
||||
.terminal-label-cell.bundled-label.label-row-bottom {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* Gebündeltes Label mit Pfeil */
|
||||
.terminal-label-cell.bundled-with-arrow {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bundled-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* Oben: Label zuerst, Pfeil unten (zeigt zum Automaten) */
|
||||
.label-row-top .bundled-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Unten: Pfeil oben (zeigt zum Automaten), Label unten */
|
||||
.bundled-content-bottom {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bundled-arrow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.bundled-arrow .terminal-phase {
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
color: rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
/* Platzhalter für gebündelte Terminals (keine Pfeile mehr in Zeile 2/4) */
|
||||
.terminal-point.bundled-placeholder {
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
/* Hauptterminal bei Bündelung */
|
||||
.terminal-point.bundled-main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Rand-Terminals bei Bündelung: Gedimmt */
|
||||
.terminal-point.bundled-edge {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Terminal-Connector wird nicht mehr angezeigt */
|
||||
.terminal-connector {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
VERBINDUNGSLINIEN (SVG Overlay)
|
||||
============================================ */
|
||||
|
||||
.connection-lines-svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.connection-shadow {
|
||||
fill: none;
|
||||
stroke: rgba(0, 0, 0, 0.3);
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.connection-line {
|
||||
fill: none;
|
||||
stroke: var(--colortextmuted);
|
||||
stroke-width: 2.5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* Grid-Rows: 1=Labels oben, 2=Terminals oben, 3=Equipment, 4=Terminals unten, 5=Labels unten */
|
||||
/* Add Button in Carrier (letzte Spalte, Zeile 2) */
|
||||
.btn-add-equipment {
|
||||
display: flex;
|
||||
|
|
@ -1179,6 +1387,25 @@ body {
|
|||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
/* Protection Section (FI/RCD-Zuordnung) */
|
||||
.protection-section {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--colorborder);
|
||||
}
|
||||
|
||||
.protection-section label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.protection-section .icon-small {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: var(--butactionbg);
|
||||
}
|
||||
|
||||
#eq-dynamic-fields .form-select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
|
@ -1228,6 +1455,12 @@ body {
|
|||
height: 20px;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
font-size: 12px;
|
||||
color: var(--colortextmuted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 14px;
|
||||
color: var(--colortextmuted);
|
||||
|
|
@ -1349,11 +1582,9 @@ body {
|
|||
.phase-btn[data-type="L3"].selected { border-color: #888; background: rgba(100,100,100,0.2); }
|
||||
.phase-btn[data-type="N"].selected { border-color: #0066cc; background: rgba(0,102,204,0.2); }
|
||||
.phase-btn[data-type="PE"].selected { border-color: #27ae60; background: rgba(39,174,96,0.2); }
|
||||
.phase-btn[data-type="L1N"].selected { border-color: #8B4513; background: rgba(139,69,19,0.2); }
|
||||
.phase-btn[data-type="LN"].selected { border-color: #8B4513; background: rgba(139,69,19,0.2); }
|
||||
.phase-btn[data-type="3P"].selected { border-color: #e74c3c; background: rgba(231,76,60,0.2); }
|
||||
.phase-btn[data-type="3P+N"].selected { border-color: #e74c3c; background: rgba(231,76,60,0.2); }
|
||||
.phase-btn[data-type="L2N"].selected { border-color: #555; background: rgba(50,50,50,0.3); }
|
||||
.phase-btn[data-type="L3N"].selected { border-color: #888; background: rgba(100,100,100,0.2); }
|
||||
.phase-btn[data-type="DATA"].selected { border-color: #9b59b6; background: rgba(155,89,182,0.2); }
|
||||
|
||||
/* Abgangsseite-Buttons */
|
||||
|
|
@ -1784,3 +2015,55 @@ body {
|
|||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TERMINAL KONTEXTMENÜ
|
||||
============================================ */
|
||||
|
||||
.terminal-context-menu {
|
||||
background: var(--colorbacktitle);
|
||||
border: 1px solid var(--colorborder);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||
overflow: hidden;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.tcm-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
cursor: pointer;
|
||||
color: var(--colortext);
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.tcm-item:not(:last-child) {
|
||||
border-bottom: 1px solid var(--colorborder);
|
||||
}
|
||||
|
||||
.tcm-item:hover,
|
||||
.tcm-item:active {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tcm-icon {
|
||||
font-size: 16px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Leere Terminals - neutral */
|
||||
.terminal-empty {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.terminal-dot-empty {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
border: 2px dashed rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
|
|
|
|||
58
pwa.php
58
pwa.php
|
|
@ -44,7 +44,7 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
|||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="img/pwa-icon-192.png">
|
||||
<link rel="apple-touch-icon" href="img/pwa-icon-192.png">
|
||||
<link rel="stylesheet" href="css/pwa.css?v=2.9">
|
||||
<link rel="stylesheet" href="css/pwa.css?v=5.3">
|
||||
<style>:root { --primary: <?php echo $themeColor; ?>; }</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -98,8 +98,12 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
|||
<!-- Kunden werden hier geladen -->
|
||||
</div>
|
||||
|
||||
<div id="offline-indicator" class="offline-bar hidden">
|
||||
⚡ Offline-Modus
|
||||
<!-- Zuletzt bearbeitete Kunden -->
|
||||
<div id="recent-customers" class="recent-section">
|
||||
<h3 class="recent-title">Zuletzt bearbeitet</h3>
|
||||
<div id="recent-list" class="list">
|
||||
<!-- Wird per JS gefüllt -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -177,6 +181,15 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
|||
<label>Bezeichnung</label>
|
||||
<input type="text" id="equipment-label" placeholder="Leer = automatisch (z.B. R1.3)">
|
||||
</div>
|
||||
<!-- FI/RCD-Schutz Zuordnung -->
|
||||
<div id="eq-protection-fields" class="protection-section">
|
||||
<div class="form-group">
|
||||
<label><svg viewBox="0 0 24 24" class="icon-small"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg> Schutzgerät (FI/RCD)</label>
|
||||
<select id="equipment-protection" class="form-select">
|
||||
<option value="">-- Keins --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="btn-delete-equipment" class="btn btn-danger hidden">Löschen</button>
|
||||
|
|
@ -230,21 +243,36 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
|||
<label>Bezeichnung</label>
|
||||
<input type="text" id="conn-label" class="form-input" placeholder="z.B. Küche Steckdosen">
|
||||
</div>
|
||||
<!-- Anschlussseite: Immer sichtbar (Automaten haben keine feste Richtung) -->
|
||||
<div id="conn-side-fields" class="form-group">
|
||||
<label>Anschlussseite</label>
|
||||
<div id="conn-side-grid" class="side-grid">
|
||||
<button type="button" class="side-btn selected" data-side="bottom">▼ Unten</button>
|
||||
<button type="button" class="side-btn" data-side="top">▲ Oben</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bundle-Option: Nur bei Abgängen + breitem Equipment sichtbar -->
|
||||
<div id="conn-bundle-fields" class="form-group hidden">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="conn-bundle-all">
|
||||
<span>Alle Terminals belegen (Drehstrom-Verbraucher)</span>
|
||||
</label>
|
||||
<p class="hint-text">Für E-Herd, Durchlauferhitzer u.ä. – ein Abgang belegt alle Klemmen</p>
|
||||
</div>
|
||||
<!-- Medium-Felder: Nur bei Abgängen sichtbar -->
|
||||
<div id="conn-output-fields">
|
||||
<div class="form-group">
|
||||
<label>Abgangsseite</label>
|
||||
<div id="conn-side-grid" class="side-grid">
|
||||
<button type="button" class="side-btn selected" data-side="bottom">▼ Unten</button>
|
||||
<button type="button" class="side-btn" data-side="top">▲ Oben</button>
|
||||
</div>
|
||||
<label>Kabeltyp</label>
|
||||
<select id="conn-medium-type" class="form-select">
|
||||
<option value="">-- Auswählen --</option>
|
||||
<!-- Wird per JS gefüllt -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Medium</label>
|
||||
<input type="text" id="conn-medium-type" class="form-input" placeholder="z.B. NYM-J, CAT6">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Spezifikation</label>
|
||||
<input type="text" id="conn-medium-spec" class="form-input" placeholder="z.B. 3x1,5mm²">
|
||||
<label>Querschnitt</label>
|
||||
<select id="conn-medium-spec" class="form-select">
|
||||
<option value="">-- Zuerst Kabeltyp wählen --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Länge</label>
|
||||
|
|
@ -346,6 +374,6 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
|||
window.DOLIBARR_URL = '<?php echo DOL_URL_ROOT; ?>';
|
||||
window.MODULE_URL = '<?php echo DOL_URL_ROOT; ?>/custom/kundenkarte';
|
||||
</script>
|
||||
<script src="js/pwa.js?v=2.9"></script>
|
||||
<script src="js/pwa.js?v=5.1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
20
sw.js
20
sw.js
|
|
@ -3,19 +3,20 @@
|
|||
* Offline-First für Schaltschrank-Dokumentation
|
||||
*/
|
||||
|
||||
const CACHE_NAME = 'kundenkarte-pwa-v2.9';
|
||||
const OFFLINE_CACHE = 'kundenkarte-offline-v2.9';
|
||||
const CACHE_NAME = 'kundenkarte-pwa-v6.1';
|
||||
const OFFLINE_CACHE = 'kundenkarte-offline-v6.1';
|
||||
|
||||
// Statische Assets die immer gecached werden
|
||||
// Statische Assets die immer gecached werden (ohne Query-String)
|
||||
const STATIC_ASSETS = [
|
||||
'pwa.php',
|
||||
'css/pwa.css',
|
||||
'js/pwa.js',
|
||||
'img/pwa-icon-192.png',
|
||||
'img/pwa-icon-512.png',
|
||||
'../../../includes/jquery/js/jquery.min.js'
|
||||
];
|
||||
|
||||
// Assets mit Versions-Query-String - NICHT cachen, immer vom Netzwerk laden
|
||||
const VERSIONED_ASSETS = ['pwa.css', 'pwa.js'];
|
||||
|
||||
// Install - Cache statische Assets
|
||||
self.addEventListener('install', event => {
|
||||
console.log('[SW] Installing...');
|
||||
|
|
@ -55,6 +56,15 @@ self.addEventListener('fetch', event => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Versionierte Assets (CSS/JS mit ?v=X) - IMMER Netzwerk, kein Cache
|
||||
// Damit neue Versionen sofort geladen werden
|
||||
if (url.search && VERSIONED_ASSETS.some(a => url.pathname.includes(a))) {
|
||||
event.respondWith(
|
||||
fetch(event.request).catch(() => caches.match(event.request))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// AJAX Requests - Netzwerk mit Offline-Fallback
|
||||
if (url.pathname.includes('/ajax/')) {
|
||||
event.respondWith(
|
||||
|
|
|
|||
Loading…
Reference in a new issue