Compare commits
23 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 785bb9f66f | |||
| b8b2655cd0 | |||
| 71272fa425 | |||
| 16e51a799a | |||
| 8826c286ef | |||
| 4181efacdb | |||
| 848232c5a6 | |||
| 95e1860940 | |||
| 6b3b6d7e95 | |||
| 4540b8c595 | |||
| be3a53e77e | |||
| 89a4db4d21 | |||
| 50ae4e4a08 | |||
| 5f23727202 | |||
| 7de0349808 | |||
| 65f24495e6 | |||
| c4338c8d7a | |||
| 3b9daeb238 | |||
| 7a548c87e2 | |||
| a14b33b7c7 | |||
| 66abaa088d | |||
| aa67a77d16 | |||
| fd8d11e764 |
45 changed files with 10355 additions and 507 deletions
209
CLAUDE.md
209
CLAUDE.md
|
|
@ -99,14 +99,62 @@ Alle Datenbankänderungen werden als idempotente Migrationen in `modKundenKarte.
|
||||||
|
|
||||||
## Dateistruktur
|
## Dateistruktur
|
||||||
|
|
||||||
|
### Tabs
|
||||||
- `tabs/anlagen.php` - Hauptansicht für Anlagen auf Kundenebene
|
- `tabs/anlagen.php` - Hauptansicht für Anlagen auf Kundenebene
|
||||||
- `tabs/contact_anlagen.php` - Anlagen für Kontakte
|
- `tabs/contact_anlagen.php` - Anlagen für Kontakte
|
||||||
- `tabs/favoriteproducts.php` - Lieblingsprodukte auf Kundenebene
|
- `tabs/favoriteproducts.php` - Lieblingsprodukte auf Kundenebene
|
||||||
- `tabs/contact_favoriteproducts.php` - Lieblingsprodukte für Kontakte
|
- `tabs/contact_favoriteproducts.php` - Lieblingsprodukte für Kontakte
|
||||||
|
|
||||||
|
### Admin
|
||||||
- `admin/anlage_types.php` - Verwaltung der Element-Typen
|
- `admin/anlage_types.php` - Verwaltung der Element-Typen
|
||||||
- `ajax/` - AJAX-Endpunkte für dynamische Funktionen
|
- `admin/building_types.php` - Verwaltung der Gebäude-Typen
|
||||||
- `js/kundenkarte.js` - Alle JavaScript-Komponenten
|
- `admin/equipment_types.php` - Verwaltung der Equipment-Typen
|
||||||
- `css/kundenkarte.css` - Alle Styles (Dark Mode)
|
- `admin/setup.php` - Modul-Einstellungen
|
||||||
|
|
||||||
|
### Klassen (class/)
|
||||||
|
- `anlage.class.php` - Haupt-Anlage-Klasse
|
||||||
|
- `anlagetype.class.php` - Element-Typen (fetchAllBySystem mit color!)
|
||||||
|
- `buildingtype.class.php` - Gebäude-Typen
|
||||||
|
- `anlageaccessory.class.php` - Zubehör mit CRUD + Lieferantenbestellung
|
||||||
|
- `anlageconnection.class.php` - Kabelverbindungen (Anlagen-Ebene)
|
||||||
|
- `anlagefile.class.php` - Datei-Anhänge
|
||||||
|
- `anlagebackup.class.php` - Backup/Restore
|
||||||
|
- `auditlog.class.php` - Änderungsprotokoll
|
||||||
|
- `equipment.class.php` - Equipment-Instanzen auf Hutschienen
|
||||||
|
- `equipmenttype.class.php` - Equipment-Typ-Vorlagen (LS, FI, Neozed etc.)
|
||||||
|
- `equipmentcarrier.class.php` - Hutschienen (DIN-Rails)
|
||||||
|
- `equipmentpanel.class.php` - Schaltschrankfelder (Panels)
|
||||||
|
- `equipmentconnection.class.php` - Verbindungen im Schaltplan-Editor
|
||||||
|
- `terminalbridge.class.php` - Terminal-Brücken
|
||||||
|
- `mediumtype.class.php` - Leitungstypen
|
||||||
|
- `busbartype.class.php` - Sammelschienen-Typen
|
||||||
|
|
||||||
|
### Libraries (lib/)
|
||||||
|
- `kundenkarte.lib.php` - Allgemeine Hilfs-Funktionen
|
||||||
|
- `graph_view.lib.php` - Shared Graph-Funktionen (Toolbar, Container, Legende)
|
||||||
|
- `wiring_diagram.lib.php` - Leitungslaufplan + Verteilungs-Tabellen (~2.130 Zeilen)
|
||||||
|
|
||||||
|
### AJAX-Endpunkte (ajax/) — 30+ Dateien
|
||||||
|
- `anlage.php` - Anlagen CRUD
|
||||||
|
- `equipment.php` - Equipment CRUD + Produkt-Suche
|
||||||
|
- `equipment_carrier.php` - Hutschienen CRUD
|
||||||
|
- `equipment_panel.php` - Panel CRUD
|
||||||
|
- `equipment_connection.php` - Verbindungen CRUD
|
||||||
|
- `anlage_accessory.php` - Zubehör CRUD + Bestellung
|
||||||
|
- `graph_data.php` - Cytoscape Graph-Daten
|
||||||
|
- `graph_save_positions.php` - Graph-Positionen speichern
|
||||||
|
- `export_schematic_pdf.php` - Schaltplan PDF-Export
|
||||||
|
- `export_wiring_diagram_pdf.php` - Leitungslaufplan PDF-Export (separates Feature)
|
||||||
|
- `export_tree_pdf.php` - Baum PDF-Export
|
||||||
|
- `file_preview.php` - Datei-Vorschau Tooltip
|
||||||
|
- `pwa_api.php` - PWA-Endpoints
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- `js/kundenkarte.js` - Haupt-JS (~15.600 Zeilen)
|
||||||
|
- `js/kundenkarte_cytoscape.js` - Graph-JS (~900 Zeilen)
|
||||||
|
- `js/pwa.js` - PWA-JS (~3.400 Zeilen)
|
||||||
|
- `css/kundenkarte.css` - Alle Styles (Dark Mode Theme)
|
||||||
|
- `css/pwa.css` - PWA-Styles
|
||||||
|
|
||||||
## Wichtige Hinweise
|
## Wichtige Hinweise
|
||||||
|
|
||||||
|
|
@ -135,7 +183,7 @@ Offline-fähige Progressive Web App für Elektriker zur Schaltschrank-Dokumentat
|
||||||
- `ajax/pwa_api.php` - Alle AJAX-Endpoints für die PWA
|
- `ajax/pwa_api.php` - Alle AJAX-Endpoints für die PWA
|
||||||
- `js/pwa.js` - Komplette App-Logik (jQuery, als IIFE mit jQuery-Parameter)
|
- `js/pwa.js` - Komplette App-Logik (jQuery, als IIFE mit jQuery-Parameter)
|
||||||
- `css/pwa.css` - Mobile-First Design, Dolibarr Dark Theme Variablen
|
- `css/pwa.css` - Mobile-First Design, Dolibarr Dark Theme Variablen
|
||||||
- `sw.js` - Service Worker für Offline-Cache (v6.1)
|
- `sw.js` - Service Worker für Offline-Cache (v12.4)
|
||||||
- `manifest.json` - Web App Manifest für Installation
|
- `manifest.json` - Web App Manifest für Installation
|
||||||
|
|
||||||
### Workflow
|
### Workflow
|
||||||
|
|
@ -212,7 +260,7 @@ Offline-fähige Progressive Web App für Elektriker zur Schaltschrank-Dokumentat
|
||||||
- `bundled_terminals = 'all'` in Connection bedeutet: Alle Terminals belegt
|
- `bundled_terminals = 'all'` in Connection bedeutet: Alle Terminals belegt
|
||||||
- Im Editor: Ein Pfeil spannt über alle Terminals des Equipment
|
- Im Editor: Ein Pfeil spannt über alle Terminals des Equipment
|
||||||
- Label wird zentriert über alle Terminals angezeigt
|
- Label wird zentriert über alle Terminals angezeigt
|
||||||
- Checkbox "Alle bündeln" nur bei Equipment mit >1 Terminal sichtbar
|
- Checkbox "Alle Terminals bündeln" im Abgang-Dialog (Website + PWA), nur bei Equipment mit >1 Terminal
|
||||||
|
|
||||||
### Terminal-Konfiguration (v7.5)
|
### Terminal-Konfiguration (v7.5)
|
||||||
- `terminals_config` JSON im Equipment-Typ definiert Terminal-Positionen
|
- `terminals_config` JSON im Equipment-Typ definiert Terminal-Positionen
|
||||||
|
|
@ -226,3 +274,154 @@ Offline-fähige Progressive Web App für Elektriker zur Schaltschrank-Dokumentat
|
||||||
- Zeile 3: Equipment-Blöcke
|
- Zeile 3: Equipment-Blöcke
|
||||||
- Zeile 4: Terminal-Punkte unten (terminal-point.terminal-row-bottom)
|
- Zeile 4: Terminal-Punkte unten (terminal-point.terminal-row-bottom)
|
||||||
- Zeile 5: Abgang-Labels unten (terminal-label-cell.label-row-bottom)
|
- Zeile 5: Abgang-Labels unten (terminal-label-cell.label-row-bottom)
|
||||||
|
|
||||||
|
## Ausgebaut-Status (v8.0)
|
||||||
|
|
||||||
|
### Spalten
|
||||||
|
- `decommissioned` (tinyint DEFAULT 0) in `llx_kundenkarte_anlage`
|
||||||
|
- `date_decommissioned` (date NULL) in `llx_kundenkarte_anlage`
|
||||||
|
|
||||||
|
### Verhalten
|
||||||
|
- Toggle per Button am Element im Baum und Graph
|
||||||
|
- Ausgebaute Elemente: `opacity: 0.4`, dashed border, Badge "Ausgebaut"
|
||||||
|
- Toggle-Button in Toolbar: Klasse `.show-decommissioned` auf `.kundenkarte-tree`
|
||||||
|
- Admin-Setting `KUNDENKARTE_SHOW_DECOMMISSIONED` für Standard-Sichtbarkeit
|
||||||
|
- Graph-View: Nodes mit Klasse `.decommissioned` (35% opacity, dashed border)
|
||||||
|
|
||||||
|
## Mein Betrieb / Werkzeuge (v8.5)
|
||||||
|
|
||||||
|
### Übersicht
|
||||||
|
Eigene Seite für Firmen-Equipment (Werkzeuge, Maschinen, Messgeräte).
|
||||||
|
|
||||||
|
### Dateien
|
||||||
|
- `werkzeuge.php` - Baumansicht für eigene Firma (fk_soc = mysoc->id)
|
||||||
|
- `class/anlageaccessory.class.php` - Zubehör-Klasse mit CRUD + Bestellfunktion
|
||||||
|
- `ajax/anlage_accessory.php` - AJAX-Endpunkte für Zubehör
|
||||||
|
|
||||||
|
### System
|
||||||
|
- Neues System `WERKZEUG` (ID 26) in `llx_c_kundenkarte_anlage_system`
|
||||||
|
- Menüpunkt unter KundenKarte > Mein Betrieb
|
||||||
|
- System-Filter fix auf "WERKZEUG"
|
||||||
|
|
||||||
|
### Produkt-Zuordnung
|
||||||
|
- `fk_product` in `llx_kundenkarte_anlage` verknüpft mit Dolibarr-Produkt
|
||||||
|
- Autocomplete-Suche via `ajax/equipment.php?action=get_products`
|
||||||
|
- Anzeige: Ref + Label + Preis unter Element im Baum
|
||||||
|
- **Typ-Flag `has_product`**: Steuert ob Produkt-Zeile im Formular sichtbar ist
|
||||||
|
- `data-has-product` Attribut auf `<option>` für JS-Steuerung
|
||||||
|
|
||||||
|
### Zubehör-System
|
||||||
|
- Tabelle `llx_kundenkarte_anlage_accessory` (fk_anlage, fk_product, qty, rang, note)
|
||||||
|
- Typ-Flag `has_accessories` steuert Verfügbarkeit
|
||||||
|
- Lieferantenbestellung via `CommandeFournisseur` generierbar
|
||||||
|
|
||||||
|
## Terminal-Farbpropagierung (v8.6)
|
||||||
|
|
||||||
|
### Übersicht
|
||||||
|
Phasenfarben werden von den Eingängen (Anschlusspunkten) durch den gesamten Schaltplan propagiert.
|
||||||
|
|
||||||
|
### Dual-Map System in JS
|
||||||
|
- `_terminalPhaseMap` — `{eqId: {termId: "L1"}}` — Phasennamen für Busbar-Logik
|
||||||
|
- `_terminalColorMap` — `{eqId: {termId: "#hex"}}` — Tatsächliche Hex-Farben (von `conn.color`)
|
||||||
|
- Aufgebaut in `buildTerminalPhaseMap()` (JS Zeile ~5499)
|
||||||
|
|
||||||
|
### Propagierungsreihenfolge
|
||||||
|
1. **Inputs** (Anschlusspunkte): `conn.color` als Startfarbe, `connection_type` als Phase
|
||||||
|
2. **Block-Durchreichung**: Top-Terminal ↔ Bottom-Terminal (paarweise)
|
||||||
|
3. **Leitungen**: Source → Target und umgekehrt
|
||||||
|
4. **Busbars**: Nur eingespeiste Phasen verteilen (fedPhases/fedColors)
|
||||||
|
|
||||||
|
### Farbzugriff
|
||||||
|
- `getTerminalConnectionColor(eqId, termId)` — Liest `_terminalColorMap`, Fallback auf Connection-Farben
|
||||||
|
- Input-Labels werden als **farbige Badges** angezeigt (Phase-Name als weißer Text auf inputColor-Hintergrund)
|
||||||
|
|
||||||
|
### Phasenfarben (PHASE_COLORS)
|
||||||
|
```
|
||||||
|
L1: '#8B4513' (braun) L2: '#1a1a1a' (schwarz) L3: '#666666' (grau)
|
||||||
|
N: '#0066cc' (blau) PE: '#27ae60' (grün)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Leitungslaufplan PDF-Export (v8.6)
|
||||||
|
|
||||||
|
### Übersicht
|
||||||
|
Normgerechter Stromlaufplan in aufgelöster Darstellung (DIN EN 61082) als PDF-Export.
|
||||||
|
**Komplett separates Feature** — kann durch Löschen von 2 Dateien + 8 Zeilen rückstandsfrei entfernt werden.
|
||||||
|
|
||||||
|
### Dateien
|
||||||
|
- `lib/wiring_diagram.lib.php` — Kernlogik (~2.130 Zeilen)
|
||||||
|
- `WiringDiagramAnalyzer` — Lädt Daten, baut Phase-Map (PHP-Port), tracet Strompfade
|
||||||
|
- `WiringDiagramRenderer` — Zeichnet PDF mit TCPDF
|
||||||
|
- `ajax/export_wiring_diagram_pdf.php` — Endpoint
|
||||||
|
- Buttons in `tabs/anlagen.php` + `tabs/contact_anlagen.php` (je 4 Zeilen)
|
||||||
|
|
||||||
|
### PDF-Inhalt (5 Teile)
|
||||||
|
1. **Leitungslaufplan** (A3 quer) — L1/L2/L3 horizontal oben, vertikale Strompfade pro Abgang, FI/RCD + LS-Symbole, Abgang-Pfeile, N/PE unten
|
||||||
|
2. **Abgangsverzeichnis** (A3 quer) — Tabelle pro Hutschiene mit: Abg.Nr, Bezeichnung, Phase, Absicherung, Kabel, Schutzgerät
|
||||||
|
3. **Kundenansicht** (A4 hoch) — `renderKundenansicht()` — Einfache Tabelle: Nr | Verbraucher | Räumlichkeit, gruppiert nach Feld/Reihe
|
||||||
|
4. **Technikeransicht** (A4 hoch) — `renderTechnikeransicht()` — Erweiterte Tabelle: R.Klem | FI | Nr | Verbraucher | Räumlichkeit | Typ
|
||||||
|
5. **Mini-Legende** — Phasenfarben DIN VDE auf Seite 1 unten links
|
||||||
|
|
||||||
|
### Abgangsnummer-Format
|
||||||
|
`R{Reihe}.{Position}` z.B. `R1.3` = Carrier-Position 1, Equipment-TE-Position 3
|
||||||
|
|
||||||
|
### Strompfad-Tracing
|
||||||
|
Pro Abgang (Connection mit `fk_target = NULL`):
|
||||||
|
1. Source-Equipment = LS-Schalter
|
||||||
|
2. Phase aus `terminalPhaseMap`
|
||||||
|
3. FI/RCD über `Equipment.fk_protection`
|
||||||
|
4. Kabel: `medium_type` + `medium_spec` + `medium_length`
|
||||||
|
5. Sortierung: FI-Gruppe → Carrier-Position → Equipment-Position
|
||||||
|
|
||||||
|
### Phase-Map (PHP-Port)
|
||||||
|
`WiringDiagramAnalyzer::buildPhaseMap()` ist ein 1:1 PHP-Port von JS `buildTerminalPhaseMap()`:
|
||||||
|
- Iterativ (max 20 Durchläufe) bis keine Änderungen mehr
|
||||||
|
- Inputs → Block-Durchreichung → Leitungen → Busbar-Verteilung
|
||||||
|
|
||||||
|
### VDE-Symbole
|
||||||
|
- LS-Schalter: Schräge Kontaktlinie + Auslöser-Rechteck
|
||||||
|
- FI/RCD: Rechteck mit Kreis + Vertikallinie (Differenzstrom-Symbol)
|
||||||
|
- Gezeichnet mit TCPDF-Primitiven (Line, Rect, Circle, Polygon)
|
||||||
|
|
||||||
|
## Räumlichkeit / output_location (v8.6)
|
||||||
|
|
||||||
|
### Übersicht
|
||||||
|
Zusätzliches Textfeld am Abgang (Output-Connection) für den Raum/Ort des Verbrauchers (z.B. "Küche", "Bad OG").
|
||||||
|
|
||||||
|
### Datenbank
|
||||||
|
- Spalte `output_location` (varchar 255) in `llx_kundenkarte_equipment_connection`
|
||||||
|
- Migration: `migrate_v1110_output_location()` in `modKundenKarte.class.php`
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- `EquipmentConnection::$output_location` — Property, in create/update/fetch
|
||||||
|
- `ajax/equipment_connection.php` — create_output + update + list_all
|
||||||
|
- `ajax/pwa_api.php` — get_carrier_equipment + create_connection + update_connection
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **Website**: Eingabefeld im `renderAbgangDialog()` (kundenkarte.js)
|
||||||
|
- **PWA**: Eingabefeld `#conn-location` im Connection-Modal (pwa.php + pwa.js)
|
||||||
|
- **Anzeige im Schaltplan**:
|
||||||
|
- Website SVG: `<tspan>` kursiv nach Label mit ` · ` Trennzeichen
|
||||||
|
- PWA Grid: `<span class="output-location">` kursiv unter dem Label-Text
|
||||||
|
- **PDF**: In Kundenansicht + Technikeransicht als eigene Tabellenspalte
|
||||||
|
|
||||||
|
## Select2 mit Kategorie-Filter
|
||||||
|
|
||||||
|
### Problem & Lösung
|
||||||
|
In anlagen.php und contact_anlagen.php gibt es einen Kategorie-Filter (Gebäude/Element),
|
||||||
|
der die Typ-Options per JS filtert und Select2 neu initialisiert.
|
||||||
|
|
||||||
|
**Wichtig**: Nach `initSelect2()` muss der Wert mit `.trigger("change")` gesetzt werden,
|
||||||
|
damit Select2 den aktuellen Wert korrekt anzeigt:
|
||||||
|
```javascript
|
||||||
|
initSelect2();
|
||||||
|
if (currentVal && $typeSelect.find('option[value="' + currentVal + '"]').length) {
|
||||||
|
$typeSelect.val(currentVal).trigger("change");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ablauf in filterTypes()
|
||||||
|
1. `currentVal` sichern
|
||||||
|
2. HTML aus `allOptionsHtml` zurücksetzen
|
||||||
|
3. Nicht passende Options entfernen
|
||||||
|
4. Select2 initialisieren
|
||||||
|
5. Wert mit `.trigger("change")` wiederherstellen
|
||||||
|
|
|
||||||
153
ChangeLog.md
153
ChangeLog.md
|
|
@ -1,5 +1,158 @@
|
||||||
# CHANGELOG MODULE KUNDENKARTE FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
# CHANGELOG MODULE KUNDENKARTE FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||||
|
|
||||||
|
## 11.1 (2026-03)
|
||||||
|
|
||||||
|
### Neue Features
|
||||||
|
|
||||||
|
- **Auto-Benennung Felder und Hutschienen**: Automatischer Name wenn Label leer gelassen wird
|
||||||
|
- Felder: "Feld N" (N = Anzahl Felder in der Anlage)
|
||||||
|
- Hutschienen: "RN" (N = Anzahl Hutschienen im Panel)
|
||||||
|
- Gilt sowohl beim Erstellen als auch beim Bearbeiten (leer lassen = Auto-Name)
|
||||||
|
- Funktioniert in PWA und Website gleichermassen
|
||||||
|
- PHP-Klassen `EquipmentPanel` und `EquipmentCarrier` berechnen Namen server-seitig
|
||||||
|
- PWA berechnet Namen client-seitig (inkl. korrektem +1 bei CREATE vs. ohne bei UPDATE)
|
||||||
|
- Website: Carrier-Dialog zeigt Platzhalter "z.B. R1 (automatisch)"
|
||||||
|
|
||||||
|
### Verbesserungen
|
||||||
|
|
||||||
|
- **PWA: Mehr Platz fuer Abgangs-Labels**: Lange Leitungsbezeichnungen vollstaendig sichtbar
|
||||||
|
- `.terminal-label` max-height: 80px → 130px
|
||||||
|
- `.terminal-label-cell` min-height: 20px → 30px
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- **Service Worker**: chrome-extension:// URLs verursachten TypeError beim Cachen
|
||||||
|
- Protocol-Check am Anfang des Fetch-Handlers verhindert Fehler
|
||||||
|
- Betrifft Browser mit installierten Extensions (Chrome, Edge)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11.0 (2026-03)
|
||||||
|
|
||||||
|
### Neue Features
|
||||||
|
|
||||||
|
- **Terminal-Farben nach Verbindung**: Terminals zeigen die Farbe der angeschlossenen Leitung
|
||||||
|
- Grau = keine Verbindung, farbig = Leitung angeschlossen
|
||||||
|
- Farbe entspricht dem Leitungstyp (L1=braun, L2=schwarz, L3=grau, N=blau, PE=gruen)
|
||||||
|
- Neue Hilfsfunktion `getTerminalConnectionColor()`
|
||||||
|
|
||||||
|
- **Leitungen hinter Bloecken**: Wires werden nun hinter den Equipment-Bloecken gerendert
|
||||||
|
- Layer-Reihenfolge geaendert: connections-layer vor blocks-layer
|
||||||
|
- Leitungen "verschwinden" hinter Bloecken und kommen auf der anderen Seite wieder raus
|
||||||
|
- Professionelleres Erscheinungsbild wie in echten Schaltplan-Editoren
|
||||||
|
|
||||||
|
- **Wire-Segment-Dragging**: Leitungen koennen verschoben werden ohne Verbindungen zu verlieren
|
||||||
|
- Shift+Klick oder Mittlere Maustaste auf Leitungssegment zum Ziehen
|
||||||
|
- Horizontale Segmente nur vertikal verschiebbar, vertikale nur horizontal
|
||||||
|
- Start- und End-Segmente (an Terminals) bleiben fix
|
||||||
|
- Automatisches Grid-Snapping (25px)
|
||||||
|
- Live-Vorschau waehrend dem Ziehen
|
||||||
|
- Neue Funktionen: `parsePathToPoints()`, `pointsToPath()`, `findClickedSegment()`
|
||||||
|
- `startWireDrag()`, `handleWireDragMove()`, `finishWireDrag()`, `cancelWireDrag()`
|
||||||
|
|
||||||
|
- **Busbar-Typen aus Datenbank**: Phasenschienen-Typen dynamisch aus DB laden
|
||||||
|
- Edit-Dialog nutzt jetzt `busbarTypes` Array statt hardcodierter Optionen
|
||||||
|
- `fk_busbar_type` wird beim Update korrekt gespeichert
|
||||||
|
- Admin-Seite fuer Busbar-Typen mit phases_config JSON-Feld
|
||||||
|
|
||||||
|
- **PWA: Farbpropagierung bei Einspeisung**: Automatische Farbuebernahme
|
||||||
|
- Bei Auswahl einer Input-Phase (L1/L2/L3/N/PE) wird Farbe automatisch gesetzt
|
||||||
|
- Funktion `propagateInputColor()` aktualisiert Farben auf Abgaengen
|
||||||
|
- Phase-Matching: L1 matched L1, LN, L1N; N matched N; etc.
|
||||||
|
- Funktioniert online und offline (mit Queue)
|
||||||
|
|
||||||
|
- **PWA: N-Phase als Einspeisung**: Neutralleiter jetzt als Input-Phase waehlbar
|
||||||
|
- INPUT_PHASES erweitert um 'N': ['L1', 'L2', 'L3', 'N', 'PE']
|
||||||
|
- 3P und 3P+N entfernt (nur Einzel-Phasen)
|
||||||
|
|
||||||
|
### Verbesserungen
|
||||||
|
|
||||||
|
- **Zeichenmodus-Verhalten**: Konsistentes Verhalten im manuellen Zeichenmodus
|
||||||
|
- Rechtsklick bricht nur aktuelle Linie ab, nicht den Zeichenmodus
|
||||||
|
- Escape-Taste verhaelt sich identisch (Linie abbrechen, Modus bleibt aktiv)
|
||||||
|
- Crosshair-Cursor ueberall im SVG waehrend Zeichenmodus (nicht nur auf Terminals)
|
||||||
|
- Keine Unterbrechung mehr beim Klicken auf leere Flaechen
|
||||||
|
|
||||||
|
- **Terminal Hit-Area**: Verbesserte Klickbarkeit der Terminals
|
||||||
|
- 30px Radius fuer einfacheres Anklicken
|
||||||
|
- `pointer-events:all` fuer zuverlaessige Event-Erfassung
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- Cursor zeigte nicht ueberall Crosshair im Zeichenmodus
|
||||||
|
- Zeichenmodus wurde bei Rechtsklick/Escape komplett beendet statt nur Linie abzubrechen
|
||||||
|
- Junction-Verbindungen (Terminal zu Leitung) werden korrekt gerendert
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8.6 (2026-03)
|
||||||
|
|
||||||
|
### Neue Features
|
||||||
|
|
||||||
|
- **has_product Typ-Flag**: Produkt-Zuordnung pro Element- und Gebaeudetyp ein-/abschaltbar
|
||||||
|
- Neue Checkbox "Produkt-Zuordnung" in Admin > Element-Typen und Gebaeudetypen
|
||||||
|
- Produkt-Zeile im Formular wird per JS dynamisch ein-/ausgeblendet je nach Typ
|
||||||
|
- `data-has-product` und `data-has-accessories` Attribute auf Options fuer JS-Steuerung
|
||||||
|
|
||||||
|
- **Decommissioned Default-Setting**: Standard-Sichtbarkeit fuer ausgebaute Elemente
|
||||||
|
- Neues Admin-Setting `KUNDENKARTE_SHOW_DECOMMISSIONED` unter Einstellungen
|
||||||
|
- Toggle-Button startet mit Admin-Default in allen 3 Ansichten (Kunden, Kontakte, Mein Betrieb)
|
||||||
|
- Tree-Div erhaelt `show-decommissioned` CSS-Klasse basierend auf Setting
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- **Select2 Typ-Select im Edit-Modus**: Wert wurde nicht angezeigt beim Bearbeiten
|
||||||
|
- Ursache: Wert wurde vor Select2-Initialisierung gesetzt ohne `.trigger("change")`
|
||||||
|
- Fix: Wert wird jetzt nach `initSelect2()` mit Trigger gesetzt
|
||||||
|
- Betrifft: anlagen.php und contact_anlagen.php (Kategorie-Filter mit Select2)
|
||||||
|
|
||||||
|
- **Fehlende color-Property**: `fetchAllBySystem()` hat `$type->color` nicht gesetzt
|
||||||
|
- Options mit `data-color` Attribut hatten leeren Wert
|
||||||
|
|
||||||
|
### Datenbank-Aenderungen
|
||||||
|
|
||||||
|
- Neue Spalte `has_product` (tinyint) in `llx_kundenkarte_anlage_type`
|
||||||
|
- Neue Spalte `has_product` (tinyint) in `llx_kundenkarte_building_type`
|
||||||
|
- Migration `migrate_v860_has_product()` in modKundenKarte.class.php
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8.5 (2026-03)
|
||||||
|
|
||||||
|
### Neue Features
|
||||||
|
|
||||||
|
- **Mein Betrieb (Werkzeuge & Maschinen)**: Eigene Baumansicht fuer Firmen-Equipment
|
||||||
|
- Neue Seite `werkzeuge.php` mit System-Tabs, Baum und Graph-Ansicht
|
||||||
|
- Neues System "WERKZEUG" fuer firmeneigene Geraete
|
||||||
|
- Menue-Eintrag unter KundenKarte > Mein Betrieb
|
||||||
|
|
||||||
|
- **Zubehoer-System**: Zubehoer und Ersatzteile pro Anlage zuordnen
|
||||||
|
- Neue Klasse `AnlageAccessory` mit CRUD und Bestellfunktion
|
||||||
|
- Lieferantenbestellung direkt aus Zubehoer-Liste generierbar
|
||||||
|
- Typ-Flag `has_accessories` steuert Verfuegbarkeit pro Typ
|
||||||
|
|
||||||
|
- **Produkt-Zuordnung**: Dolibarr-Produkt mit Anlage verknuepfen
|
||||||
|
- `fk_product` Spalte in `llx_kundenkarte_anlage`
|
||||||
|
- Autocomplete-Suche im Formular
|
||||||
|
- Produkt-Details (Ref, Label, Preis) unter Element im Baum
|
||||||
|
|
||||||
|
- **Ausgebaut-Status**: Anlagen als "ausgebaut" markieren
|
||||||
|
- Toggle per Rechtsklick/Button am Element
|
||||||
|
- Ausgebaute Elemente ausgegraut (opacity 0.4, dashed border)
|
||||||
|
- Toggle-Button in Toolbar zum Ein-/Ausblenden
|
||||||
|
- Ausbaudatum wird erfasst und angezeigt
|
||||||
|
|
||||||
|
### Datenbank-Aenderungen
|
||||||
|
|
||||||
|
- Neue Spalte `decommissioned` in `llx_kundenkarte_anlage`
|
||||||
|
- Neue Spalte `date_decommissioned` in `llx_kundenkarte_anlage`
|
||||||
|
- Neue Spalte `fk_product` in `llx_kundenkarte_anlage`
|
||||||
|
- Neue Spalte `has_accessories` in `llx_kundenkarte_anlage_type`
|
||||||
|
- Neue Tabelle `llx_kundenkarte_anlage_accessory`
|
||||||
|
- Neues System `WERKZEUG` in `llx_c_kundenkarte_anlage_system`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 7.5 (2026-03)
|
## 7.5 (2026-03)
|
||||||
|
|
||||||
### Neue Features
|
### Neue Features
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ if ($action == 'add') {
|
||||||
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
|
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
|
||||||
$anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml'));
|
$anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml'));
|
||||||
$anlageType->can_have_equipment = GETPOSTINT('can_have_equipment');
|
$anlageType->can_have_equipment = GETPOSTINT('can_have_equipment');
|
||||||
|
$anlageType->has_accessories = GETPOSTINT('has_accessories');
|
||||||
|
$anlageType->has_product = GETPOSTINT('has_product');
|
||||||
$anlageType->picto = GETPOST('picto', 'alphanohtml');
|
$anlageType->picto = GETPOST('picto', 'alphanohtml');
|
||||||
$anlageType->color = GETPOST('color', 'alphanohtml');
|
$anlageType->color = GETPOST('color', 'alphanohtml');
|
||||||
$anlageType->position = GETPOSTINT('position');
|
$anlageType->position = GETPOSTINT('position');
|
||||||
|
|
@ -113,6 +115,8 @@ if ($action == 'update') {
|
||||||
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
|
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
|
||||||
$anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml'));
|
$anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml'));
|
||||||
$anlageType->can_have_equipment = GETPOSTINT('can_have_equipment');
|
$anlageType->can_have_equipment = GETPOSTINT('can_have_equipment');
|
||||||
|
$anlageType->has_accessories = GETPOSTINT('has_accessories');
|
||||||
|
$anlageType->has_product = GETPOSTINT('has_product');
|
||||||
$anlageType->picto = GETPOST('picto', 'alphanohtml');
|
$anlageType->picto = GETPOST('picto', 'alphanohtml');
|
||||||
$anlageType->color = GETPOST('color', 'alphanohtml');
|
$anlageType->color = GETPOST('color', 'alphanohtml');
|
||||||
$anlageType->position = GETPOSTINT('position');
|
$anlageType->position = GETPOSTINT('position');
|
||||||
|
|
@ -166,6 +170,8 @@ if ($action == 'copy' && $typeId > 0) {
|
||||||
$newType->can_be_nested = $sourceType->can_be_nested;
|
$newType->can_be_nested = $sourceType->can_be_nested;
|
||||||
$newType->allowed_parent_types = $sourceType->allowed_parent_types;
|
$newType->allowed_parent_types = $sourceType->allowed_parent_types;
|
||||||
$newType->can_have_equipment = $sourceType->can_have_equipment;
|
$newType->can_have_equipment = $sourceType->can_have_equipment;
|
||||||
|
$newType->has_accessories = $sourceType->has_accessories;
|
||||||
|
$newType->has_product = $sourceType->has_product;
|
||||||
$newType->picto = $sourceType->picto;
|
$newType->picto = $sourceType->picto;
|
||||||
$newType->color = $sourceType->color;
|
$newType->color = $sourceType->color;
|
||||||
$newType->position = $sourceType->position + 1;
|
$newType->position = $sourceType->position + 1;
|
||||||
|
|
@ -202,6 +208,10 @@ if ($action == 'add_field') {
|
||||||
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
||||||
$fieldType = GETPOST('field_type', 'aZ09');
|
$fieldType = GETPOST('field_type', 'aZ09');
|
||||||
$fieldOptions = GETPOST('field_options', 'nohtml');
|
$fieldOptions = GETPOST('field_options', 'nohtml');
|
||||||
|
// Leerzeichen um Pipe-Trennzeichen entfernen und leere Optionen entfernen
|
||||||
|
if ($fieldOptions) {
|
||||||
|
$fieldOptions = implode('|', array_filter(array_map('trim', explode('|', $fieldOptions)), 'strlen'));
|
||||||
|
}
|
||||||
$showInTree = GETPOSTINT('show_in_tree');
|
$showInTree = GETPOSTINT('show_in_tree');
|
||||||
$treeDisplayMode = GETPOST('tree_display_mode', 'aZ09');
|
$treeDisplayMode = GETPOST('tree_display_mode', 'aZ09');
|
||||||
if (empty($treeDisplayMode)) $treeDisplayMode = 'badge';
|
if (empty($treeDisplayMode)) $treeDisplayMode = 'badge';
|
||||||
|
|
@ -237,6 +247,10 @@ if ($action == 'update_field') {
|
||||||
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
||||||
$fieldType = GETPOST('field_type', 'aZ09');
|
$fieldType = GETPOST('field_type', 'aZ09');
|
||||||
$fieldOptions = GETPOST('field_options', 'nohtml');
|
$fieldOptions = GETPOST('field_options', 'nohtml');
|
||||||
|
// Leerzeichen um Pipe-Trennzeichen entfernen und leere Optionen entfernen
|
||||||
|
if ($fieldOptions) {
|
||||||
|
$fieldOptions = implode('|', array_filter(array_map('trim', explode('|', $fieldOptions)), 'strlen'));
|
||||||
|
}
|
||||||
$showInTree = GETPOSTINT('show_in_tree');
|
$showInTree = GETPOSTINT('show_in_tree');
|
||||||
$treeDisplayMode = GETPOST('tree_display_mode', 'aZ09');
|
$treeDisplayMode = GETPOST('tree_display_mode', 'aZ09');
|
||||||
if (empty($treeDisplayMode)) $treeDisplayMode = 'badge';
|
if (empty($treeDisplayMode)) $treeDisplayMode = 'badge';
|
||||||
|
|
@ -402,6 +416,16 @@ if (in_array($action, array('create', 'edit'))) {
|
||||||
print '<td><input type="checkbox" name="can_have_equipment" value="1"'.($anlageType->can_have_equipment ? ' checked' : '').'>';
|
print '<td><input type="checkbox" name="can_have_equipment" value="1"'.($anlageType->can_have_equipment ? ' checked' : '').'>';
|
||||||
print ' <span class="opacitymedium">('.$langs->trans('CanHaveEquipmentHelp').')</span></td></tr>';
|
print ' <span class="opacitymedium">('.$langs->trans('CanHaveEquipmentHelp').')</span></td></tr>';
|
||||||
|
|
||||||
|
// Hat Zubehör (Zubehör/Ersatzteile zuordnen)
|
||||||
|
print '<tr><td>'.$langs->trans('HasAccessories').'</td>';
|
||||||
|
print '<td><input type="checkbox" name="has_accessories" value="1"'.($anlageType->has_accessories ? ' checked' : '').'>';
|
||||||
|
print ' <span class="opacitymedium">('.$langs->trans('HasAccessoriesHelp').')</span></td></tr>';
|
||||||
|
|
||||||
|
// Produkt-Zuordnung erlauben
|
||||||
|
print '<tr><td>'.$langs->trans('HasProduct').'</td>';
|
||||||
|
print '<td><input type="checkbox" name="has_product" value="1"'.($anlageType->has_product ? ' checked' : '').'>';
|
||||||
|
print ' <span class="opacitymedium">('.$langs->trans('HasProductHelp').')</span></td></tr>';
|
||||||
|
|
||||||
// Allowed parent types - with multi-select UI
|
// Allowed parent types - with multi-select UI
|
||||||
print '<tr><td>'.$langs->trans('AllowedParentTypes').'</td>';
|
print '<tr><td>'.$langs->trans('AllowedParentTypes').'</td>';
|
||||||
print '<td>';
|
print '<td>';
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ if ($action == 'add' && $user->admin) {
|
||||||
$buildingType->icon = GETPOST('icon', 'alphanohtml');
|
$buildingType->icon = GETPOST('icon', 'alphanohtml');
|
||||||
$buildingType->color = GETPOST('color', 'alphanohtml');
|
$buildingType->color = GETPOST('color', 'alphanohtml');
|
||||||
$buildingType->can_have_children = GETPOSTINT('can_have_children');
|
$buildingType->can_have_children = GETPOSTINT('can_have_children');
|
||||||
|
$buildingType->has_product = GETPOSTINT('has_product');
|
||||||
$buildingType->position = GETPOSTINT('position');
|
$buildingType->position = GETPOSTINT('position');
|
||||||
$buildingType->active = GETPOSTINT('active');
|
$buildingType->active = GETPOSTINT('active');
|
||||||
|
|
||||||
|
|
@ -82,6 +83,7 @@ if ($action == 'update' && $user->admin) {
|
||||||
$buildingType->icon = GETPOST('icon', 'alphanohtml');
|
$buildingType->icon = GETPOST('icon', 'alphanohtml');
|
||||||
$buildingType->color = GETPOST('color', 'alphanohtml');
|
$buildingType->color = GETPOST('color', 'alphanohtml');
|
||||||
$buildingType->can_have_children = GETPOSTINT('can_have_children');
|
$buildingType->can_have_children = GETPOSTINT('can_have_children');
|
||||||
|
$buildingType->has_product = GETPOSTINT('has_product');
|
||||||
$buildingType->position = GETPOSTINT('position');
|
$buildingType->position = GETPOSTINT('position');
|
||||||
$buildingType->active = GETPOSTINT('active');
|
$buildingType->active = GETPOSTINT('active');
|
||||||
|
|
||||||
|
|
@ -222,6 +224,12 @@ if ($action == 'create' || $action == 'edit') {
|
||||||
print '<input type="checkbox" name="can_have_children" value="1"'.($buildingType->can_have_children || $action != 'edit' ? ' checked' : '').'>';
|
print '<input type="checkbox" name="can_have_children" value="1"'.($buildingType->can_have_children || $action != 'edit' ? ' checked' : '').'>';
|
||||||
print '</td></tr>';
|
print '</td></tr>';
|
||||||
|
|
||||||
|
// Produkt-Zuordnung erlauben
|
||||||
|
print '<tr><td>'.$langs->trans('HasProduct').'</td><td>';
|
||||||
|
print '<input type="checkbox" name="has_product" value="1"'.($buildingType->has_product ? ' checked' : '').'>';
|
||||||
|
print ' <span class="opacitymedium">('.$langs->trans('HasProductHelp').')</span>';
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
// Position
|
// Position
|
||||||
print '<tr><td>'.$langs->trans('Position').'</td><td>';
|
print '<tr><td>'.$langs->trans('Position').'</td><td>';
|
||||||
$defaultPos = $action == 'create' ? $buildingType->getNextPosition() : $buildingType->position;
|
$defaultPos = $action == 'create' ? $buildingType->getNextPosition() : $buildingType->position;
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,12 @@ if ($action == 'add') {
|
||||||
$busbarType->description = GETPOST('description', 'restricthtml');
|
$busbarType->description = GETPOST('description', 'restricthtml');
|
||||||
$busbarType->fk_system = GETPOSTINT('fk_system');
|
$busbarType->fk_system = GETPOSTINT('fk_system');
|
||||||
$busbarType->phases = GETPOST('phases', 'alphanohtml');
|
$busbarType->phases = GETPOST('phases', 'alphanohtml');
|
||||||
|
// Convert comma-separated phases_config to JSON array
|
||||||
|
$phasesConfigInput = GETPOST('phases_config', 'alphanohtml');
|
||||||
|
if (!empty($phasesConfigInput)) {
|
||||||
|
$arr = array_map('trim', explode(',', $phasesConfigInput));
|
||||||
|
$busbarType->phases_config = json_encode($arr);
|
||||||
|
}
|
||||||
$busbarType->num_lines = GETPOSTINT('num_lines');
|
$busbarType->num_lines = GETPOSTINT('num_lines');
|
||||||
$busbarType->color = GETPOST('color', 'alphanohtml');
|
$busbarType->color = GETPOST('color', 'alphanohtml');
|
||||||
$busbarType->default_color = GETPOST('default_color', 'alphanohtml');
|
$busbarType->default_color = GETPOST('default_color', 'alphanohtml');
|
||||||
|
|
@ -120,6 +126,14 @@ if ($action == 'update') {
|
||||||
$busbarType->description = GETPOST('description', 'restricthtml');
|
$busbarType->description = GETPOST('description', 'restricthtml');
|
||||||
$busbarType->fk_system = GETPOSTINT('fk_system');
|
$busbarType->fk_system = GETPOSTINT('fk_system');
|
||||||
$busbarType->phases = GETPOST('phases', 'alphanohtml');
|
$busbarType->phases = GETPOST('phases', 'alphanohtml');
|
||||||
|
// Convert comma-separated phases_config to JSON array
|
||||||
|
$phasesConfigInput = GETPOST('phases_config', 'alphanohtml');
|
||||||
|
if (!empty($phasesConfigInput)) {
|
||||||
|
$arr = array_map('trim', explode(',', $phasesConfigInput));
|
||||||
|
$busbarType->phases_config = json_encode($arr);
|
||||||
|
} else {
|
||||||
|
$busbarType->phases_config = null;
|
||||||
|
}
|
||||||
$busbarType->num_lines = GETPOSTINT('num_lines');
|
$busbarType->num_lines = GETPOSTINT('num_lines');
|
||||||
$busbarType->color = GETPOST('color', 'alphanohtml');
|
$busbarType->color = GETPOST('color', 'alphanohtml');
|
||||||
$busbarType->default_color = GETPOST('default_color', 'alphanohtml');
|
$busbarType->default_color = GETPOST('default_color', 'alphanohtml');
|
||||||
|
|
@ -267,6 +281,20 @@ if ($action == 'create' || $action == 'edit') {
|
||||||
print '<tr><td>'.$langs->trans('NumLines').'</td>';
|
print '<tr><td>'.$langs->trans('NumLines').'</td>';
|
||||||
print '<td><input type="number" name="num_lines" id="numlines-input" class="flat" value="'.($busbarType->num_lines ?: 1).'" min="1" max="10"></td></tr>';
|
print '<td><input type="number" name="num_lines" id="numlines-input" class="flat" value="'.($busbarType->num_lines ?: 1).'" min="1" max="10"></td></tr>';
|
||||||
|
|
||||||
|
// Phase labels per line (phases_config)
|
||||||
|
$phasesConfigVal = '';
|
||||||
|
if (!empty($busbarType->phases_config)) {
|
||||||
|
$arr = json_decode($busbarType->phases_config, true);
|
||||||
|
if (is_array($arr)) {
|
||||||
|
$phasesConfigVal = implode(',', $arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '<tr><td>'.$langs->trans('PhaseLabels').'</td>';
|
||||||
|
print '<td>';
|
||||||
|
print '<input type="text" name="phases_config" id="phases-config-input" class="flat minwidth200" value="'.dol_escape_htmltag($phasesConfigVal).'" placeholder="L1,L2,L3">';
|
||||||
|
print '<div class="opacitymedium small">Kommagetrennte Bezeichnungen pro Linie, wiederholen sich (z.B. L1,L2,L3 oder L1,N)</div>';
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
print '<tr><td>'.$langs->trans('Colors').'</td>';
|
print '<tr><td>'.$langs->trans('Colors').'</td>';
|
||||||
print '<td>';
|
print '<td>';
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,10 @@ if ($action == 'add_field') {
|
||||||
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
||||||
$fieldType = GETPOST('field_type', 'aZ09');
|
$fieldType = GETPOST('field_type', 'aZ09');
|
||||||
$fieldOptions = GETPOST('field_options', 'nohtml');
|
$fieldOptions = GETPOST('field_options', 'nohtml');
|
||||||
|
// Leerzeichen um Pipe-Trennzeichen entfernen und leere Optionen entfernen
|
||||||
|
if ($fieldOptions) {
|
||||||
|
$fieldOptions = implode('|', array_filter(array_map('trim', explode('|', $fieldOptions)), 'strlen'));
|
||||||
|
}
|
||||||
$showInHover = GETPOSTINT('show_in_hover');
|
$showInHover = GETPOSTINT('show_in_hover');
|
||||||
$showOnBlock = GETPOSTINT('show_on_block');
|
$showOnBlock = GETPOSTINT('show_on_block');
|
||||||
$isRequired = GETPOSTINT('is_required');
|
$isRequired = GETPOSTINT('is_required');
|
||||||
|
|
@ -256,6 +260,10 @@ if ($action == 'update_field') {
|
||||||
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
||||||
$fieldType = GETPOST('field_type', 'aZ09');
|
$fieldType = GETPOST('field_type', 'aZ09');
|
||||||
$fieldOptions = GETPOST('field_options', 'nohtml');
|
$fieldOptions = GETPOST('field_options', 'nohtml');
|
||||||
|
// Leerzeichen um Pipe-Trennzeichen entfernen und leere Optionen entfernen
|
||||||
|
if ($fieldOptions) {
|
||||||
|
$fieldOptions = implode('|', array_filter(array_map('trim', explode('|', $fieldOptions)), 'strlen'));
|
||||||
|
}
|
||||||
$showInHover = GETPOSTINT('show_in_hover');
|
$showInHover = GETPOSTINT('show_in_hover');
|
||||||
$showOnBlock = GETPOSTINT('show_on_block');
|
$showOnBlock = GETPOSTINT('show_on_block');
|
||||||
$isRequired = GETPOSTINT('is_required');
|
$isRequired = GETPOSTINT('is_required');
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,9 @@ if ($action == 'update') {
|
||||||
// View mode
|
// View mode
|
||||||
dolibarr_set_const($db, 'KUNDENKARTE_DEFAULT_VIEW', GETPOST('KUNDENKARTE_DEFAULT_VIEW', 'aZ09'), 'chaine', 0, '', $conf->entity);
|
dolibarr_set_const($db, 'KUNDENKARTE_DEFAULT_VIEW', GETPOST('KUNDENKARTE_DEFAULT_VIEW', 'aZ09'), 'chaine', 0, '', $conf->entity);
|
||||||
|
|
||||||
|
// Ausgebaute Elemente standardmäßig anzeigen
|
||||||
|
dolibarr_set_const($db, 'KUNDENKARTE_SHOW_DECOMMISSIONED', GETPOSTINT('KUNDENKARTE_SHOW_DECOMMISSIONED'), 'chaine', 0, '', $conf->entity);
|
||||||
|
|
||||||
// Tree display settings
|
// Tree display settings
|
||||||
dolibarr_set_const($db, 'KUNDENKARTE_TREE_INFO_DISPLAY', GETPOST('KUNDENKARTE_TREE_INFO_DISPLAY', 'aZ09'), 'chaine', 0, '', $conf->entity);
|
dolibarr_set_const($db, 'KUNDENKARTE_TREE_INFO_DISPLAY', GETPOST('KUNDENKARTE_TREE_INFO_DISPLAY', 'aZ09'), 'chaine', 0, '', $conf->entity);
|
||||||
dolibarr_set_const($db, 'KUNDENKARTE_TREE_BADGE_COLOR', GETPOST('KUNDENKARTE_TREE_BADGE_COLOR', 'alphanohtml'), 'chaine', 0, '', $conf->entity);
|
dolibarr_set_const($db, 'KUNDENKARTE_TREE_BADGE_COLOR', GETPOST('KUNDENKARTE_TREE_BADGE_COLOR', 'alphanohtml'), 'chaine', 0, '', $conf->entity);
|
||||||
|
|
@ -207,6 +210,14 @@ print $form->selectarray('KUNDENKARTE_DEFAULT_VIEW', $viewModes, getDolGlobalStr
|
||||||
print '</td>';
|
print '</td>';
|
||||||
print '</tr>';
|
print '</tr>';
|
||||||
|
|
||||||
|
// Ausgebaute Elemente standardmäßig anzeigen
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
print '<td>'.$langs->trans("ShowDecommissionedDefault").'</td>';
|
||||||
|
print '<td>';
|
||||||
|
print $form->selectyesno('KUNDENKARTE_SHOW_DECOMMISSIONED', getDolGlobalInt('KUNDENKARTE_SHOW_DECOMMISSIONED', 0), 1);
|
||||||
|
print '</td>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
print '</table>';
|
print '</table>';
|
||||||
|
|
||||||
// Tree Display Settings
|
// Tree Display Settings
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ function treeToArray($nodes) {
|
||||||
'fk_parent' => $node->fk_parent,
|
'fk_parent' => $node->fk_parent,
|
||||||
'fk_system' => $node->fk_system,
|
'fk_system' => $node->fk_system,
|
||||||
'type_label' => $node->type_label,
|
'type_label' => $node->type_label,
|
||||||
'status' => $node->status
|
'status' => $node->status,
|
||||||
|
'decommissioned' => (int) $node->decommissioned
|
||||||
);
|
);
|
||||||
if (!empty($node->children)) {
|
if (!empty($node->children)) {
|
||||||
$item['children'] = treeToArray($node->children);
|
$item['children'] = treeToArray($node->children);
|
||||||
|
|
@ -94,7 +95,8 @@ switch ($action) {
|
||||||
'display_label' => $prefix . $node->label,
|
'display_label' => $prefix . $node->label,
|
||||||
'fk_parent' => $node->fk_parent,
|
'fk_parent' => $node->fk_parent,
|
||||||
'type_label' => $node->type_label,
|
'type_label' => $node->type_label,
|
||||||
'status' => $node->status
|
'status' => $node->status,
|
||||||
|
'decommissioned' => (int) $node->decommissioned
|
||||||
);
|
);
|
||||||
if (!empty($node->children)) {
|
if (!empty($node->children)) {
|
||||||
$flattenTree($node->children, $prefix . ' ');
|
$flattenTree($node->children, $prefix . ' ');
|
||||||
|
|
@ -123,6 +125,7 @@ switch ($action) {
|
||||||
'type_label' => $anlage->type_label,
|
'type_label' => $anlage->type_label,
|
||||||
'fk_system' => $anlage->fk_system,
|
'fk_system' => $anlage->fk_system,
|
||||||
'status' => $anlage->status,
|
'status' => $anlage->status,
|
||||||
|
'decommissioned' => (int) $anlage->decommissioned,
|
||||||
'field_values' => $anlage->getFieldValues()
|
'field_values' => $anlage->getFieldValues()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -130,6 +133,36 @@ switch ($action) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'toggle_decommissioned':
|
||||||
|
// Ausgebaut-Status umschalten
|
||||||
|
if (!$user->hasRight('kundenkarte', 'write')) {
|
||||||
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($anlageId > 0 && $anlage->fetch($anlageId) > 0) {
|
||||||
|
$anlage->decommissioned = $anlage->decommissioned ? 0 : 1;
|
||||||
|
if ($anlage->decommissioned) {
|
||||||
|
// Ausbau: Datum setzen (aus POST oder heute)
|
||||||
|
$dateStr = GETPOST('date_decommissioned', 'alpha');
|
||||||
|
$anlage->date_decommissioned = !empty($dateStr) ? $dateStr : date('Y-m-d');
|
||||||
|
} else {
|
||||||
|
// Wieder einbauen: Datum löschen
|
||||||
|
$anlage->date_decommissioned = null;
|
||||||
|
}
|
||||||
|
$result = $anlage->update($user);
|
||||||
|
if ($result > 0) {
|
||||||
|
$response['success'] = true;
|
||||||
|
$response['decommissioned'] = (int) $anlage->decommissioned;
|
||||||
|
$response['date_decommissioned'] = $anlage->date_decommissioned;
|
||||||
|
} else {
|
||||||
|
$response['error'] = 'Fehler beim Speichern';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$response['error'] = $langs->trans('ErrorRecordNotFound');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'reorder':
|
case 'reorder':
|
||||||
// Reihenfolge der Elemente aktualisieren
|
// Reihenfolge der Elemente aktualisieren
|
||||||
if (!$user->hasRight('kundenkarte', 'write')) {
|
if (!$user->hasRight('kundenkarte', 'write')) {
|
||||||
|
|
|
||||||
154
ajax/anlage_accessory.php
Executable file
154
ajax/anlage_accessory.php
Executable file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Alles Watt lauft
|
||||||
|
*
|
||||||
|
* AJAX-Endpunkt für Anlagen-Zubehör Operationen
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
||||||
|
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
|
||||||
|
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
||||||
|
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
||||||
|
|
||||||
|
$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("Include of main fails");
|
||||||
|
|
||||||
|
dol_include_once('/kundenkarte/class/anlageaccessory.class.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
|
||||||
|
$langs->loadLangs(array('kundenkarte@kundenkarte'));
|
||||||
|
|
||||||
|
$action = GETPOST('action', 'aZ09');
|
||||||
|
|
||||||
|
$response = array('success' => false, 'error' => '');
|
||||||
|
|
||||||
|
// Berechtigungsprüfung
|
||||||
|
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||||
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
||||||
|
echo json_encode($response);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessory = new AnlageAccessory($db);
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'list':
|
||||||
|
// Alle Zubehörteile einer Anlage laden
|
||||||
|
$anlageId = GETPOSTINT('fk_anlage');
|
||||||
|
if ($anlageId > 0) {
|
||||||
|
$accessories = $accessory->fetchAllByAnlage($anlageId);
|
||||||
|
$result = array();
|
||||||
|
foreach ($accessories as $acc) {
|
||||||
|
$result[] = array(
|
||||||
|
'id' => $acc->id,
|
||||||
|
'fk_product' => $acc->fk_product,
|
||||||
|
'product_ref' => $acc->product_ref,
|
||||||
|
'product_label' => $acc->product_label,
|
||||||
|
'product_price' => $acc->product_price,
|
||||||
|
'qty' => $acc->qty,
|
||||||
|
'note' => $acc->note,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$response['success'] = true;
|
||||||
|
$response['accessories'] = $result;
|
||||||
|
} else {
|
||||||
|
$response['error'] = 'Missing fk_anlage';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'add':
|
||||||
|
// Zubehör hinzufügen
|
||||||
|
if (!$user->hasRight('kundenkarte', 'write')) {
|
||||||
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessory->fk_anlage = GETPOSTINT('fk_anlage');
|
||||||
|
$accessory->fk_product = GETPOSTINT('fk_product');
|
||||||
|
$accessory->qty = GETPOSTINT('qty') > 0 ? GETPOSTINT('qty') : 1;
|
||||||
|
$accessory->note = GETPOST('note', 'alphanohtml');
|
||||||
|
|
||||||
|
$result = $accessory->create($user);
|
||||||
|
if ($result > 0) {
|
||||||
|
$response['success'] = true;
|
||||||
|
$response['id'] = $result;
|
||||||
|
} else {
|
||||||
|
$response['error'] = $accessory->error ?: 'Fehler beim Speichern';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'update':
|
||||||
|
// Zubehör aktualisieren (Menge, Notiz)
|
||||||
|
if (!$user->hasRight('kundenkarte', 'write')) {
|
||||||
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = GETPOSTINT('id');
|
||||||
|
if ($id > 0 && $accessory->fetch($id) > 0) {
|
||||||
|
$accessory->qty = GETPOSTINT('qty') > 0 ? GETPOSTINT('qty') : $accessory->qty;
|
||||||
|
$accessory->note = GETPOST('note', 'alphanohtml');
|
||||||
|
|
||||||
|
$result = $accessory->update($user);
|
||||||
|
if ($result > 0) {
|
||||||
|
$response['success'] = true;
|
||||||
|
} else {
|
||||||
|
$response['error'] = 'Fehler beim Speichern';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$response['error'] = $langs->trans('ErrorRecordNotFound');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
// Zubehör löschen
|
||||||
|
if (!$user->hasRight('kundenkarte', 'delete')) {
|
||||||
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = GETPOSTINT('id');
|
||||||
|
if ($id > 0 && $accessory->fetch($id) > 0) {
|
||||||
|
$result = $accessory->delete($user);
|
||||||
|
if ($result > 0) {
|
||||||
|
$response['success'] = true;
|
||||||
|
} else {
|
||||||
|
$response['error'] = 'Fehler beim Löschen';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$response['error'] = $langs->trans('ErrorRecordNotFound');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'order':
|
||||||
|
// Lieferantenbestellung aus Zubehör erstellen
|
||||||
|
if (!$user->hasRight('kundenkarte', 'write')) {
|
||||||
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$anlageId = GETPOSTINT('fk_anlage');
|
||||||
|
$supplierId = GETPOSTINT('supplier_id');
|
||||||
|
$idsRaw = GETPOST('ids', 'array');
|
||||||
|
|
||||||
|
if ($anlageId > 0 && $supplierId > 0 && !empty($idsRaw)) {
|
||||||
|
$ids = array_map('intval', $idsRaw);
|
||||||
|
$result = $accessory->generateSupplierOrder($user, $supplierId, $anlageId, $ids);
|
||||||
|
if ($result > 0) {
|
||||||
|
$response['success'] = true;
|
||||||
|
$response['order_id'] = $result;
|
||||||
|
} else {
|
||||||
|
$response['error'] = $accessory->error ?: 'Fehler beim Erstellen der Bestellung';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$response['error'] = 'Fehlende Parameter (Anlage, Lieferant, IDs)';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$response['error'] = 'Unknown action';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
71
ajax/busbar_types.php
Executable file
71
ajax/busbar_types.php
Executable file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Alles Watt lauft
|
||||||
|
*
|
||||||
|
* AJAX endpoint for busbar types (Sammelschienen-Typen)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
||||||
|
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
|
||||||
|
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
||||||
|
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
||||||
|
|
||||||
|
$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("Include of main fails");
|
||||||
|
|
||||||
|
dol_include_once('/kundenkarte/class/busbartype.class.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
|
||||||
|
$langs->loadLangs(array('kundenkarte@kundenkarte'));
|
||||||
|
|
||||||
|
$action = GETPOST('action', 'aZ09');
|
||||||
|
$systemId = GETPOSTINT('system_id');
|
||||||
|
|
||||||
|
$response = array('success' => false, 'error' => '');
|
||||||
|
|
||||||
|
// Security check
|
||||||
|
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||||
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
||||||
|
echo json_encode($response);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$busbarType = new BusbarType($db);
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'list':
|
||||||
|
// Get all busbar types for a system (or all if system_id = 0)
|
||||||
|
$types = $busbarType->fetchAllBySystem($systemId, 1);
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
foreach ($types as $t) {
|
||||||
|
$result[] = array(
|
||||||
|
'id' => $t->id,
|
||||||
|
'ref' => $t->ref,
|
||||||
|
'label' => $t->label,
|
||||||
|
'label_short' => $t->label_short,
|
||||||
|
'phases' => $t->phases,
|
||||||
|
'phases_config' => $t->phases_config ? json_decode($t->phases_config, true) : null,
|
||||||
|
'num_lines' => $t->num_lines,
|
||||||
|
'color' => $t->color,
|
||||||
|
'default_color' => $t->default_color,
|
||||||
|
'line_height' => $t->line_height,
|
||||||
|
'line_spacing' => $t->line_spacing,
|
||||||
|
'position_default' => $t->position_default,
|
||||||
|
'fk_system' => $t->fk_system,
|
||||||
|
'system_label' => $t->system_label
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response['success'] = true;
|
||||||
|
$response['types'] = $result;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$response['error'] = 'Unknown action';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
|
@ -267,6 +267,7 @@ switch ($action) {
|
||||||
'block_color' => $eq->getBlockColor(),
|
'block_color' => $eq->getBlockColor(),
|
||||||
'field_values' => $eq->getFieldValues(),
|
'field_values' => $eq->getFieldValues(),
|
||||||
'fk_product' => $eq->fk_product,
|
'fk_product' => $eq->fk_product,
|
||||||
|
'fk_protection' => $eq->fk_protection,
|
||||||
'product_ref' => $productRef,
|
'product_ref' => $productRef,
|
||||||
'product_label' => $productLabel
|
'product_label' => $productLabel
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,11 @@ switch ($action) {
|
||||||
'id' => $connection->id,
|
'id' => $connection->id,
|
||||||
'fk_source' => $connection->fk_source,
|
'fk_source' => $connection->fk_source,
|
||||||
'source_terminal' => $connection->source_terminal,
|
'source_terminal' => $connection->source_terminal,
|
||||||
|
'source_terminal_id' => $connection->source_terminal_id,
|
||||||
|
'bundled_terminals' => $connection->bundled_terminals,
|
||||||
'fk_target' => $connection->fk_target,
|
'fk_target' => $connection->fk_target,
|
||||||
'target_terminal' => $connection->target_terminal,
|
'target_terminal' => $connection->target_terminal,
|
||||||
|
'target_terminal_id' => $connection->target_terminal_id,
|
||||||
'connection_type' => $connection->connection_type,
|
'connection_type' => $connection->connection_type,
|
||||||
'color' => $connection->color,
|
'color' => $connection->color,
|
||||||
'output_label' => $connection->output_label,
|
'output_label' => $connection->output_label,
|
||||||
|
|
@ -77,11 +80,14 @@ switch ($action) {
|
||||||
'id' => $c->id,
|
'id' => $c->id,
|
||||||
'fk_source' => $c->fk_source,
|
'fk_source' => $c->fk_source,
|
||||||
'source_terminal' => $c->source_terminal,
|
'source_terminal' => $c->source_terminal,
|
||||||
|
'source_terminal_id' => $c->source_terminal_id,
|
||||||
|
'bundled_terminals' => $c->bundled_terminals,
|
||||||
'source_label' => $c->source_label,
|
'source_label' => $c->source_label,
|
||||||
'source_pos' => $c->source_pos,
|
'source_pos' => $c->source_pos,
|
||||||
'source_width' => $c->source_width,
|
'source_width' => $c->source_width,
|
||||||
'fk_target' => $c->fk_target,
|
'fk_target' => $c->fk_target,
|
||||||
'target_terminal' => $c->target_terminal,
|
'target_terminal' => $c->target_terminal,
|
||||||
|
'target_terminal_id' => $c->target_terminal_id,
|
||||||
'target_label' => $c->target_label,
|
'target_label' => $c->target_label,
|
||||||
'target_pos' => $c->target_pos,
|
'target_pos' => $c->target_pos,
|
||||||
'connection_type' => $c->connection_type,
|
'connection_type' => $c->connection_type,
|
||||||
|
|
@ -96,6 +102,8 @@ switch ($action) {
|
||||||
'rail_phases' => $c->rail_phases,
|
'rail_phases' => $c->rail_phases,
|
||||||
'excluded_te' => $c->excluded_te,
|
'excluded_te' => $c->excluded_te,
|
||||||
'position_y' => $c->position_y,
|
'position_y' => $c->position_y,
|
||||||
|
'fk_busbar_type' => $c->fk_busbar_type,
|
||||||
|
'phases_config' => $c->phases_config ? json_decode($c->phases_config, true) : null,
|
||||||
'display_label' => $c->getDisplayLabel()
|
'display_label' => $c->getDisplayLabel()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +162,7 @@ switch ($action) {
|
||||||
$connection->fk_carrier = $carrierId;
|
$connection->fk_carrier = $carrierId;
|
||||||
$connection->position_y = GETPOSTINT('position_y');
|
$connection->position_y = GETPOSTINT('position_y');
|
||||||
$connection->path_data = GETPOST('path_data', 'nohtml');
|
$connection->path_data = GETPOST('path_data', 'nohtml');
|
||||||
|
$connection->bundled_terminals = GETPOST('bundled_terminals', 'alphanohtml');
|
||||||
|
|
||||||
$result = $connection->create($user);
|
$result = $connection->create($user);
|
||||||
if ($result > 0) {
|
if ($result > 0) {
|
||||||
|
|
@ -178,6 +187,7 @@ switch ($action) {
|
||||||
if (GETPOSTISSET('connection_type')) $connection->connection_type = GETPOST('connection_type', 'alphanohtml');
|
if (GETPOSTISSET('connection_type')) $connection->connection_type = GETPOST('connection_type', 'alphanohtml');
|
||||||
if (GETPOSTISSET('color')) $connection->color = GETPOST('color', 'alphanohtml');
|
if (GETPOSTISSET('color')) $connection->color = GETPOST('color', 'alphanohtml');
|
||||||
if (GETPOSTISSET('output_label')) $connection->output_label = GETPOST('output_label', 'alphanohtml');
|
if (GETPOSTISSET('output_label')) $connection->output_label = GETPOST('output_label', 'alphanohtml');
|
||||||
|
if (GETPOSTISSET('output_location')) $connection->output_location = GETPOST('output_location', 'alphanohtml');
|
||||||
if (GETPOSTISSET('medium_type')) $connection->medium_type = GETPOST('medium_type', 'alphanohtml');
|
if (GETPOSTISSET('medium_type')) $connection->medium_type = GETPOST('medium_type', 'alphanohtml');
|
||||||
if (GETPOSTISSET('medium_spec')) $connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
if (GETPOSTISSET('medium_spec')) $connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
||||||
if (GETPOSTISSET('medium_length')) $connection->medium_length = GETPOST('medium_length', 'alphanohtml');
|
if (GETPOSTISSET('medium_length')) $connection->medium_length = GETPOST('medium_length', 'alphanohtml');
|
||||||
|
|
@ -188,6 +198,8 @@ switch ($action) {
|
||||||
if (GETPOSTISSET('excluded_te')) $connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
|
if (GETPOSTISSET('excluded_te')) $connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
|
||||||
if (GETPOSTISSET('position_y')) $connection->position_y = GETPOSTINT('position_y');
|
if (GETPOSTISSET('position_y')) $connection->position_y = GETPOSTINT('position_y');
|
||||||
if (GETPOSTISSET('path_data')) $connection->path_data = GETPOST('path_data', 'nohtml');
|
if (GETPOSTISSET('path_data')) $connection->path_data = GETPOST('path_data', 'nohtml');
|
||||||
|
if (GETPOSTISSET('bundled_terminals')) $connection->bundled_terminals = GETPOST('bundled_terminals', 'alphanohtml');
|
||||||
|
if (GETPOSTISSET('fk_busbar_type')) $connection->fk_busbar_type = GETPOSTINT('fk_busbar_type') ?: null;
|
||||||
|
|
||||||
$result = $connection->update($user);
|
$result = $connection->update($user);
|
||||||
if ($result > 0) {
|
if ($result > 0) {
|
||||||
|
|
@ -231,6 +243,8 @@ switch ($action) {
|
||||||
$connection->rail_end_te = GETPOSTINT('rail_end_te');
|
$connection->rail_end_te = GETPOSTINT('rail_end_te');
|
||||||
$connection->rail_phases = GETPOST('rail_phases', 'alphanohtml');
|
$connection->rail_phases = GETPOST('rail_phases', 'alphanohtml');
|
||||||
$connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
|
$connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
|
||||||
|
$connection->num_lines = GETPOSTINT('num_lines') ?: 1;
|
||||||
|
$connection->fk_busbar_type = GETPOSTINT('fk_busbar_type') ?: null;
|
||||||
$connection->fk_carrier = $carrierId;
|
$connection->fk_carrier = $carrierId;
|
||||||
$connection->position_y = GETPOSTINT('position_y');
|
$connection->position_y = GETPOSTINT('position_y');
|
||||||
|
|
||||||
|
|
@ -240,6 +254,7 @@ switch ($action) {
|
||||||
$response['connection_id'] = $result;
|
$response['connection_id'] = $result;
|
||||||
} else {
|
} else {
|
||||||
$response['error'] = $connection->error ?: 'Create failed';
|
$response['error'] = $connection->error ?: 'Create failed';
|
||||||
|
$response['sql_errors'] = $connection->errors;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -289,6 +304,7 @@ switch ($action) {
|
||||||
$connection->connection_type = GETPOST('connection_type', 'alphanohtml');
|
$connection->connection_type = GETPOST('connection_type', 'alphanohtml');
|
||||||
$connection->color = GETPOST('color', 'alphanohtml');
|
$connection->color = GETPOST('color', 'alphanohtml');
|
||||||
$connection->output_label = GETPOST('output_label', 'alphanohtml');
|
$connection->output_label = GETPOST('output_label', 'alphanohtml');
|
||||||
|
$connection->output_location = GETPOST('output_location', 'alphanohtml');
|
||||||
$connection->medium_type = GETPOST('medium_type', 'alphanohtml');
|
$connection->medium_type = GETPOST('medium_type', 'alphanohtml');
|
||||||
$connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
$connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
||||||
$connection->medium_length = GETPOST('medium_length', 'alphanohtml');
|
$connection->medium_length = GETPOST('medium_length', 'alphanohtml');
|
||||||
|
|
@ -341,6 +357,7 @@ switch ($action) {
|
||||||
'fk_source' => $obj->fk_source,
|
'fk_source' => $obj->fk_source,
|
||||||
'source_terminal' => $obj->source_terminal,
|
'source_terminal' => $obj->source_terminal,
|
||||||
'source_terminal_id' => $obj->source_terminal_id,
|
'source_terminal_id' => $obj->source_terminal_id,
|
||||||
|
'bundled_terminals' => isset($obj->bundled_terminals) ? $obj->bundled_terminals : null,
|
||||||
'source_label' => $obj->source_label,
|
'source_label' => $obj->source_label,
|
||||||
'source_pos' => $obj->source_pos,
|
'source_pos' => $obj->source_pos,
|
||||||
'source_width' => $obj->source_width,
|
'source_width' => $obj->source_width,
|
||||||
|
|
@ -352,6 +369,7 @@ switch ($action) {
|
||||||
'connection_type' => $obj->connection_type,
|
'connection_type' => $obj->connection_type,
|
||||||
'color' => $obj->color ?: '#3498db',
|
'color' => $obj->color ?: '#3498db',
|
||||||
'output_label' => $obj->output_label,
|
'output_label' => $obj->output_label,
|
||||||
|
'output_location' => isset($obj->output_location) ? $obj->output_location : null,
|
||||||
'medium_type' => $obj->medium_type,
|
'medium_type' => $obj->medium_type,
|
||||||
'medium_spec' => $obj->medium_spec,
|
'medium_spec' => $obj->medium_spec,
|
||||||
'medium_length' => $obj->medium_length,
|
'medium_length' => $obj->medium_length,
|
||||||
|
|
@ -359,6 +377,8 @@ switch ($action) {
|
||||||
'rail_start_te' => $obj->rail_start_te,
|
'rail_start_te' => $obj->rail_start_te,
|
||||||
'rail_end_te' => $obj->rail_end_te,
|
'rail_end_te' => $obj->rail_end_te,
|
||||||
'rail_phases' => $obj->rail_phases,
|
'rail_phases' => $obj->rail_phases,
|
||||||
|
'num_lines' => isset($obj->num_lines) ? $obj->num_lines : 1,
|
||||||
|
'fk_busbar_type' => isset($obj->fk_busbar_type) ? $obj->fk_busbar_type : null,
|
||||||
'position_y' => $obj->position_y,
|
'position_y' => $obj->position_y,
|
||||||
'fk_carrier' => $obj->fk_carrier,
|
'fk_carrier' => $obj->fk_carrier,
|
||||||
'path_data' => isset($obj->path_data) ? $obj->path_data : null
|
'path_data' => isset($obj->path_data) ? $obj->path_data : null
|
||||||
|
|
|
||||||
60
ajax/export_wiring_diagram_pdf.php
Normal file
60
ajax/export_wiring_diagram_pdf.php
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Alles Watt lauft
|
||||||
|
*
|
||||||
|
* Leitungslaufplan PDF-Export (Stromlaufplan in aufgelöster Darstellung)
|
||||||
|
* Separater Endpoint - kann ohne Auswirkungen entfernt werden.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$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 && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||||
|
if (!$res) die("Include of main fails");
|
||||||
|
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||||
|
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||||
|
dol_include_once('/kundenkarte/lib/wiring_diagram.lib.php');
|
||||||
|
|
||||||
|
$langs->loadLangs(array('companies', 'kundenkarte@kundenkarte'));
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
$anlageId = GETPOSTINT('anlage_id');
|
||||||
|
$format = GETPOST('format', 'alpha') ?: 'A3';
|
||||||
|
$orientation = GETPOST('orientation', 'alpha') ?: 'L';
|
||||||
|
|
||||||
|
// Rechte-Check
|
||||||
|
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||||
|
accessforbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anlage laden
|
||||||
|
$anlage = new Anlage($db);
|
||||||
|
if ($anlage->fetch($anlageId) <= 0) {
|
||||||
|
die('Anlage nicht gefunden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kunde laden
|
||||||
|
$societe = new Societe($db);
|
||||||
|
$societe->fetch($anlage->fk_soc);
|
||||||
|
|
||||||
|
// Analyse
|
||||||
|
$analyzer = new WiringDiagramAnalyzer($db, $anlageId);
|
||||||
|
$analyzer->loadData();
|
||||||
|
$analyzer->analyze();
|
||||||
|
|
||||||
|
// PDF erstellen
|
||||||
|
$pdf = pdf_getInstance();
|
||||||
|
$pdf->SetCreator('Dolibarr - KundenKarte Leitungslaufplan');
|
||||||
|
$pdf->SetAuthor($user->getFullName($langs));
|
||||||
|
$pdf->SetTitle('Leitungslaufplan - '.$anlage->label);
|
||||||
|
|
||||||
|
// Renderer
|
||||||
|
$renderer = new WiringDiagramRenderer($pdf, $analyzer, $anlage, $societe, $user, $format, $orientation);
|
||||||
|
$renderer->render();
|
||||||
|
$renderer->renderAbgangTabelle();
|
||||||
|
$renderer->renderLegende();
|
||||||
|
|
||||||
|
// PDF ausgeben
|
||||||
|
$filename = 'Leitungslaufplan_'.dol_sanitizeFileName($anlage->label).'_'.date('Y-m-d').'.pdf';
|
||||||
|
$pdf->Output($filename, 'D');
|
||||||
|
|
@ -66,7 +66,7 @@ if ($resFields) {
|
||||||
// Elemente laden - OHNE GLOBAL-System (das ist nur die separate Gebäudestruktur)
|
// Elemente laden - OHNE GLOBAL-System (das ist nur die separate Gebäudestruktur)
|
||||||
// Gebäude/Räume werden über den Typ erkannt (type_system_code = GLOBAL)
|
// Gebäude/Räume werden über den Typ erkannt (type_system_code = GLOBAL)
|
||||||
// Hierarchie kommt aus fk_parent (wie im Baum)
|
// Hierarchie kommt aus fk_parent (wie im Baum)
|
||||||
$sql = "SELECT a.rowid, a.label, a.fk_parent, a.fk_system, a.fk_anlage_type,";
|
$sql = "SELECT a.rowid, a.label, a.fk_parent, a.fk_system, a.fk_anlage_type, a.decommissioned, a.date_decommissioned,";
|
||||||
$sql .= " a.field_values, a.fk_contact, a.graph_x, a.graph_y, a.graph_width, a.graph_height,";
|
$sql .= " a.field_values, a.fk_contact, a.graph_x, a.graph_y, a.graph_width, a.graph_height,";
|
||||||
$sql .= " t.label as type_label, t.picto as type_picto, t.color as type_color,";
|
$sql .= " t.label as type_label, t.picto as type_picto, t.color as type_color,";
|
||||||
$sql .= " t.can_have_children as type_can_have_children,";
|
$sql .= " t.can_have_children as type_can_have_children,";
|
||||||
|
|
@ -128,6 +128,8 @@ if ($resql) {
|
||||||
'fk_parent' => (int) $obj->fk_parent,
|
'fk_parent' => (int) $obj->fk_parent,
|
||||||
'fk_anlage_type' => (int) $obj->fk_anlage_type,
|
'fk_anlage_type' => (int) $obj->fk_anlage_type,
|
||||||
'is_building' => $isBuilding,
|
'is_building' => $isBuilding,
|
||||||
|
'decommissioned' => isset($obj->decommissioned) ? (int) $obj->decommissioned : 0,
|
||||||
|
'date_decommissioned' => isset($obj->date_decommissioned) ? $obj->date_decommissioned : null,
|
||||||
'image_count' => (int) $obj->image_count,
|
'image_count' => (int) $obj->image_count,
|
||||||
'doc_count' => (int) $obj->doc_count,
|
'doc_count' => (int) $obj->doc_count,
|
||||||
'graph_x' => $obj->graph_x !== null ? (float) $obj->graph_x : null,
|
'graph_x' => $obj->graph_x !== null ? (float) $obj->graph_x : null,
|
||||||
|
|
|
||||||
74
ajax/pwa_api.php
Normal file → Executable file
74
ajax/pwa_api.php
Normal file → Executable file
|
|
@ -291,7 +291,7 @@ switch ($action) {
|
||||||
$outputsData = array();
|
$outputsData = array();
|
||||||
if (!empty($equipmentData)) {
|
if (!empty($equipmentData)) {
|
||||||
$equipmentIds = array_map(function($e) { return (int) $e['id']; }, $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 = "SELECT rowid, fk_source, output_label, output_location, 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 .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
||||||
$sql .= " WHERE fk_source IN (".implode(',', $equipmentIds).")";
|
$sql .= " WHERE fk_source IN (".implode(',', $equipmentIds).")";
|
||||||
$sql .= " AND fk_target IS NULL";
|
$sql .= " AND fk_target IS NULL";
|
||||||
|
|
@ -344,6 +344,7 @@ switch ($action) {
|
||||||
'id' => $obj->rowid,
|
'id' => $obj->rowid,
|
||||||
'fk_source' => $obj->fk_source,
|
'fk_source' => $obj->fk_source,
|
||||||
'output_label' => $obj->output_label,
|
'output_label' => $obj->output_label,
|
||||||
|
'output_location' => isset($obj->output_location) ? $obj->output_location : '',
|
||||||
'medium_type' => $obj->medium_type,
|
'medium_type' => $obj->medium_type,
|
||||||
'medium_spec' => $obj->medium_spec,
|
'medium_spec' => $obj->medium_spec,
|
||||||
'medium_length' => $obj->medium_length,
|
'medium_length' => $obj->medium_length,
|
||||||
|
|
@ -360,7 +361,7 @@ switch ($action) {
|
||||||
// Einspeisungen laden (Connections mit fk_source IS NULL = Inputs)
|
// Einspeisungen laden (Connections mit fk_source IS NULL = Inputs)
|
||||||
$inputsData = array();
|
$inputsData = array();
|
||||||
if (!empty($equipmentData)) {
|
if (!empty($equipmentData)) {
|
||||||
$sql = "SELECT rowid, fk_target, output_label, connection_type, color";
|
$sql = "SELECT rowid, fk_target, target_terminal_id, output_label, connection_type, color";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
||||||
$sql .= " WHERE fk_target IN (".implode(',', $equipmentIds).")";
|
$sql .= " WHERE fk_target IN (".implode(',', $equipmentIds).")";
|
||||||
$sql .= " AND fk_source IS NULL";
|
$sql .= " AND fk_source IS NULL";
|
||||||
|
|
@ -371,6 +372,7 @@ switch ($action) {
|
||||||
$inputsData[] = array(
|
$inputsData[] = array(
|
||||||
'id' => $obj->rowid,
|
'id' => $obj->rowid,
|
||||||
'fk_target' => $obj->fk_target,
|
'fk_target' => $obj->fk_target,
|
||||||
|
'target_terminal_id' => $obj->target_terminal_id ?: '',
|
||||||
'output_label' => $obj->output_label,
|
'output_label' => $obj->output_label,
|
||||||
'connection_type' => $obj->connection_type,
|
'connection_type' => $obj->connection_type,
|
||||||
'color' => $obj->color
|
'color' => $obj->color
|
||||||
|
|
@ -379,31 +381,64 @@ switch ($action) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verbindungen zwischen Equipment laden (mit path_data für Linien-Anzeige)
|
// Verbindungen zwischen Equipment laden (alle, für Phasen-Propagierung + Linien-Anzeige)
|
||||||
$connectionsData = array();
|
$connectionsData = array();
|
||||||
if (!empty($equipmentData)) {
|
if (!empty($equipmentData)) {
|
||||||
$sql = "SELECT rowid, fk_source, fk_target, source_terminal_id, target_terminal_id, connection_type, color, path_data";
|
$sql = "SELECT rowid, fk_source, fk_target, source_terminal_id, target_terminal_id, connection_type, color, path_data, is_rail";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
||||||
$sql .= " WHERE fk_source IN (".implode(',', $equipmentIds).")";
|
$sql .= " WHERE fk_source IN (".implode(',', $equipmentIds).")";
|
||||||
$sql .= " AND fk_target IS NOT NULL";
|
$sql .= " AND fk_target IS NOT NULL";
|
||||||
$sql .= " AND fk_target IN (".implode(',', $equipmentIds).")";
|
$sql .= " AND fk_target IN (".implode(',', $equipmentIds).")";
|
||||||
|
$sql .= " AND is_rail = 0";
|
||||||
$sql .= " AND status = 1";
|
$sql .= " AND status = 1";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
if ($resql) {
|
if ($resql) {
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
// Nur Verbindungen mit gezeichnetem Pfad laden
|
$connectionsData[] = array(
|
||||||
if (!empty($obj->path_data)) {
|
'id' => $obj->rowid,
|
||||||
$connectionsData[] = array(
|
'fk_source' => $obj->fk_source,
|
||||||
'id' => $obj->rowid,
|
'fk_target' => $obj->fk_target,
|
||||||
'fk_source' => $obj->fk_source,
|
'source_terminal_id' => $obj->source_terminal_id,
|
||||||
'fk_target' => $obj->fk_target,
|
'target_terminal_id' => $obj->target_terminal_id,
|
||||||
'source_terminal_id' => $obj->source_terminal_id,
|
'connection_type' => $obj->connection_type,
|
||||||
'target_terminal_id' => $obj->target_terminal_id,
|
'color' => $obj->color,
|
||||||
'connection_type' => $obj->connection_type,
|
'path_data' => $obj->path_data ?: null
|
||||||
'color' => $obj->color,
|
);
|
||||||
'path_data' => $obj->path_data
|
}
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Busbars (Phasenschienen) laden für Farbpropagierung
|
||||||
|
$busbarsData = array();
|
||||||
|
if (!empty($carriersData)) {
|
||||||
|
$carrierIds = array_map(function($c) { return (int) $c['id']; }, $carriersData);
|
||||||
|
$sql = "SELECT c.rowid, c.fk_carrier, c.rail_start_te, c.rail_end_te, c.rail_phases,";
|
||||||
|
$sql .= " c.excluded_te, c.position_y, c.connection_type,";
|
||||||
|
$sql .= " bt.phases_config as busbar_phases_config";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection as c";
|
||||||
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_busbar_type as bt ON c.fk_busbar_type = bt.rowid";
|
||||||
|
$sql .= " WHERE c.fk_carrier IN (".implode(',', $carrierIds).")";
|
||||||
|
$sql .= " AND c.is_rail = 1";
|
||||||
|
$sql .= " AND c.status = 1";
|
||||||
|
$sql .= " ORDER BY c.position_y ASC";
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
$phasesConfig = null;
|
||||||
|
if (!empty($obj->busbar_phases_config)) {
|
||||||
|
$phasesConfig = json_decode($obj->busbar_phases_config, true);
|
||||||
}
|
}
|
||||||
|
$busbarsData[] = array(
|
||||||
|
'id' => $obj->rowid,
|
||||||
|
'fk_carrier' => $obj->fk_carrier,
|
||||||
|
'rail_start_te' => (int) $obj->rail_start_te,
|
||||||
|
'rail_end_te' => (int) $obj->rail_end_te,
|
||||||
|
'rail_phases' => $obj->rail_phases,
|
||||||
|
'excluded_te' => $obj->excluded_te ?: '',
|
||||||
|
'position_y' => (int) $obj->position_y,
|
||||||
|
'connection_type' => $obj->connection_type,
|
||||||
|
'phases_config' => $phasesConfig
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -458,6 +493,7 @@ switch ($action) {
|
||||||
$response['outputs'] = $outputsData;
|
$response['outputs'] = $outputsData;
|
||||||
$response['inputs'] = $inputsData;
|
$response['inputs'] = $inputsData;
|
||||||
$response['connections'] = $connectionsData;
|
$response['connections'] = $connectionsData;
|
||||||
|
$response['busbars'] = $busbarsData;
|
||||||
$response['types'] = $typesData;
|
$response['types'] = $typesData;
|
||||||
$response['field_meta'] = $fieldMetaData;
|
$response['field_meta'] = $fieldMetaData;
|
||||||
break;
|
break;
|
||||||
|
|
@ -481,7 +517,7 @@ switch ($action) {
|
||||||
|
|
||||||
$panel = new EquipmentPanel($db);
|
$panel = new EquipmentPanel($db);
|
||||||
$panel->fk_anlage = $anlageId;
|
$panel->fk_anlage = $anlageId;
|
||||||
$panel->label = $label ?: 'Feld';
|
$panel->label = $label; // PHP-Klasse vergibt Auto-Name wenn leer
|
||||||
|
|
||||||
$result = $panel->create($user);
|
$result = $panel->create($user);
|
||||||
if ($result > 0) {
|
if ($result > 0) {
|
||||||
|
|
@ -520,7 +556,7 @@ switch ($action) {
|
||||||
$carrier = new EquipmentCarrier($db);
|
$carrier = new EquipmentCarrier($db);
|
||||||
$carrier->fk_anlage = $panelObj->fk_anlage;
|
$carrier->fk_anlage = $panelObj->fk_anlage;
|
||||||
$carrier->fk_panel = $panelId;
|
$carrier->fk_panel = $panelId;
|
||||||
$carrier->label = $label ?: 'Hutschiene';
|
$carrier->label = $label; // PHP-Klasse vergibt Auto-Name wenn leer
|
||||||
$carrier->total_te = $totalTe;
|
$carrier->total_te = $totalTe;
|
||||||
|
|
||||||
$result = $carrier->create($user);
|
$result = $carrier->create($user);
|
||||||
|
|
@ -837,6 +873,7 @@ switch ($action) {
|
||||||
$conn->connection_type = $connectionType;
|
$conn->connection_type = $connectionType;
|
||||||
$conn->color = GETPOST('color', 'alphanohtml');
|
$conn->color = GETPOST('color', 'alphanohtml');
|
||||||
$conn->output_label = $outputLabel;
|
$conn->output_label = $outputLabel;
|
||||||
|
$conn->output_location = GETPOST('output_location', 'alphanohtml');
|
||||||
$conn->fk_carrier = $eq->fk_carrier;
|
$conn->fk_carrier = $eq->fk_carrier;
|
||||||
|
|
||||||
if ($direction === 'input') {
|
if ($direction === 'input') {
|
||||||
|
|
@ -892,6 +929,7 @@ switch ($action) {
|
||||||
$conn->connection_type = GETPOST('connection_type', 'alphanohtml');
|
$conn->connection_type = GETPOST('connection_type', 'alphanohtml');
|
||||||
$conn->color = GETPOST('color', 'alphanohtml');
|
$conn->color = GETPOST('color', 'alphanohtml');
|
||||||
$conn->output_label = GETPOST('output_label', 'alphanohtml');
|
$conn->output_label = GETPOST('output_label', 'alphanohtml');
|
||||||
|
if (GETPOSTISSET('output_location')) $conn->output_location = GETPOST('output_location', 'alphanohtml');
|
||||||
$conn->medium_type = GETPOST('medium_type', 'alphanohtml');
|
$conn->medium_type = GETPOST('medium_type', 'alphanohtml');
|
||||||
$conn->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
$conn->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
||||||
$conn->medium_length = GETPOST('medium_length', 'alphanohtml');
|
$conn->medium_length = GETPOST('medium_length', 'alphanohtml');
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class Anlage extends CommonObject
|
||||||
public $fk_parent;
|
public $fk_parent;
|
||||||
public $fk_system;
|
public $fk_system;
|
||||||
public $fk_building_node;
|
public $fk_building_node;
|
||||||
|
public $fk_product;
|
||||||
|
|
||||||
public $manufacturer;
|
public $manufacturer;
|
||||||
public $model;
|
public $model;
|
||||||
|
|
@ -40,6 +41,8 @@ class Anlage extends CommonObject
|
||||||
public $note_private;
|
public $note_private;
|
||||||
public $note_public;
|
public $note_public;
|
||||||
public $status;
|
public $status;
|
||||||
|
public $decommissioned;
|
||||||
|
public $date_decommissioned;
|
||||||
|
|
||||||
public $date_creation;
|
public $date_creation;
|
||||||
public $fk_user_creat;
|
public $fk_user_creat;
|
||||||
|
|
@ -94,7 +97,7 @@ class Anlage extends CommonObject
|
||||||
$this->db->begin();
|
$this->db->begin();
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
$sql .= "entity, ref, label, fk_soc, fk_contact, fk_anlage_type, fk_parent, fk_system,";
|
$sql .= "entity, ref, label, fk_soc, fk_contact, fk_anlage_type, fk_parent, fk_system, fk_product,";
|
||||||
$sql .= " manufacturer, model, serial_number, power_rating, field_values,";
|
$sql .= " manufacturer, model, serial_number, power_rating, field_values,";
|
||||||
$sql .= " location, installation_date, warranty_until,";
|
$sql .= " location, installation_date, warranty_until,";
|
||||||
$sql .= " rang, level, note_private, note_public, status,";
|
$sql .= " rang, level, note_private, note_public, status,";
|
||||||
|
|
@ -108,6 +111,7 @@ class Anlage extends CommonObject
|
||||||
$sql .= ", ".((int) $this->fk_anlage_type);
|
$sql .= ", ".((int) $this->fk_anlage_type);
|
||||||
$sql .= ", ".((int) ($this->fk_parent > 0 ? $this->fk_parent : 0));
|
$sql .= ", ".((int) ($this->fk_parent > 0 ? $this->fk_parent : 0));
|
||||||
$sql .= ", ".((int) $this->fk_system);
|
$sql .= ", ".((int) $this->fk_system);
|
||||||
|
$sql .= ", ".($this->fk_product > 0 ? (int) $this->fk_product : "NULL");
|
||||||
$sql .= ", ".($this->manufacturer ? "'".$this->db->escape($this->manufacturer)."'" : "NULL");
|
$sql .= ", ".($this->manufacturer ? "'".$this->db->escape($this->manufacturer)."'" : "NULL");
|
||||||
$sql .= ", ".($this->model ? "'".$this->db->escape($this->model)."'" : "NULL");
|
$sql .= ", ".($this->model ? "'".$this->db->escape($this->model)."'" : "NULL");
|
||||||
$sql .= ", ".($this->serial_number ? "'".$this->db->escape($this->serial_number)."'" : "NULL");
|
$sql .= ", ".($this->serial_number ? "'".$this->db->escape($this->serial_number)."'" : "NULL");
|
||||||
|
|
@ -201,6 +205,7 @@ class Anlage extends CommonObject
|
||||||
$this->fk_parent = $obj->fk_parent;
|
$this->fk_parent = $obj->fk_parent;
|
||||||
$this->fk_system = $obj->fk_system;
|
$this->fk_system = $obj->fk_system;
|
||||||
$this->fk_building_node = isset($obj->fk_building_node) ? (int) $obj->fk_building_node : 0;
|
$this->fk_building_node = isset($obj->fk_building_node) ? (int) $obj->fk_building_node : 0;
|
||||||
|
$this->fk_product = isset($obj->fk_product) ? (int) $obj->fk_product : null;
|
||||||
|
|
||||||
$this->manufacturer = $obj->manufacturer;
|
$this->manufacturer = $obj->manufacturer;
|
||||||
$this->model = $obj->model;
|
$this->model = $obj->model;
|
||||||
|
|
@ -217,6 +222,8 @@ class Anlage extends CommonObject
|
||||||
$this->note_private = $obj->note_private;
|
$this->note_private = $obj->note_private;
|
||||||
$this->note_public = $obj->note_public;
|
$this->note_public = $obj->note_public;
|
||||||
$this->status = $obj->status;
|
$this->status = $obj->status;
|
||||||
|
$this->decommissioned = isset($obj->decommissioned) ? (int) $obj->decommissioned : 0;
|
||||||
|
$this->date_decommissioned = isset($obj->date_decommissioned) ? $obj->date_decommissioned : null;
|
||||||
|
|
||||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||||
$this->tms = $this->db->jdate($obj->tms);
|
$this->tms = $this->db->jdate($obj->tms);
|
||||||
|
|
@ -227,8 +234,14 @@ class Anlage extends CommonObject
|
||||||
$this->type_label = $obj->type_label;
|
$this->type_label = $obj->type_label;
|
||||||
$this->type_short = $obj->type_short;
|
$this->type_short = $obj->type_short;
|
||||||
$this->type_picto = $obj->type_picto;
|
$this->type_picto = $obj->type_picto;
|
||||||
|
$this->type_color = isset($obj->type_color) ? $obj->type_color : '';
|
||||||
$this->type_can_have_children = isset($obj->type_can_have_children) ? (int) $obj->type_can_have_children : 0;
|
$this->type_can_have_children = isset($obj->type_can_have_children) ? (int) $obj->type_can_have_children : 0;
|
||||||
$this->type_can_have_equipment = isset($obj->type_can_have_equipment) ? (int) $obj->type_can_have_equipment : 0;
|
$this->type_can_have_equipment = isset($obj->type_can_have_equipment) ? (int) $obj->type_can_have_equipment : 0;
|
||||||
|
$this->type_has_accessories = isset($obj->type_has_accessories) ? (int) $obj->type_has_accessories : 0;
|
||||||
|
|
||||||
|
// Produkt-Info (aus JOIN)
|
||||||
|
$this->product_ref = isset($obj->product_ref) ? $obj->product_ref : '';
|
||||||
|
$this->product_label = isset($obj->product_label) ? $obj->product_label : '';
|
||||||
|
|
||||||
// System info
|
// System info
|
||||||
$this->system_label = $obj->system_label;
|
$this->system_label = $obj->system_label;
|
||||||
|
|
@ -287,6 +300,9 @@ class Anlage extends CommonObject
|
||||||
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||||
$sql .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
$sql .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
||||||
$sql .= ", status = ".((int) $this->status);
|
$sql .= ", status = ".((int) $this->status);
|
||||||
|
$sql .= ", fk_product = ".($this->fk_product > 0 ? (int) $this->fk_product : "NULL");
|
||||||
|
$sql .= ", decommissioned = ".((int) $this->decommissioned);
|
||||||
|
$sql .= ", date_decommissioned = ".($this->date_decommissioned ? "'".$this->db->escape($this->date_decommissioned)."'" : "NULL");
|
||||||
$sql .= ", fk_user_modif = ".((int) $user->id);
|
$sql .= ", fk_user_modif = ".((int) $user->id);
|
||||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||||
|
|
||||||
|
|
@ -401,9 +417,11 @@ class Anlage extends CommonObject
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
|
|
||||||
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,";
|
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto, t.color as type_color,";
|
||||||
$sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,";
|
$sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,";
|
||||||
|
$sql .= " t.has_accessories as type_has_accessories,";
|
||||||
$sql .= " s.label as system_label, s.code as system_code,";
|
$sql .= " s.label as system_label, s.code as system_code,";
|
||||||
|
$sql .= " p.ref as product_ref, p.label as product_label,";
|
||||||
// Count images
|
// Count images
|
||||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";
|
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";
|
||||||
// Count documents (pdf + document)
|
// Count documents (pdf + document)
|
||||||
|
|
@ -411,6 +429,7 @@ class Anlage extends CommonObject
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
||||||
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid";
|
||||||
$sql .= " WHERE a.fk_parent = ".((int) $parentId);
|
$sql .= " WHERE a.fk_parent = ".((int) $parentId);
|
||||||
$sql .= " AND a.entity = ".((int) $conf->entity);
|
$sql .= " AND a.entity = ".((int) $conf->entity);
|
||||||
$sql .= " AND a.status = 1";
|
$sql .= " AND a.status = 1";
|
||||||
|
|
@ -711,9 +730,11 @@ class Anlage extends CommonObject
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
|
|
||||||
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,";
|
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto, t.color as type_color,";
|
||||||
$sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,";
|
$sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,";
|
||||||
|
$sql .= " t.has_accessories as type_has_accessories,";
|
||||||
$sql .= " s.label as system_label, s.code as system_code,";
|
$sql .= " s.label as system_label, s.code as system_code,";
|
||||||
|
$sql .= " p.ref as product_ref, p.label as product_label,";
|
||||||
// Count images
|
// Count images
|
||||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";
|
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";
|
||||||
// Count documents (pdf + document)
|
// Count documents (pdf + document)
|
||||||
|
|
@ -721,6 +742,7 @@ class Anlage extends CommonObject
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
||||||
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid";
|
||||||
$sql .= " WHERE a.fk_parent = ".((int) $parentId);
|
$sql .= " WHERE a.fk_parent = ".((int) $parentId);
|
||||||
$sql .= " AND a.entity = ".((int) $conf->entity);
|
$sql .= " AND a.entity = ".((int) $conf->entity);
|
||||||
$sql .= " AND a.status = 1";
|
$sql .= " AND a.status = 1";
|
||||||
|
|
|
||||||
391
class/anlageaccessory.class.php
Executable file
391
class/anlageaccessory.class.php
Executable file
|
|
@ -0,0 +1,391 @@
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Alles Watt lauft
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AnlageAccessory
|
||||||
|
* Verwaltet Zubehör/Ersatzteile für Anlagen-Elemente
|
||||||
|
*/
|
||||||
|
class AnlageAccessory extends CommonObject
|
||||||
|
{
|
||||||
|
public $element = 'anlageaccessory';
|
||||||
|
public $table_element = 'kundenkarte_anlage_accessory';
|
||||||
|
|
||||||
|
public $fk_anlage;
|
||||||
|
public $fk_product;
|
||||||
|
public $qty;
|
||||||
|
public $rang;
|
||||||
|
public $note;
|
||||||
|
|
||||||
|
public $date_creation;
|
||||||
|
public $fk_user_creat;
|
||||||
|
|
||||||
|
// Geladene Objekte (aus JOIN)
|
||||||
|
public $product_ref;
|
||||||
|
public $product_label;
|
||||||
|
public $product_price;
|
||||||
|
public $product_fk_unit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param DoliDB $db Database handler
|
||||||
|
*/
|
||||||
|
public function __construct($db)
|
||||||
|
{
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zubehör erstellen
|
||||||
|
*
|
||||||
|
* @param User $user Benutzer
|
||||||
|
* @return int <0 bei Fehler, ID bei Erfolg
|
||||||
|
*/
|
||||||
|
public function create($user)
|
||||||
|
{
|
||||||
|
$error = 0;
|
||||||
|
$now = dol_now();
|
||||||
|
|
||||||
|
if (empty($this->fk_anlage) || empty($this->fk_product)) {
|
||||||
|
$this->error = 'ErrorMissingParameters';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob bereits vorhanden
|
||||||
|
if ($this->alreadyExists($this->fk_anlage, $this->fk_product)) {
|
||||||
|
$this->error = 'ErrorAccessoryAlreadyExists';
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->begin();
|
||||||
|
|
||||||
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
|
$sql .= "fk_anlage, fk_product, qty, rang, note,";
|
||||||
|
$sql .= " date_creation, fk_user_creat";
|
||||||
|
$sql .= ") VALUES (";
|
||||||
|
$sql .= ((int) $this->fk_anlage);
|
||||||
|
$sql .= ", ".((int) $this->fk_product);
|
||||||
|
$sql .= ", ".((float) ($this->qty > 0 ? $this->qty : 1));
|
||||||
|
$sql .= ", ".((int) $this->rang);
|
||||||
|
$sql .= ", ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL");
|
||||||
|
$sql .= ", '".$this->db->idate($now)."'";
|
||||||
|
$sql .= ", ".((int) $user->id);
|
||||||
|
$sql .= ")";
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if (!$resql) {
|
||||||
|
$error++;
|
||||||
|
$this->errors[] = "Error ".$this->db->lasterror();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$error) {
|
||||||
|
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$this->db->rollback();
|
||||||
|
return -1 * $error;
|
||||||
|
} else {
|
||||||
|
$this->db->commit();
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zubehör laden
|
||||||
|
*
|
||||||
|
* @param int $id ID
|
||||||
|
* @return int <0 bei Fehler, 0 nicht gefunden, >0 OK
|
||||||
|
*/
|
||||||
|
public function fetch($id)
|
||||||
|
{
|
||||||
|
$sql = "SELECT a.*, p.ref as product_ref, p.label as product_label, p.price as product_price, p.fk_unit as product_fk_unit";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
||||||
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid";
|
||||||
|
$sql .= " WHERE a.rowid = ".((int) $id);
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
if ($this->db->num_rows($resql)) {
|
||||||
|
$obj = $this->db->fetch_object($resql);
|
||||||
|
$this->id = $obj->rowid;
|
||||||
|
$this->fk_anlage = $obj->fk_anlage;
|
||||||
|
$this->fk_product = $obj->fk_product;
|
||||||
|
$this->qty = $obj->qty;
|
||||||
|
$this->rang = $obj->rang;
|
||||||
|
$this->note = $obj->note;
|
||||||
|
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||||
|
$this->fk_user_creat = $obj->fk_user_creat;
|
||||||
|
$this->product_ref = $obj->product_ref;
|
||||||
|
$this->product_label = $obj->product_label;
|
||||||
|
$this->product_price = $obj->product_price;
|
||||||
|
$this->product_fk_unit = $obj->product_fk_unit;
|
||||||
|
$this->db->free($resql);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
$this->db->free($resql);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->error = $this->db->lasterror();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zubehör aktualisieren
|
||||||
|
*
|
||||||
|
* @param User $user Benutzer
|
||||||
|
* @return int <0 bei Fehler, >0 OK
|
||||||
|
*/
|
||||||
|
public function update($user)
|
||||||
|
{
|
||||||
|
$error = 0;
|
||||||
|
|
||||||
|
$this->db->begin();
|
||||||
|
|
||||||
|
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||||
|
$sql .= " qty = ".((float) $this->qty);
|
||||||
|
$sql .= ", rang = ".((int) $this->rang);
|
||||||
|
$sql .= ", note = ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL");
|
||||||
|
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if (!$resql) {
|
||||||
|
$error++;
|
||||||
|
$this->errors[] = "Error ".$this->db->lasterror();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$this->db->rollback();
|
||||||
|
return -1 * $error;
|
||||||
|
} else {
|
||||||
|
$this->db->commit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zubehör löschen
|
||||||
|
*
|
||||||
|
* @param User $user Benutzer
|
||||||
|
* @return int <0 bei Fehler, >0 OK
|
||||||
|
*/
|
||||||
|
public function delete($user)
|
||||||
|
{
|
||||||
|
$this->db->begin();
|
||||||
|
|
||||||
|
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||||
|
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if (!$resql) {
|
||||||
|
$this->db->rollback();
|
||||||
|
$this->error = $this->db->lasterror();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Zubehörteile einer Anlage laden
|
||||||
|
*
|
||||||
|
* @param int $anlageId Anlage-ID
|
||||||
|
* @return array Array von AnlageAccessory-Objekten
|
||||||
|
*/
|
||||||
|
public function fetchAllByAnlage($anlageId)
|
||||||
|
{
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
$sql = "SELECT a.*, p.ref as product_ref, p.label as product_label, p.price as product_price, p.fk_unit as product_fk_unit";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
||||||
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid";
|
||||||
|
$sql .= " WHERE a.fk_anlage = ".((int) $anlageId);
|
||||||
|
$sql .= " ORDER BY a.rang ASC, a.rowid ASC";
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $this->db->fetch_object($resql)) {
|
||||||
|
$acc = new AnlageAccessory($this->db);
|
||||||
|
$acc->id = $obj->rowid;
|
||||||
|
$acc->fk_anlage = $obj->fk_anlage;
|
||||||
|
$acc->fk_product = $obj->fk_product;
|
||||||
|
$acc->qty = $obj->qty;
|
||||||
|
$acc->rang = $obj->rang;
|
||||||
|
$acc->note = $obj->note;
|
||||||
|
$acc->date_creation = $this->db->jdate($obj->date_creation);
|
||||||
|
$acc->fk_user_creat = $obj->fk_user_creat;
|
||||||
|
$acc->product_ref = $obj->product_ref;
|
||||||
|
$acc->product_label = $obj->product_label;
|
||||||
|
$acc->product_price = $obj->product_price;
|
||||||
|
$acc->product_fk_unit = $obj->product_fk_unit;
|
||||||
|
$results[] = $acc;
|
||||||
|
}
|
||||||
|
$this->db->free($resql);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüfen ob Produkt bereits als Zubehör zugeordnet ist
|
||||||
|
*
|
||||||
|
* @param int $anlageId Anlage-ID
|
||||||
|
* @param int $productId Produkt-ID
|
||||||
|
* @return bool true wenn bereits vorhanden
|
||||||
|
*/
|
||||||
|
public function alreadyExists($anlageId, $productId)
|
||||||
|
{
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||||
|
$sql .= " WHERE fk_anlage = ".((int) $anlageId);
|
||||||
|
$sql .= " AND fk_product = ".((int) $productId);
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
$obj = $this->db->fetch_object($resql);
|
||||||
|
return ($obj->cnt > 0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lieferantenbestellung aus ausgewählten Zubehörteilen erstellen
|
||||||
|
*
|
||||||
|
* @param User $user Benutzer
|
||||||
|
* @param int $supplierId Lieferanten-ID (fournisseur)
|
||||||
|
* @param int $anlageId Anlage-ID
|
||||||
|
* @param array $selectedIds Array von Accessory-IDs
|
||||||
|
* @param array $quantities Optional: ID => Menge
|
||||||
|
* @return int Bestell-ID bei Erfolg, <0 bei Fehler
|
||||||
|
*/
|
||||||
|
public function generateSupplierOrder($user, $supplierId, $anlageId, $selectedIds, $quantities = array())
|
||||||
|
{
|
||||||
|
global $conf, $langs, $mysoc;
|
||||||
|
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
|
||||||
|
if (empty($selectedIds)) {
|
||||||
|
$this->error = 'NoProductsSelected';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lieferant laden
|
||||||
|
$supplier = new Societe($this->db);
|
||||||
|
if ($supplier->fetch($supplierId) <= 0) {
|
||||||
|
$this->error = 'ErrorLoadingSupplier';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zubehör der Anlage laden
|
||||||
|
$accessories = $this->fetchAllByAnlage($anlageId);
|
||||||
|
if (!is_array($accessories) || empty($accessories)) {
|
||||||
|
$this->error = 'NoAccessoriesFound';
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ausgewählte filtern
|
||||||
|
$toAdd = array();
|
||||||
|
foreach ($accessories as $acc) {
|
||||||
|
if (in_array($acc->id, $selectedIds)) {
|
||||||
|
$qty = isset($quantities[$acc->id]) ? (float) $quantities[$acc->id] : $acc->qty;
|
||||||
|
if ($qty > 0) {
|
||||||
|
$toAdd[] = array(
|
||||||
|
'product_id' => $acc->fk_product,
|
||||||
|
'qty' => $qty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($toAdd)) {
|
||||||
|
$this->error = 'NoValidProductsToAdd';
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lieferantenbestellung erstellen
|
||||||
|
$order = new CommandeFournisseur($this->db);
|
||||||
|
$order->socid = $supplierId;
|
||||||
|
$order->date = dol_now();
|
||||||
|
$order->note_private = $langs->trans('OrderGeneratedFromAccessories');
|
||||||
|
|
||||||
|
$this->db->begin();
|
||||||
|
|
||||||
|
$result = $order->create($user);
|
||||||
|
if ($result <= 0) {
|
||||||
|
$this->error = $order->error;
|
||||||
|
$this->errors = $order->errors;
|
||||||
|
$this->db->rollback();
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produkte hinzufügen
|
||||||
|
foreach ($toAdd as $item) {
|
||||||
|
$product = new Product($this->db);
|
||||||
|
$product->fetch($item['product_id']);
|
||||||
|
|
||||||
|
// MwSt-Satz ermitteln (Lieferant = Verkäufer, eigene Firma = Käufer)
|
||||||
|
$tva_tx = get_default_tva($supplier, $mysoc, $product->id);
|
||||||
|
$localtax1_tx = get_default_localtax($supplier, $mysoc, 1, $product->id);
|
||||||
|
$localtax2_tx = get_default_localtax($supplier, $mysoc, 2, $product->id);
|
||||||
|
|
||||||
|
// Lieferantenpreis ermitteln
|
||||||
|
$fournPrice = $product->price;
|
||||||
|
$fournPriceId = 0;
|
||||||
|
$fournRef = '';
|
||||||
|
$sqlFourn = "SELECT rowid, price as fourn_price, ref_fourn";
|
||||||
|
$sqlFourn .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
|
||||||
|
$sqlFourn .= " WHERE fk_product = ".((int) $product->id);
|
||||||
|
$sqlFourn .= " AND fk_soc = ".((int) $supplierId);
|
||||||
|
$sqlFourn .= " ORDER BY price ASC LIMIT 1";
|
||||||
|
$resFourn = $this->db->query($sqlFourn);
|
||||||
|
if ($resFourn && $this->db->num_rows($resFourn) > 0) {
|
||||||
|
$objFourn = $this->db->fetch_object($resFourn);
|
||||||
|
$fournPrice = $objFourn->fourn_price;
|
||||||
|
$fournPriceId = $objFourn->rowid;
|
||||||
|
$fournRef = $objFourn->ref_fourn;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lineResult = $order->addline(
|
||||||
|
$product->label, // Beschreibung
|
||||||
|
$fournPrice, // Preis HT
|
||||||
|
$item['qty'], // Menge
|
||||||
|
$tva_tx, // MwSt
|
||||||
|
$localtax1_tx, // Lokale Steuer 1
|
||||||
|
$localtax2_tx, // Lokale Steuer 2
|
||||||
|
$product->id, // Produkt-ID
|
||||||
|
$fournPriceId, // Lieferantenpreis-ID
|
||||||
|
$fournRef, // Lieferanten-Referenz
|
||||||
|
0, // Rabatt
|
||||||
|
'HT', // Preis-Basis
|
||||||
|
0, // Preis TTC
|
||||||
|
0, // Typ (0=Produkt)
|
||||||
|
0, // Info bits
|
||||||
|
false, // notrigger
|
||||||
|
null, // Startdatum
|
||||||
|
null, // Enddatum
|
||||||
|
array(), // Optionen
|
||||||
|
$product->fk_unit // Einheit
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($lineResult < 0) {
|
||||||
|
$this->error = $order->error;
|
||||||
|
$this->errors = $order->errors;
|
||||||
|
$this->db->rollback();
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return $order->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ class AnlageType extends CommonObject
|
||||||
public $can_be_nested;
|
public $can_be_nested;
|
||||||
public $allowed_parent_types;
|
public $allowed_parent_types;
|
||||||
public $can_have_equipment;
|
public $can_have_equipment;
|
||||||
|
public $has_accessories;
|
||||||
|
public $has_product;
|
||||||
|
|
||||||
public $picto;
|
public $picto;
|
||||||
public $color;
|
public $color;
|
||||||
|
|
@ -73,7 +75,7 @@ class AnlageType extends CommonObject
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
$sql .= "entity, ref, label, label_short, description, fk_system,";
|
$sql .= "entity, ref, label, label_short, description, fk_system,";
|
||||||
$sql .= " can_have_children, can_be_nested, allowed_parent_types, can_have_equipment,";
|
$sql .= " can_have_children, can_be_nested, allowed_parent_types, can_have_equipment, has_accessories, has_product,";
|
||||||
$sql .= " picto, color, is_system, position, active,";
|
$sql .= " picto, color, is_system, position, active,";
|
||||||
$sql .= " date_creation, fk_user_creat";
|
$sql .= " date_creation, fk_user_creat";
|
||||||
$sql .= ") VALUES (";
|
$sql .= ") VALUES (";
|
||||||
|
|
@ -87,6 +89,8 @@ class AnlageType extends CommonObject
|
||||||
$sql .= ", ".((int) $this->can_be_nested);
|
$sql .= ", ".((int) $this->can_be_nested);
|
||||||
$sql .= ", ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL");
|
$sql .= ", ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL");
|
||||||
$sql .= ", ".((int) $this->can_have_equipment);
|
$sql .= ", ".((int) $this->can_have_equipment);
|
||||||
|
$sql .= ", ".((int) $this->has_accessories);
|
||||||
|
$sql .= ", ".((int) $this->has_product);
|
||||||
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
||||||
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||||
$sql .= ", 0"; // is_system = 0 for user-created
|
$sql .= ", 0"; // is_system = 0 for user-created
|
||||||
|
|
@ -144,6 +148,8 @@ class AnlageType extends CommonObject
|
||||||
$this->can_be_nested = $obj->can_be_nested;
|
$this->can_be_nested = $obj->can_be_nested;
|
||||||
$this->allowed_parent_types = $obj->allowed_parent_types;
|
$this->allowed_parent_types = $obj->allowed_parent_types;
|
||||||
$this->can_have_equipment = $obj->can_have_equipment ?? 0;
|
$this->can_have_equipment = $obj->can_have_equipment ?? 0;
|
||||||
|
$this->has_accessories = $obj->has_accessories ?? 0;
|
||||||
|
$this->has_product = $obj->has_product ?? 0;
|
||||||
$this->picto = $obj->picto;
|
$this->picto = $obj->picto;
|
||||||
$this->color = $obj->color;
|
$this->color = $obj->color;
|
||||||
$this->is_system = $obj->is_system;
|
$this->is_system = $obj->is_system;
|
||||||
|
|
@ -190,6 +196,8 @@ class AnlageType extends CommonObject
|
||||||
$sql .= ", can_be_nested = ".((int) $this->can_be_nested);
|
$sql .= ", can_be_nested = ".((int) $this->can_be_nested);
|
||||||
$sql .= ", allowed_parent_types = ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL");
|
$sql .= ", allowed_parent_types = ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL");
|
||||||
$sql .= ", can_have_equipment = ".((int) $this->can_have_equipment);
|
$sql .= ", can_have_equipment = ".((int) $this->can_have_equipment);
|
||||||
|
$sql .= ", has_accessories = ".((int) $this->has_accessories);
|
||||||
|
$sql .= ", has_product = ".((int) $this->has_product);
|
||||||
$sql .= ", picto = ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
$sql .= ", picto = ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
||||||
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||||
$sql .= ", position = ".((int) $this->position);
|
$sql .= ", position = ".((int) $this->position);
|
||||||
|
|
@ -312,7 +320,10 @@ class AnlageType extends CommonObject
|
||||||
$type->can_be_nested = $obj->can_be_nested;
|
$type->can_be_nested = $obj->can_be_nested;
|
||||||
$type->allowed_parent_types = $obj->allowed_parent_types;
|
$type->allowed_parent_types = $obj->allowed_parent_types;
|
||||||
$type->can_have_equipment = $obj->can_have_equipment ?? 0;
|
$type->can_have_equipment = $obj->can_have_equipment ?? 0;
|
||||||
|
$type->has_accessories = $obj->has_accessories ?? 0;
|
||||||
|
$type->has_product = $obj->has_product ?? 0;
|
||||||
$type->picto = $obj->picto;
|
$type->picto = $obj->picto;
|
||||||
|
$type->color = $obj->color;
|
||||||
$type->is_system = $obj->is_system;
|
$type->is_system = $obj->is_system;
|
||||||
$type->position = $obj->position;
|
$type->position = $obj->position;
|
||||||
$type->active = $obj->active;
|
$type->active = $obj->active;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ class BuildingType extends CommonObject
|
||||||
public $picto;
|
public $picto;
|
||||||
public $is_system;
|
public $is_system;
|
||||||
public $can_have_children;
|
public $can_have_children;
|
||||||
|
public $has_product;
|
||||||
public $position;
|
public $position;
|
||||||
public $active;
|
public $active;
|
||||||
public $date_creation;
|
public $date_creation;
|
||||||
|
|
@ -69,7 +70,7 @@ class BuildingType extends CommonObject
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
$sql .= "entity, ref, label, label_short, description, fk_parent, level_type,";
|
$sql .= "entity, ref, label, label_short, description, fk_parent, level_type,";
|
||||||
$sql .= "icon, color, picto, is_system, can_have_children, position, active,";
|
$sql .= "icon, color, picto, is_system, can_have_children, has_product, position, active,";
|
||||||
$sql .= "date_creation, fk_user_creat";
|
$sql .= "date_creation, fk_user_creat";
|
||||||
$sql .= ") VALUES (";
|
$sql .= ") VALUES (";
|
||||||
$sql .= (int)$conf->entity;
|
$sql .= (int)$conf->entity;
|
||||||
|
|
@ -84,6 +85,7 @@ class BuildingType extends CommonObject
|
||||||
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
||||||
$sql .= ", ".(int)($this->is_system ?: 0);
|
$sql .= ", ".(int)($this->is_system ?: 0);
|
||||||
$sql .= ", ".(int)($this->can_have_children !== null ? $this->can_have_children : 1);
|
$sql .= ", ".(int)($this->can_have_children !== null ? $this->can_have_children : 1);
|
||||||
|
$sql .= ", ".(int)($this->has_product ?: 0);
|
||||||
$sql .= ", ".(int)($this->position ?: 0);
|
$sql .= ", ".(int)($this->position ?: 0);
|
||||||
$sql .= ", ".(int)($this->active !== null ? $this->active : 1);
|
$sql .= ", ".(int)($this->active !== null ? $this->active : 1);
|
||||||
$sql .= ", '".$this->db->idate($now)."'";
|
$sql .= ", '".$this->db->idate($now)."'";
|
||||||
|
|
@ -135,6 +137,7 @@ class BuildingType extends CommonObject
|
||||||
$this->picto = $obj->picto;
|
$this->picto = $obj->picto;
|
||||||
$this->is_system = $obj->is_system;
|
$this->is_system = $obj->is_system;
|
||||||
$this->can_have_children = $obj->can_have_children;
|
$this->can_have_children = $obj->can_have_children;
|
||||||
|
$this->has_product = $obj->has_product ?? 0;
|
||||||
$this->position = $obj->position;
|
$this->position = $obj->position;
|
||||||
$this->active = $obj->active;
|
$this->active = $obj->active;
|
||||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||||
|
|
@ -171,6 +174,7 @@ class BuildingType extends CommonObject
|
||||||
$sql .= ", icon = ".($this->icon ? "'".$this->db->escape($this->icon)."'" : "NULL");
|
$sql .= ", icon = ".($this->icon ? "'".$this->db->escape($this->icon)."'" : "NULL");
|
||||||
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||||
$sql .= ", can_have_children = ".(int)$this->can_have_children;
|
$sql .= ", can_have_children = ".(int)$this->can_have_children;
|
||||||
|
$sql .= ", has_product = ".(int)($this->has_product ?: 0);
|
||||||
$sql .= ", position = ".(int)$this->position;
|
$sql .= ", position = ".(int)$this->position;
|
||||||
$sql .= ", active = ".(int)$this->active;
|
$sql .= ", active = ".(int)$this->active;
|
||||||
$sql .= ", fk_user_modif = ".(int)$user->id;
|
$sql .= ", fk_user_modif = ".(int)$user->id;
|
||||||
|
|
@ -265,6 +269,7 @@ class BuildingType extends CommonObject
|
||||||
$type->picto = $obj->picto;
|
$type->picto = $obj->picto;
|
||||||
$type->is_system = $obj->is_system;
|
$type->is_system = $obj->is_system;
|
||||||
$type->can_have_children = $obj->can_have_children;
|
$type->can_have_children = $obj->can_have_children;
|
||||||
|
$type->has_product = $obj->has_product ?? 0;
|
||||||
$type->position = $obj->position;
|
$type->position = $obj->position;
|
||||||
$type->active = $obj->active;
|
$type->active = $obj->active;
|
||||||
$type->parent_label = $obj->parent_label;
|
$type->parent_label = $obj->parent_label;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class BusbarType extends CommonObject
|
||||||
|
|
||||||
// Busbar-spezifische Felder
|
// Busbar-spezifische Felder
|
||||||
public $phases; // Channel configuration (A, B, AB, ABC, or legacy L1, L2, L3, N, PE, etc.)
|
public $phases; // Channel configuration (A, B, AB, ABC, or legacy L1, L2, L3, N, PE, etc.)
|
||||||
|
public $phases_config; // JSON array of phase labels per line, e.g. ["L1","L2","L3"]
|
||||||
public $num_lines = 1; // Anzahl der Linien
|
public $num_lines = 1; // Anzahl der Linien
|
||||||
public $color; // Kommagetrennte Farben
|
public $color; // Kommagetrennte Farben
|
||||||
public $default_color; // Standard-Einzelfarbe
|
public $default_color; // Standard-Einzelfarbe
|
||||||
|
|
@ -80,7 +81,7 @@ class BusbarType extends CommonObject
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
$sql .= "entity, ref, label, label_short, description, fk_system,";
|
$sql .= "entity, ref, label, label_short, description, fk_system,";
|
||||||
$sql .= " phases, num_lines, color, default_color, line_height, line_spacing, position_default,";
|
$sql .= " phases, phases_config, num_lines, color, default_color, line_height, line_spacing, position_default,";
|
||||||
$sql .= " fk_product, picto, icon_file, is_system, position, active,";
|
$sql .= " fk_product, picto, icon_file, is_system, position, active,";
|
||||||
$sql .= " date_creation, fk_user_creat";
|
$sql .= " date_creation, fk_user_creat";
|
||||||
$sql .= ") VALUES (";
|
$sql .= ") VALUES (";
|
||||||
|
|
@ -91,6 +92,7 @@ class BusbarType extends CommonObject
|
||||||
$sql .= ", ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
|
$sql .= ", ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
|
||||||
$sql .= ", ".((int) $this->fk_system);
|
$sql .= ", ".((int) $this->fk_system);
|
||||||
$sql .= ", '".$this->db->escape($this->phases)."'";
|
$sql .= ", '".$this->db->escape($this->phases)."'";
|
||||||
|
$sql .= ", ".($this->phases_config ? "'".$this->db->escape($this->phases_config)."'" : "NULL");
|
||||||
$sql .= ", ".((int) ($this->num_lines > 0 ? $this->num_lines : 1));
|
$sql .= ", ".((int) ($this->num_lines > 0 ? $this->num_lines : 1));
|
||||||
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||||
$sql .= ", ".($this->default_color ? "'".$this->db->escape($this->default_color)."'" : "NULL");
|
$sql .= ", ".($this->default_color ? "'".$this->db->escape($this->default_color)."'" : "NULL");
|
||||||
|
|
@ -206,6 +208,7 @@ class BusbarType extends CommonObject
|
||||||
$sql .= ", description = ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
|
$sql .= ", description = ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
|
||||||
$sql .= ", fk_system = ".((int) $this->fk_system);
|
$sql .= ", fk_system = ".((int) $this->fk_system);
|
||||||
$sql .= ", phases = '".$this->db->escape($this->phases)."'";
|
$sql .= ", phases = '".$this->db->escape($this->phases)."'";
|
||||||
|
$sql .= ", phases_config = ".($this->phases_config ? "'".$this->db->escape($this->phases_config)."'" : "NULL");
|
||||||
$sql .= ", num_lines = ".((int) ($this->num_lines > 0 ? $this->num_lines : 1));
|
$sql .= ", num_lines = ".((int) ($this->num_lines > 0 ? $this->num_lines : 1));
|
||||||
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||||
$sql .= ", default_color = ".($this->default_color ? "'".$this->db->escape($this->default_color)."'" : "NULL");
|
$sql .= ", default_color = ".($this->default_color ? "'".$this->db->escape($this->default_color)."'" : "NULL");
|
||||||
|
|
@ -316,6 +319,7 @@ class BusbarType extends CommonObject
|
||||||
$type->label_short = $obj->label_short;
|
$type->label_short = $obj->label_short;
|
||||||
$type->fk_system = $obj->fk_system;
|
$type->fk_system = $obj->fk_system;
|
||||||
$type->phases = $obj->phases;
|
$type->phases = $obj->phases;
|
||||||
|
$type->phases_config = $obj->phases_config;
|
||||||
$type->num_lines = $obj->num_lines;
|
$type->num_lines = $obj->num_lines;
|
||||||
$type->color = $obj->color;
|
$type->color = $obj->color;
|
||||||
$type->default_color = $obj->default_color;
|
$type->default_color = $obj->default_color;
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class EquipmentCarrier extends CommonObject
|
||||||
$error = 0;
|
$error = 0;
|
||||||
$now = dol_now();
|
$now = dol_now();
|
||||||
|
|
||||||
if (empty($this->fk_anlage) || empty($this->label)) {
|
if (empty($this->fk_anlage)) {
|
||||||
$this->error = 'ErrorMissingParameters';
|
$this->error = 'ErrorMissingParameters';
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +72,15 @@ class EquipmentCarrier extends CommonObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-naming wenn kein Label angegeben
|
||||||
|
if (empty($this->label)) {
|
||||||
|
$sqlCnt = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||||
|
$sqlCnt .= " WHERE fk_panel = ".((int) $this->fk_panel);
|
||||||
|
$resCnt = $this->db->query($sqlCnt);
|
||||||
|
$cnt = ($resCnt && ($objCnt = $this->db->fetch_object($resCnt))) ? (int) $objCnt->cnt : 0;
|
||||||
|
$this->label = 'R'.($cnt + 1);
|
||||||
|
}
|
||||||
|
|
||||||
$this->db->begin();
|
$this->db->begin();
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
|
|
@ -166,6 +175,15 @@ class EquipmentCarrier extends CommonObject
|
||||||
{
|
{
|
||||||
$error = 0;
|
$error = 0;
|
||||||
|
|
||||||
|
// Auto-naming wenn kein Label angegeben
|
||||||
|
if (empty($this->label)) {
|
||||||
|
$sqlCnt = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||||
|
$sqlCnt .= " WHERE fk_panel = ".((int) $this->fk_panel);
|
||||||
|
$resCnt = $this->db->query($sqlCnt);
|
||||||
|
$cnt = ($resCnt && ($objCnt = $this->db->fetch_object($resCnt))) ? (int) $objCnt->cnt : 1;
|
||||||
|
$this->label = 'R'.$cnt;
|
||||||
|
}
|
||||||
|
|
||||||
$this->db->begin();
|
$this->db->begin();
|
||||||
|
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class EquipmentConnection extends CommonObject
|
||||||
|
|
||||||
// Output/endpoint info
|
// Output/endpoint info
|
||||||
public $output_label;
|
public $output_label;
|
||||||
|
public $output_location; // Räumlichkeit/Örtlichkeit des Verbrauchers
|
||||||
|
|
||||||
// Medium info (cable, wire, etc.)
|
// Medium info (cable, wire, etc.)
|
||||||
public $medium_type;
|
public $medium_type;
|
||||||
|
|
@ -42,6 +43,9 @@ class EquipmentConnection extends CommonObject
|
||||||
public $rail_end_te;
|
public $rail_end_te;
|
||||||
public $rail_phases; // '3P', '3P+N', 'L1', 'L1N', etc.
|
public $rail_phases; // '3P', '3P+N', 'L1', 'L1N', etc.
|
||||||
public $excluded_te; // Comma-separated TE positions to exclude (gaps for FI)
|
public $excluded_te; // Comma-separated TE positions to exclude (gaps for FI)
|
||||||
|
public $num_lines = 1; // Number of lines for busbar (1-5)
|
||||||
|
public $fk_busbar_type; // Reference to busbar type template
|
||||||
|
public $phases_config; // JSON array of phase labels from busbar type
|
||||||
|
|
||||||
public $fk_carrier;
|
public $fk_carrier;
|
||||||
public $position_y = 0;
|
public $position_y = 0;
|
||||||
|
|
@ -88,9 +92,9 @@ class EquipmentConnection extends CommonObject
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
$sql .= "entity, fk_source, source_terminal, source_terminal_id, bundled_terminals, 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 .= " connection_type, color, output_label, output_location,";
|
||||||
$sql .= " medium_type, medium_spec, medium_length,";
|
$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,";
|
$sql .= " is_rail, rail_start_te, rail_end_te, rail_phases, excluded_te, num_lines, fk_busbar_type, fk_carrier, position_y, path_data,";
|
||||||
$sql .= " note_private, status, date_creation, fk_user_creat";
|
$sql .= " note_private, status, date_creation, fk_user_creat";
|
||||||
$sql .= ") VALUES (";
|
$sql .= ") VALUES (";
|
||||||
$sql .= ((int) $conf->entity);
|
$sql .= ((int) $conf->entity);
|
||||||
|
|
@ -104,6 +108,7 @@ class EquipmentConnection extends CommonObject
|
||||||
$sql .= ", ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "NULL");
|
$sql .= ", ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "NULL");
|
||||||
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||||
$sql .= ", ".($this->output_label ? "'".$this->db->escape($this->output_label)."'" : "NULL");
|
$sql .= ", ".($this->output_label ? "'".$this->db->escape($this->output_label)."'" : "NULL");
|
||||||
|
$sql .= ", ".($this->output_location ? "'".$this->db->escape($this->output_location)."'" : "NULL");
|
||||||
$sql .= ", ".($this->medium_type ? "'".$this->db->escape($this->medium_type)."'" : "NULL");
|
$sql .= ", ".($this->medium_type ? "'".$this->db->escape($this->medium_type)."'" : "NULL");
|
||||||
$sql .= ", ".($this->medium_spec ? "'".$this->db->escape($this->medium_spec)."'" : "NULL");
|
$sql .= ", ".($this->medium_spec ? "'".$this->db->escape($this->medium_spec)."'" : "NULL");
|
||||||
$sql .= ", ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
$sql .= ", ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
||||||
|
|
@ -112,6 +117,8 @@ class EquipmentConnection extends CommonObject
|
||||||
$sql .= ", ".($this->rail_end_te > 0 ? ((int) $this->rail_end_te) : "NULL");
|
$sql .= ", ".($this->rail_end_te > 0 ? ((int) $this->rail_end_te) : "NULL");
|
||||||
$sql .= ", ".($this->rail_phases ? "'".$this->db->escape($this->rail_phases)."'" : "NULL");
|
$sql .= ", ".($this->rail_phases ? "'".$this->db->escape($this->rail_phases)."'" : "NULL");
|
||||||
$sql .= ", ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
|
$sql .= ", ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
|
||||||
|
$sql .= ", ".((int) ($this->num_lines > 0 ? $this->num_lines : 1));
|
||||||
|
$sql .= ", ".($this->fk_busbar_type > 0 ? ((int) $this->fk_busbar_type) : "NULL");
|
||||||
$sql .= ", ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
|
$sql .= ", ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
|
||||||
$sql .= ", ".((int) $this->position_y);
|
$sql .= ", ".((int) $this->position_y);
|
||||||
$sql .= ", ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
|
$sql .= ", ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
|
||||||
|
|
@ -173,6 +180,7 @@ class EquipmentConnection extends CommonObject
|
||||||
$this->connection_type = $obj->connection_type;
|
$this->connection_type = $obj->connection_type;
|
||||||
$this->color = $obj->color;
|
$this->color = $obj->color;
|
||||||
$this->output_label = $obj->output_label;
|
$this->output_label = $obj->output_label;
|
||||||
|
$this->output_location = isset($obj->output_location) ? $obj->output_location : null;
|
||||||
$this->medium_type = $obj->medium_type;
|
$this->medium_type = $obj->medium_type;
|
||||||
$this->medium_spec = $obj->medium_spec;
|
$this->medium_spec = $obj->medium_spec;
|
||||||
$this->medium_length = $obj->medium_length;
|
$this->medium_length = $obj->medium_length;
|
||||||
|
|
@ -229,6 +237,7 @@ class EquipmentConnection extends CommonObject
|
||||||
$sql .= ", connection_type = ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "NULL");
|
$sql .= ", connection_type = ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "NULL");
|
||||||
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||||
$sql .= ", output_label = ".($this->output_label ? "'".$this->db->escape($this->output_label)."'" : "NULL");
|
$sql .= ", output_label = ".($this->output_label ? "'".$this->db->escape($this->output_label)."'" : "NULL");
|
||||||
|
$sql .= ", output_location = ".($this->output_location ? "'".$this->db->escape($this->output_location)."'" : "NULL");
|
||||||
$sql .= ", medium_type = ".($this->medium_type ? "'".$this->db->escape($this->medium_type)."'" : "NULL");
|
$sql .= ", medium_type = ".($this->medium_type ? "'".$this->db->escape($this->medium_type)."'" : "NULL");
|
||||||
$sql .= ", medium_spec = ".($this->medium_spec ? "'".$this->db->escape($this->medium_spec)."'" : "NULL");
|
$sql .= ", medium_spec = ".($this->medium_spec ? "'".$this->db->escape($this->medium_spec)."'" : "NULL");
|
||||||
$sql .= ", medium_length = ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
$sql .= ", medium_length = ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
||||||
|
|
@ -237,6 +246,7 @@ class EquipmentConnection extends CommonObject
|
||||||
$sql .= ", rail_end_te = ".($this->rail_end_te > 0 ? ((int) $this->rail_end_te) : "NULL");
|
$sql .= ", rail_end_te = ".($this->rail_end_te > 0 ? ((int) $this->rail_end_te) : "NULL");
|
||||||
$sql .= ", rail_phases = ".($this->rail_phases ? "'".$this->db->escape($this->rail_phases)."'" : "NULL");
|
$sql .= ", rail_phases = ".($this->rail_phases ? "'".$this->db->escape($this->rail_phases)."'" : "NULL");
|
||||||
$sql .= ", excluded_te = ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
|
$sql .= ", excluded_te = ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
|
||||||
|
$sql .= ", fk_busbar_type = ".($this->fk_busbar_type > 0 ? ((int) $this->fk_busbar_type) : "NULL");
|
||||||
$sql .= ", fk_carrier = ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
|
$sql .= ", fk_carrier = ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
|
||||||
$sql .= ", position_y = ".((int) $this->position_y);
|
$sql .= ", position_y = ".((int) $this->position_y);
|
||||||
$sql .= ", path_data = ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
|
$sql .= ", path_data = ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
|
||||||
|
|
@ -300,10 +310,12 @@ class EquipmentConnection extends CommonObject
|
||||||
|
|
||||||
$sql = "SELECT c.*, ";
|
$sql = "SELECT c.*, ";
|
||||||
$sql .= " src.label as source_label, src.position_te as source_pos, src.width_te as source_width,";
|
$sql .= " src.label as source_label, src.position_te as source_pos, src.width_te as source_width,";
|
||||||
$sql .= " tgt.label as target_label, tgt.position_te as target_pos";
|
$sql .= " tgt.label as target_label, tgt.position_te as target_pos,";
|
||||||
|
$sql .= " bt.phases_config as busbar_phases_config";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as src ON c.fk_source = src.rowid";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as src ON c.fk_source = src.rowid";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as tgt ON c.fk_target = tgt.rowid";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as tgt ON c.fk_target = tgt.rowid";
|
||||||
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_busbar_type as bt ON c.fk_busbar_type = bt.rowid";
|
||||||
$sql .= " WHERE c.fk_carrier = ".((int) $carrierId);
|
$sql .= " WHERE c.fk_carrier = ".((int) $carrierId);
|
||||||
if ($activeOnly) {
|
if ($activeOnly) {
|
||||||
$sql .= " AND c.status = 1";
|
$sql .= " AND c.status = 1";
|
||||||
|
|
@ -344,6 +356,8 @@ class EquipmentConnection extends CommonObject
|
||||||
$conn->source_width = $obj->source_width;
|
$conn->source_width = $obj->source_width;
|
||||||
$conn->target_label = $obj->target_label;
|
$conn->target_label = $obj->target_label;
|
||||||
$conn->target_pos = $obj->target_pos;
|
$conn->target_pos = $obj->target_pos;
|
||||||
|
$conn->fk_busbar_type = isset($obj->fk_busbar_type) ? $obj->fk_busbar_type : null;
|
||||||
|
$conn->phases_config = isset($obj->busbar_phases_config) ? $obj->busbar_phases_config : null;
|
||||||
|
|
||||||
$results[] = $conn;
|
$results[] = $conn;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,15 @@ class EquipmentPanel extends CommonObject
|
||||||
{
|
{
|
||||||
$error = 0;
|
$error = 0;
|
||||||
|
|
||||||
|
// Auto-naming wenn kein Label angegeben
|
||||||
|
if (empty($this->label)) {
|
||||||
|
$sqlCnt = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||||
|
$sqlCnt .= " WHERE fk_anlage = ".((int) $this->fk_anlage);
|
||||||
|
$resCnt = $this->db->query($sqlCnt);
|
||||||
|
$cnt = ($resCnt && ($objCnt = $this->db->fetch_object($resCnt))) ? (int) $objCnt->cnt : 1;
|
||||||
|
$this->label = 'Feld '.$cnt;
|
||||||
|
}
|
||||||
|
|
||||||
$this->db->begin();
|
$this->db->begin();
|
||||||
|
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,7 @@ class EquipmentType extends CommonObject
|
||||||
$type->picto = $obj->picto;
|
$type->picto = $obj->picto;
|
||||||
$type->icon_file = $obj->icon_file;
|
$type->icon_file = $obj->icon_file;
|
||||||
$type->block_image = $obj->block_image;
|
$type->block_image = $obj->block_image;
|
||||||
|
$type->terminals_config = $obj->terminals_config;
|
||||||
$type->is_system = $obj->is_system;
|
$type->is_system = $obj->is_system;
|
||||||
$type->position = $obj->position;
|
$type->position = $obj->position;
|
||||||
$type->active = $obj->active;
|
$type->active = $obj->active;
|
||||||
|
|
|
||||||
|
|
@ -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'
|
$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'
|
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||||
$this->version = '8.3';
|
$this->version = '11.1.2';
|
||||||
// Url to the file with your last numberversion of this module
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||||
|
|
||||||
|
|
@ -412,6 +412,23 @@ class modKundenKarte extends DolibarrModules
|
||||||
'target' => '',
|
'target' => '',
|
||||||
'user' => 0,
|
'user' => 0,
|
||||||
);
|
);
|
||||||
|
// Werkzeuge-Seite unter Start-Menü
|
||||||
|
$this->menu[$r++] = array(
|
||||||
|
'fk_menu' => 'fk_mainmenu=home',
|
||||||
|
'type' => 'left',
|
||||||
|
'titre' => 'CompanyTools',
|
||||||
|
'prefix' => img_picto('', 'fa-wrench', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||||
|
'mainmenu' => 'home',
|
||||||
|
'leftmenu' => 'kundenkarte_werkzeuge',
|
||||||
|
'url' => '/kundenkarte/werkzeuge.php',
|
||||||
|
'langs' => 'kundenkarte@kundenkarte',
|
||||||
|
'position' => 100,
|
||||||
|
'enabled' => 'isModEnabled("kundenkarte")',
|
||||||
|
'perms' => '$user->hasRight("kundenkarte", "read")',
|
||||||
|
'target' => '',
|
||||||
|
'user' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
/* END MODULEBUILDER LEFTMENU */
|
/* END MODULEBUILDER LEFTMENU */
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -633,6 +650,21 @@ class modKundenKarte extends DolibarrModules
|
||||||
|
|
||||||
// v6.8.0: Schutzgruppen-Zuordnung (fk_protection)
|
// v6.8.0: Schutzgruppen-Zuordnung (fk_protection)
|
||||||
$this->migrate_v680_protection_groups();
|
$this->migrate_v680_protection_groups();
|
||||||
|
|
||||||
|
// v8.0.0: Ausgebaut-Status für Anlagen
|
||||||
|
$this->migrate_v800_decommissioned();
|
||||||
|
|
||||||
|
// v8.1.0: Werkzeuge & Zubehör
|
||||||
|
$this->migrate_v810_werkzeuge();
|
||||||
|
|
||||||
|
// v8.6.0: has_product Flag für Typen
|
||||||
|
$this->migrate_v860_has_product();
|
||||||
|
|
||||||
|
// v11.0.0: Busbar type reference and num_lines for connections
|
||||||
|
$this->migrate_v1100_busbar_fields();
|
||||||
|
|
||||||
|
// v11.1.0: Räumlichkeit (output_location) für Abgänge
|
||||||
|
$this->migrate_v1110_output_location();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -927,6 +959,161 @@ class modKundenKarte extends DolibarrModules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration v8.0.0: Ausgebaut-Status für Anlagen
|
||||||
|
*/
|
||||||
|
private function migrate_v800_decommissioned()
|
||||||
|
{
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_anlage";
|
||||||
|
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'decommissioned'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN decommissioned tinyint DEFAULT 0 NOT NULL AFTER status");
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD INDEX idx_anlage_decommissioned (decommissioned)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ausbau-Datum
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'date_decommissioned'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN date_decommissioned DATE NULL AFTER decommissioned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration v8.1.0: Werkzeuge & Zubehör
|
||||||
|
* - fk_product auf Anlage (Produkt-Zuordnung)
|
||||||
|
* - has_accessories auf Anlage-Typ
|
||||||
|
* - Zubehör-Tabelle
|
||||||
|
* - WERKZEUG System-Kategorie
|
||||||
|
*/
|
||||||
|
private function migrate_v810_werkzeuge()
|
||||||
|
{
|
||||||
|
// 1. fk_product auf Anlage
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_anlage";
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'fk_product'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN fk_product integer NULL AFTER fk_building_node");
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD INDEX idx_anlage_fk_product (fk_product)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. has_accessories auf Anlage-Typ
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_anlage_type";
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'has_accessories'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN has_accessories tinyint DEFAULT 0 NOT NULL AFTER can_have_equipment");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Zubehör-Tabelle
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_anlage_accessory";
|
||||||
|
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$sql = "CREATE TABLE ".$table." (";
|
||||||
|
$sql .= " rowid integer AUTO_INCREMENT PRIMARY KEY,";
|
||||||
|
$sql .= " fk_anlage integer NOT NULL,";
|
||||||
|
$sql .= " fk_product integer NOT NULL,";
|
||||||
|
$sql .= " qty double DEFAULT 1,";
|
||||||
|
$sql .= " rang integer DEFAULT 0,";
|
||||||
|
$sql .= " note varchar(255),";
|
||||||
|
$sql .= " date_creation datetime,";
|
||||||
|
$sql .= " fk_user_creat integer,";
|
||||||
|
$sql .= " UNIQUE KEY uk_anlage_accessory (fk_anlage, fk_product),";
|
||||||
|
$sql .= " INDEX idx_accessory_anlage (fk_anlage),";
|
||||||
|
$sql .= " CONSTRAINT fk_accessory_anlage FOREIGN KEY (fk_anlage) REFERENCES ".MAIN_DB_PREFIX."kundenkarte_anlage(rowid) ON DELETE CASCADE";
|
||||||
|
$sql .= ") ENGINE=InnoDB";
|
||||||
|
$this->db->query($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. WERKZEUG System-Kategorie (falls nicht vorhanden)
|
||||||
|
$sysTable = MAIN_DB_PREFIX."c_kundenkarte_anlage_system";
|
||||||
|
$resql = $this->db->query("SELECT rowid FROM ".$sysTable." WHERE code = 'WERKZEUG'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("INSERT INTO ".$sysTable." (code, label, active, position) VALUES ('WERKZEUG', 'Werkzeuge & Maschinen', 1, 90)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration v8.6.0: has_product Flag für Element-Typen und Gebäude-Typen
|
||||||
|
*/
|
||||||
|
private function migrate_v860_has_product()
|
||||||
|
{
|
||||||
|
// 1. has_product auf Element-Typ
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_anlage_type";
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'has_product'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN has_product tinyint DEFAULT 0 NOT NULL AFTER has_accessories");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. has_product auf Gebäude-Typ
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_building_type";
|
||||||
|
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||||
|
if ($resql && $this->db->num_rows($resql) > 0) {
|
||||||
|
$resql2 = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'has_product'");
|
||||||
|
if (!$resql2 || $this->db->num_rows($resql2) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN has_product tinyint DEFAULT 0 NOT NULL AFTER can_have_children");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration v11.0.0: Add busbar type fields to connections
|
||||||
|
*/
|
||||||
|
private function migrate_v1100_busbar_fields()
|
||||||
|
{
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
||||||
|
|
||||||
|
// Check if table exists
|
||||||
|
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add num_lines column
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'num_lines'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN num_lines int(11) DEFAULT 1 AFTER excluded_te");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add fk_busbar_type column
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'fk_busbar_type'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN fk_busbar_type int(11) DEFAULT NULL AFTER num_lines");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend color column to support comma-separated colors for multi-line busbars
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." WHERE Field = 'color' AND Type LIKE 'varchar(20)%'");
|
||||||
|
if ($resql && $this->db->num_rows($resql) > 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." MODIFY COLUMN color varchar(255)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add phases_config column to busbar_type table
|
||||||
|
$busbarTable = MAIN_DB_PREFIX."kundenkarte_busbar_type";
|
||||||
|
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($busbarTable)."'");
|
||||||
|
if ($resql && $this->db->num_rows($resql) > 0) {
|
||||||
|
$resql2 = $this->db->query("SHOW COLUMNS FROM ".$busbarTable." LIKE 'phases_config'");
|
||||||
|
if (!$resql2 || $this->db->num_rows($resql2) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$busbarTable." ADD COLUMN phases_config TEXT DEFAULT NULL AFTER phases");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration v11.1.0: Räumlichkeit (output_location) für Abgänge
|
||||||
|
*/
|
||||||
|
private function migrate_v1110_output_location()
|
||||||
|
{
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_equipment_connection";
|
||||||
|
|
||||||
|
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'output_location'");
|
||||||
|
if (!$resql || $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN output_location varchar(255) DEFAULT NULL AFTER output_label");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called when module is disabled.
|
* Function called when module is disabled.
|
||||||
* Remove from database constants, boxes and permissions from Dolibarr database.
|
* Remove from database constants, boxes and permissions from Dolibarr database.
|
||||||
|
|
|
||||||
|
|
@ -2119,6 +2119,13 @@ body.kundenkarte-drag-active * {
|
||||||
margin-top: 20px !important;
|
margin-top: 20px !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
overflow-x: auto !important;
|
overflow-x: auto !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent gap between header and message */
|
||||||
|
.schematic-editor-wrapper > * {
|
||||||
|
flex-shrink: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent Schematic Editor from breaking Dolibarr layout */
|
/* Prevent Schematic Editor from breaking Dolibarr layout */
|
||||||
|
|
@ -2135,6 +2142,37 @@ body.kundenkarte-drag-active * {
|
||||||
background: #252525 !important;
|
background: #252525 !important;
|
||||||
border: 1px solid #333 !important;
|
border: 1px solid #333 !important;
|
||||||
border-radius: 4px 4px 0 0 !important;
|
border-radius: 4px 4px 0 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
min-height: 50px !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schematic-editor-actions {
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
gap: 5px !important;
|
||||||
|
align-items: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Einheitliche Größe für alle Toolbar-Buttons (button + a) */
|
||||||
|
.schematic-editor-actions > button,
|
||||||
|
.schematic-editor-actions > a {
|
||||||
|
padding: 5px 8px !important;
|
||||||
|
background: #333 !important;
|
||||||
|
border: 1px solid #555 !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
height: 30px !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 4px !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
font-family: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schematic-editor-toggle {
|
.schematic-editor-toggle {
|
||||||
|
|
@ -2163,6 +2201,7 @@ body.kundenkarte-drag-active * {
|
||||||
min-height: 300px !important;
|
min-height: 300px !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schematic-editor-canvas.expanded {
|
.schematic-editor-canvas.expanded {
|
||||||
|
|
@ -2214,13 +2253,19 @@ body.kundenkarte-drag-active * {
|
||||||
transition: filter 0.2s ease !important;
|
transition: filter 0.2s ease !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Terminals */
|
/* Terminals - hitarea captures all events */
|
||||||
.schematic-terminal {
|
.schematic-terminal {
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schematic-terminal-hitarea {
|
||||||
cursor: crosshair !important;
|
cursor: crosshair !important;
|
||||||
|
pointer-events: all !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schematic-terminal-circle {
|
.schematic-terminal-circle {
|
||||||
transition: all 0.2s ease !important;
|
transition: all 0.2s ease !important;
|
||||||
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schematic-terminal:hover .schematic-terminal-circle {
|
.schematic-terminal:hover .schematic-terminal-circle {
|
||||||
|
|
@ -2260,7 +2305,7 @@ body.kundenkarte-drag-active * {
|
||||||
/* Messages - Fixed height status bar */
|
/* Messages - Fixed height status bar */
|
||||||
.schematic-message {
|
.schematic-message {
|
||||||
padding: 6px 15px !important;
|
padding: 6px 15px !important;
|
||||||
margin-bottom: 0 !important;
|
margin: 0 !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
height: 28px !important;
|
height: 28px !important;
|
||||||
|
|
@ -2275,6 +2320,7 @@ body.kundenkarte-drag-active * {
|
||||||
border: 1px solid #3498db !important;
|
border: 1px solid #3498db !important;
|
||||||
border-top: none !important;
|
border-top: none !important;
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schematic-message.info {
|
.schematic-message.info {
|
||||||
|
|
@ -2846,3 +2892,43 @@ body.kundenkarte-drag-active * {
|
||||||
.edit-product-clear:hover {
|
.edit-product-clear:hover {
|
||||||
color: #e74c3c !important;
|
color: #e74c3c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
AUSGEBAUTE ELEMENTE (Decommissioned)
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
/* Ausgebaute Elemente im Baum standardmäßig ausgeblendet */
|
||||||
|
/* Funktion 1: kundenkarte-tree-node, Funktion 2: kundenkarte-tree-row */
|
||||||
|
.kundenkarte-tree .decommissioned {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sichtbar wenn Toggle aktiv */
|
||||||
|
.kundenkarte-tree.show-decommissioned .decommissioned {
|
||||||
|
display: flex !important;
|
||||||
|
opacity: 0.4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ausgegraut: Label durchgestrichen */
|
||||||
|
.kundenkarte-tree.show-decommissioned .decommissioned .tree-label,
|
||||||
|
.kundenkarte-tree.show-decommissioned .decommissioned .tree-item-label {
|
||||||
|
text-decoration: line-through !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge "Ausgebaut" mit Datum */
|
||||||
|
.badge-decommissioned {
|
||||||
|
display: inline-block !important;
|
||||||
|
margin-left: 8px !important;
|
||||||
|
padding: 1px 6px !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
background: #8b4513 !important;
|
||||||
|
color: #ddd !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle-Button aktiver Zustand */
|
||||||
|
#btn-toggle-decommissioned.active {
|
||||||
|
background: rgba(139, 69, 19, 0.3) !important;
|
||||||
|
border-color: #8b4513 !important;
|
||||||
|
}
|
||||||
|
|
|
||||||
40
css/pwa.css
Normal file → Executable file
40
css/pwa.css
Normal file → Executable file
|
|
@ -905,12 +905,14 @@ body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 3px 2px;
|
padding: 3px 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Zeile 2 (obere Terminals): direkt am Equipment (margin-bottom negativ) */
|
/* Zeile 2 (obere Terminals): direkt am Equipment (margin-bottom negativ) */
|
||||||
|
|
@ -950,6 +952,8 @@ body {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
align-self: center;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-arrow-down {
|
.terminal-arrow-down {
|
||||||
|
|
@ -977,7 +981,7 @@ body {
|
||||||
.terminal-label-cell {
|
.terminal-label-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 20px;
|
min-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Obere Labels (Zeile 1): am unteren Rand ausrichten (zum Terminal hin) */
|
/* Obere Labels (Zeile 1): am unteren Rand ausrichten (zum Terminal hin) */
|
||||||
|
|
@ -1002,7 +1006,7 @@ body {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
max-height: 80px;
|
max-height: 130px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
@ -1029,6 +1033,15 @@ body {
|
||||||
background: rgba(173, 140, 79, 0.4);
|
background: rgba(173, 140, 79, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.terminal-label .output-location {
|
||||||
|
display: block;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 8px;
|
||||||
|
color: rgba(255,255,255,0.5);
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.terminal-label .cable-info {
|
.terminal-label .cable-info {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 7px;
|
font-size: 7px;
|
||||||
|
|
@ -1144,6 +1157,21 @@ body {
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wire Toggle Button */
|
||||||
|
#btn-toggle-wires {
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.2s, background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-toggle-wires.active {
|
||||||
|
opacity: 1;
|
||||||
|
background: rgba(173, 140, 79, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-toggle-wires svg {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
/* Grid-Rows: 1=Labels oben, 2=Terminals oben, 3=Equipment, 4=Terminals unten, 5=Labels unten */
|
/* Grid-Rows: 1=Labels oben, 2=Terminals oben, 3=Equipment, 4=Terminals unten, 5=Labels unten */
|
||||||
/* Add Button in Carrier (letzte Spalte, Zeile 2) */
|
/* Add Button in Carrier (letzte Spalte, Zeile 2) */
|
||||||
.btn-add-equipment {
|
.btn-add-equipment {
|
||||||
|
|
@ -2067,3 +2095,9 @@ body {
|
||||||
border: 2px dashed rgba(255, 255, 255, 0.25);
|
border: 2px dashed rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Leere TE-Positionen ohne Terminal (z.B. NEO 4TE aber nur 3 Terminals) */
|
||||||
|
.terminal-point.no-terminal {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
0
img/pwa-icon-192.png
Normal file → Executable file
0
img/pwa-icon-192.png
Normal file → Executable file
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
0
img/pwa-icon-512.png
Normal file → Executable file
0
img/pwa-icon-512.png
Normal file → Executable file
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
4612
js/kundenkarte.js
4612
js/kundenkarte.js
File diff suppressed because it is too large
Load diff
|
|
@ -338,6 +338,11 @@
|
||||||
n.data.display_label = lines.join('\n');
|
n.data.display_label = lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ausgebaut-Markierung
|
||||||
|
if (n.data.decommissioned) {
|
||||||
|
n.classes = (n.classes || '') + ' decommissioned';
|
||||||
|
}
|
||||||
|
|
||||||
cyElements.push(n);
|
cyElements.push(n);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -539,6 +544,14 @@
|
||||||
'opacity': 0.6
|
'opacity': 0.6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Ausgebaute Elemente - ausgegraut
|
||||||
|
{
|
||||||
|
selector: '.decommissioned',
|
||||||
|
style: {
|
||||||
|
'opacity': 0.35,
|
||||||
|
'border-style': 'dashed'
|
||||||
|
}
|
||||||
|
},
|
||||||
// Hover
|
// Hover
|
||||||
{
|
{
|
||||||
selector: 'node:active',
|
selector: 'node:active',
|
||||||
|
|
@ -606,6 +619,35 @@
|
||||||
case 'add-child':
|
case 'add-child':
|
||||||
window.location.href = baseUrl + '&action=create&parent_id=' + anlageId;
|
window.location.href = baseUrl + '&action=create&parent_id=' + anlageId;
|
||||||
break;
|
break;
|
||||||
|
case 'toggle-decommissioned':
|
||||||
|
var nodeEl = self.cy.$('#n_' + anlageId);
|
||||||
|
var isDecomm = nodeEl.length && nodeEl.data('decommissioned');
|
||||||
|
if (isDecomm) {
|
||||||
|
// Wieder einbauen - Bestätigung
|
||||||
|
window.KundenKarte.showConfirm('Wieder einbauen', 'Element wieder als eingebaut markieren?', function() {
|
||||||
|
$.post(self.moduleUrl + '/ajax/anlage.php', {
|
||||||
|
action: 'toggle_decommissioned',
|
||||||
|
anlage_id: anlageId,
|
||||||
|
token: $('input[name="token"]').val() || ''
|
||||||
|
}, function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
nodeEl.data('decommissioned', 0);
|
||||||
|
nodeEl.removeClass('decommissioned');
|
||||||
|
} else {
|
||||||
|
alert(res.error || 'Fehler');
|
||||||
|
}
|
||||||
|
}, 'json');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Ausbauen - Dialog mit Datum
|
||||||
|
window.KundenKarte.showDecommissionDialog(anlageId, function(res) {
|
||||||
|
if (nodeEl.length) {
|
||||||
|
nodeEl.data('decommissioned', 1);
|
||||||
|
nodeEl.addClass('decommissioned');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
self.hideContextMenu();
|
self.hideContextMenu();
|
||||||
});
|
});
|
||||||
|
|
@ -620,6 +662,15 @@
|
||||||
if (!this.contextMenuEl) return;
|
if (!this.contextMenuEl) return;
|
||||||
this.contextMenuNodeId = node.data('id').replace('n_', '');
|
this.contextMenuNodeId = node.data('id').replace('n_', '');
|
||||||
|
|
||||||
|
// Ausgebaut-Label im Kontextmenü anpassen
|
||||||
|
var isDecomm = node.data('decommissioned');
|
||||||
|
var $decommLabel = $(this.contextMenuEl).find('.ctx-decommission-label');
|
||||||
|
var $decommIcon = $(this.contextMenuEl).find('.ctx-decommission i');
|
||||||
|
if ($decommLabel.length) {
|
||||||
|
$decommLabel.text(isDecomm ? 'Wieder einbauen' : 'Ausbauen');
|
||||||
|
$decommIcon.attr('class', isDecomm ? 'fa fa-plug' : 'fa fa-power-off');
|
||||||
|
}
|
||||||
|
|
||||||
var container = document.getElementById(this.containerId);
|
var container = document.getElementById(this.containerId);
|
||||||
var rect = container.getBoundingClientRect();
|
var rect = container.getBoundingClientRect();
|
||||||
var x = rect.left + renderedPos.x;
|
var x = rect.left + renderedPos.x;
|
||||||
|
|
|
||||||
574
js/pwa.js
Normal file → Executable file
574
js/pwa.js
Normal file → Executable file
|
|
@ -36,6 +36,9 @@
|
||||||
offlineQueue: [],
|
offlineQueue: [],
|
||||||
isOnline: navigator.onLine,
|
isOnline: navigator.onLine,
|
||||||
|
|
||||||
|
// Display settings
|
||||||
|
showConnectionLines: false, // Leitungen standardmäßig ausgeblendet
|
||||||
|
|
||||||
// Current modal state
|
// Current modal state
|
||||||
currentCarrierId: null,
|
currentCarrierId: null,
|
||||||
editCarrierId: null, // null = Add-Modus, ID = Edit-Modus (Hutschiene)
|
editCarrierId: null, // null = Add-Modus, ID = Edit-Modus (Hutschiene)
|
||||||
|
|
@ -56,10 +59,14 @@
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
// Register Service Worker
|
// Register Service Worker + Update erzwingen
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('sw.js')
|
navigator.serviceWorker.register('sw.js', { updateViaCache: 'none' })
|
||||||
.then(reg => console.log('[PWA] Service Worker registered'))
|
.then(reg => {
|
||||||
|
console.log('[PWA] Service Worker registered');
|
||||||
|
// Sofort nach Updates suchen
|
||||||
|
reg.update();
|
||||||
|
})
|
||||||
.catch(err => console.error('[PWA] SW registration failed:', err));
|
.catch(err => console.error('[PWA] SW registration failed:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,6 +193,7 @@
|
||||||
// Editor actions
|
// Editor actions
|
||||||
$('#btn-add-panel').on('click', () => openModal('add-panel'));
|
$('#btn-add-panel').on('click', () => openModal('add-panel'));
|
||||||
$('#btn-save-panel').on('click', handleSavePanel);
|
$('#btn-save-panel').on('click', handleSavePanel);
|
||||||
|
$('#btn-toggle-wires').on('click', handleToggleWires);
|
||||||
|
|
||||||
$('#editor-content').on('click', '.btn-add-carrier', handleAddCarrier);
|
$('#editor-content').on('click', '.btn-add-carrier', handleAddCarrier);
|
||||||
$('#editor-content').on('click', '.carrier-header', handleCarrierClick);
|
$('#editor-content').on('click', '.carrier-header', handleCarrierClick);
|
||||||
|
|
@ -222,6 +230,17 @@
|
||||||
// Medium-Type Change -> Spezifikationen laden
|
// Medium-Type Change -> Spezifikationen laden
|
||||||
$('#conn-medium-type').on('change', handleMediumTypeChange);
|
$('#conn-medium-type').on('change', handleMediumTypeChange);
|
||||||
|
|
||||||
|
// Connection-Type Change -> Auto-Farbe bei Input-Phasen
|
||||||
|
$('#conn-type').on('change', function() {
|
||||||
|
if (App.connectionDirection === 'input') {
|
||||||
|
const phase = $(this).val();
|
||||||
|
if (phase) {
|
||||||
|
const color = getPhaseColor(phase);
|
||||||
|
$('#conn-color').val(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Bestätigungsdialog
|
// Bestätigungsdialog
|
||||||
$('#btn-confirm-ok').on('click', function() {
|
$('#btn-confirm-ok').on('click', function() {
|
||||||
closeModal('confirm');
|
closeModal('confirm');
|
||||||
|
|
@ -734,6 +753,7 @@
|
||||||
App.outputs = response.outputs || [];
|
App.outputs = response.outputs || [];
|
||||||
App.inputs = response.inputs || [];
|
App.inputs = response.inputs || [];
|
||||||
App.connections = response.connections || [];
|
App.connections = response.connections || [];
|
||||||
|
App.busbars = response.busbars || [];
|
||||||
App.fieldMeta = response.field_meta || {};
|
App.fieldMeta = response.field_meta || {};
|
||||||
|
|
||||||
// Cache for offline
|
// Cache for offline
|
||||||
|
|
@ -745,6 +765,7 @@
|
||||||
outputs: App.outputs,
|
outputs: App.outputs,
|
||||||
inputs: App.inputs,
|
inputs: App.inputs,
|
||||||
connections: App.connections,
|
connections: App.connections,
|
||||||
|
busbars: App.busbars,
|
||||||
fieldMeta: App.fieldMeta
|
fieldMeta: App.fieldMeta
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -765,6 +786,7 @@
|
||||||
App.outputs = data.outputs || [];
|
App.outputs = data.outputs || [];
|
||||||
App.inputs = data.inputs || [];
|
App.inputs = data.inputs || [];
|
||||||
App.connections = data.connections || [];
|
App.connections = data.connections || [];
|
||||||
|
App.busbars = data.busbars || [];
|
||||||
App.fieldMeta = data.fieldMeta || {};
|
App.fieldMeta = data.fieldMeta || {};
|
||||||
renderEditor();
|
renderEditor();
|
||||||
showToast('Offline - Zeige gecachte Daten', 'warning');
|
showToast('Offline - Zeige gecachte Daten', 'warning');
|
||||||
|
|
@ -812,6 +834,9 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Terminal-Farbpropagierung aufbauen (Phasenfarben an allen Terminals)
|
||||||
|
buildTerminalPhaseMap();
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
App.panels.forEach(panel => {
|
App.panels.forEach(panel => {
|
||||||
|
|
@ -864,21 +889,26 @@
|
||||||
html += `<span class="terminal-label-cell label-row-top bundled-label" style="${gridColStyle}" data-connection-id="${bundledTop.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
html += `<span class="terminal-label-cell label-row-top bundled-label" style="${gridColStyle}" data-connection-id="${bundledTop.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
||||||
if (bundledTop.output_label) {
|
if (bundledTop.output_label) {
|
||||||
html += `<span class="terminal-label">${escapeHtml(bundledTop.output_label)}`;
|
html += `<span class="terminal-label">${escapeHtml(bundledTop.output_label)}`;
|
||||||
|
if (bundledTop.output_location) html += `<span class="output-location">${escapeHtml(bundledTop.output_location)}</span>`;
|
||||||
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
}
|
}
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
} else {
|
} else {
|
||||||
// Normale einzelne Labels pro Terminal - nur für tatsächliche Terminals
|
// Normale einzelne Labels pro Terminal - per Terminal-ID matchen
|
||||||
|
const eqTerminals = getTerminals(eq);
|
||||||
|
const topTerms = eqTerminals.filter(tm => tm.pos === 'top');
|
||||||
for (let t = 0; t < topTerminalCount; t++) {
|
for (let t = 0; t < topTerminalCount; t++) {
|
||||||
const colPos = posTe > 0 ? posTe + t : 0;
|
const colPos = posTe > 0 ? posTe + t : 0;
|
||||||
const style = `grid-row:1;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
const style = `grid-row:1;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
||||||
const topOut = eqTopOutputs[t] || null;
|
const termId = topTerms[t] ? topTerms[t].id : ('t' + (t + 1));
|
||||||
|
const topOut = eqTopOutputs.find(o => o.source_terminal_id === termId) || eqTopOutputs[t] || null;
|
||||||
|
|
||||||
if (topOut && topOut.output_label && (!topOut.bundled_terminals || widthTe <= 1)) {
|
if (topOut && topOut.output_label && (!topOut.bundled_terminals || widthTe <= 1)) {
|
||||||
const cableInfo = buildCableInfo(topOut);
|
const cableInfo = buildCableInfo(topOut);
|
||||||
html += `<span class="terminal-label-cell label-row-top" style="${style}" data-connection-id="${topOut.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
html += `<span class="terminal-label-cell label-row-top" style="${style}" data-connection-id="${topOut.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
||||||
html += `<span class="terminal-label">${escapeHtml(topOut.output_label)}`;
|
html += `<span class="terminal-label">${escapeHtml(topOut.output_label)}`;
|
||||||
|
if (topOut.output_location) html += `<span class="output-location">${escapeHtml(topOut.output_location)}</span>`;
|
||||||
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
|
|
@ -899,38 +929,43 @@
|
||||||
carrierEquipment.forEach(eq => {
|
carrierEquipment.forEach(eq => {
|
||||||
const widthTe = parseFloat(eq.width_te) || 1;
|
const widthTe = parseFloat(eq.width_te) || 1;
|
||||||
const posTe = parseFloat(eq.position_te) || 0;
|
const posTe = parseFloat(eq.position_te) || 0;
|
||||||
const eqInputs = App.inputs ? App.inputs.filter(i => i.fk_target == eq.id && i.target_terminal_id === 't1') : [];
|
|
||||||
const eqTopOutputs = App.outputs ? App.outputs.filter(o => o.fk_source == eq.id && o.is_top) : [];
|
|
||||||
|
|
||||||
// Terminal-Anzahl aus terminals_config ermitteln
|
|
||||||
const type = App.equipmentTypes.find(t => t.id == eq.fk_equipment_type);
|
const type = App.equipmentTypes.find(t => t.id == eq.fk_equipment_type);
|
||||||
const topTerminalCount = getTerminalCount(type, 'top', widthTe);
|
const topTerminalCount = getTerminalCount(type, 'top', widthTe);
|
||||||
|
|
||||||
|
// Terminal-IDs für diese Position ermitteln
|
||||||
|
const eqTerminals = getTerminals(eq);
|
||||||
|
const topTerms = eqTerminals.filter(tm => tm.pos === 'top');
|
||||||
|
const topTermIds = topTerms.map(tm => tm.id);
|
||||||
|
|
||||||
|
// Inputs und Outputs per Terminal-ID matchen (nicht per Index!)
|
||||||
|
const eqTopInputs = App.inputs ? App.inputs.filter(i => i.fk_target == eq.id && topTermIds.indexOf(i.target_terminal_id) !== -1) : [];
|
||||||
|
const eqTopOutputs = App.outputs ? App.outputs.filter(o => o.fk_source == eq.id && o.is_top) : [];
|
||||||
|
|
||||||
// Gebündelter Abgang?
|
// Gebündelter Abgang?
|
||||||
const bundledTop = eqTopOutputs.find(o => o.bundled_terminals === 'all');
|
const bundledTop = eqTopOutputs.find(o => o.bundled_terminals === 'all');
|
||||||
|
|
||||||
// Nur so viele Terminal-Punkte wie tatsächlich konfiguriert
|
|
||||||
for (let t = 0; t < topTerminalCount; t++) {
|
for (let t = 0; t < topTerminalCount; t++) {
|
||||||
const colPos = posTe > 0 ? posTe + t : 0;
|
const colPos = posTe > 0 ? posTe + t : 0;
|
||||||
const style = `grid-row:2;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
const style = `grid-row:2;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
||||||
const inp = eqInputs[t] || null;
|
const termId = topTerms[t] ? topTerms[t].id : ('t' + (t + 1));
|
||||||
const topOut = bundledTop || eqTopOutputs[t] || null;
|
|
||||||
|
// Input/Output per Terminal-ID finden
|
||||||
|
const inp = eqTopInputs.find(i => i.target_terminal_id === termId) || null;
|
||||||
|
const topOut = bundledTop || eqTopOutputs.find(o => o.source_terminal_id === termId) || eqTopOutputs[t] || null;
|
||||||
|
|
||||||
if (bundledTop && widthTe > 1) {
|
if (bundledTop && widthTe > 1) {
|
||||||
// Gebündelter Abgang: Pfeil nur beim ersten Terminal, Rest leer
|
|
||||||
if (t === 0) {
|
if (t === 0) {
|
||||||
const phaseColor = bundledTop.color || getPhaseColor(bundledTop.connection_type);
|
const phaseColor = bundledTop.color || getPhaseColor(bundledTop.connection_type);
|
||||||
const bundledStyle = posTe > 0
|
const bundledStyle = posTe > 0
|
||||||
? `grid-row:2; grid-column: ${posTe} / span ${topTerminalCount}`
|
? `grid-row:2; grid-column: ${posTe} / span ${widthTe}`
|
||||||
: `grid-row:2; grid-column: span ${topTerminalCount}`;
|
: `grid-row:2; grid-column: span ${widthTe}`;
|
||||||
html += `<span class="terminal-point terminal-output terminal-row-top bundled-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="top" data-connection-id="${bundledTop.id}" style="${bundledStyle}">`;
|
html += `<span class="terminal-point terminal-output terminal-row-top bundled-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="top" data-connection-id="${bundledTop.id}" style="${bundledStyle}">`;
|
||||||
html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`;
|
html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`;
|
||||||
html += `<span class="terminal-phase">${escapeHtml(bundledTop.connection_type || '')}</span>`;
|
html += `<span class="terminal-phase">${escapeHtml(bundledTop.connection_type || '')}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
}
|
}
|
||||||
// Restliche Terminals überspringen (grid-column: span hat sie schon)
|
} else if (topOut && topOut.output_label && (!topOut.bundled_terminals || widthTe <= 1)) {
|
||||||
} else if (topOut && (!topOut.bundled_terminals || widthTe <= 1)) {
|
// Output MIT Label → Pfeil (echter Abgang)
|
||||||
// Normaler Top-Output ODER bundled bei 1 TE (Bundle macht bei 1 TE keinen Unterschied)
|
|
||||||
const phaseColor = topOut.color || getPhaseColor(topOut.connection_type);
|
const phaseColor = topOut.color || getPhaseColor(topOut.connection_type);
|
||||||
html += `<span class="terminal-point terminal-output terminal-row-top" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="top" data-connection-id="${topOut.id}" style="${style}">`;
|
html += `<span class="terminal-point terminal-output terminal-row-top" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="top" data-connection-id="${topOut.id}" style="${style}">`;
|
||||||
html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`;
|
html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`;
|
||||||
|
|
@ -943,13 +978,22 @@
|
||||||
html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`;
|
html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
} else {
|
} else {
|
||||||
// Leerer Terminal - neutral, Position "top"
|
// Phasenfarbe aus Propagierung
|
||||||
html += `<span class="terminal-point terminal-empty terminal-row-top" data-equipment-id="${eq.id}" data-terminal-position="top" data-connection-id="" style="${style}">`;
|
const propColor = (App.terminalColorMap[eq.id] || {})[termId];
|
||||||
html += `<span class="terminal-dot terminal-dot-empty"></span>`;
|
const propPhase = (App.terminalPhaseMap[eq.id] || {})[termId];
|
||||||
html += `</span>`;
|
if (propColor) {
|
||||||
|
html += `<span class="terminal-point terminal-propagated terminal-row-top" data-equipment-id="${eq.id}" data-terminal-position="top" data-connection-id="" style="${style}">`;
|
||||||
|
html += `<span class="terminal-dot" style="background:${propColor}"></span>`;
|
||||||
|
html += `<span class="terminal-phase">${escapeHtml(propPhase || '')}</span>`;
|
||||||
|
html += `</span>`;
|
||||||
|
} else {
|
||||||
|
html += `<span class="terminal-point terminal-empty terminal-row-top" data-equipment-id="${eq.id}" data-terminal-position="top" data-connection-id="" style="${style}">`;
|
||||||
|
html += `<span class="terminal-dot terminal-dot-empty"></span>`;
|
||||||
|
html += `</span>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Leere Zellen für restliche TE-Breite (ohne Terminal-Punkte)
|
// Leere Zellen für restliche TE-Breite
|
||||||
for (let t = topTerminalCount; t < widthTe; t++) {
|
for (let t = topTerminalCount; t < widthTe; t++) {
|
||||||
const colPos = posTe > 0 ? posTe + t : 0;
|
const colPos = posTe > 0 ? posTe + t : 0;
|
||||||
const style = `grid-row:2;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
const style = `grid-row:2;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
||||||
|
|
@ -1018,38 +1062,43 @@
|
||||||
carrierEquipment.forEach(eq => {
|
carrierEquipment.forEach(eq => {
|
||||||
const widthTe = parseFloat(eq.width_te) || 1;
|
const widthTe = parseFloat(eq.width_te) || 1;
|
||||||
const posTe = parseFloat(eq.position_te) || 0;
|
const posTe = parseFloat(eq.position_te) || 0;
|
||||||
const eqBottomOutputs = App.outputs ? App.outputs.filter(o => o.fk_source == eq.id && !o.is_top) : [];
|
|
||||||
const eqBottomInputs = App.inputs ? App.inputs.filter(i => i.fk_target == eq.id && i.target_terminal_id === 't2') : [];
|
|
||||||
|
|
||||||
// Terminal-Anzahl aus terminals_config ermitteln
|
|
||||||
const type = App.equipmentTypes.find(t => t.id == eq.fk_equipment_type);
|
const type = App.equipmentTypes.find(t => t.id == eq.fk_equipment_type);
|
||||||
const bottomTerminalCount = getTerminalCount(type, 'bottom', widthTe);
|
const bottomTerminalCount = getTerminalCount(type, 'bottom', widthTe);
|
||||||
|
|
||||||
|
// Terminal-IDs für Bottom ermitteln
|
||||||
|
const eqTerminals = getTerminals(eq);
|
||||||
|
const botTerms = eqTerminals.filter(tm => tm.pos === 'bottom');
|
||||||
|
const botTermIds = botTerms.map(tm => tm.id);
|
||||||
|
|
||||||
|
// Inputs und Outputs per Terminal-ID matchen
|
||||||
|
const eqBottomInputs = App.inputs ? App.inputs.filter(i => i.fk_target == eq.id && botTermIds.indexOf(i.target_terminal_id) !== -1) : [];
|
||||||
|
const eqBottomOutputs = App.outputs ? App.outputs.filter(o => o.fk_source == eq.id && !o.is_top) : [];
|
||||||
|
|
||||||
// Gebündelter Abgang?
|
// Gebündelter Abgang?
|
||||||
const bundledBottom = eqBottomOutputs.find(o => o.bundled_terminals === 'all');
|
const bundledBottom = eqBottomOutputs.find(o => o.bundled_terminals === 'all');
|
||||||
|
|
||||||
// Nur so viele Terminal-Punkte wie tatsächlich konfiguriert
|
|
||||||
for (let t = 0; t < bottomTerminalCount; t++) {
|
for (let t = 0; t < bottomTerminalCount; t++) {
|
||||||
const colPos = posTe > 0 ? posTe + t : 0;
|
const colPos = posTe > 0 ? posTe + t : 0;
|
||||||
const style = `grid-row:4;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
const style = `grid-row:4;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
||||||
const out = bundledBottom || eqBottomOutputs[t] || null;
|
const termId = botTerms[t] ? botTerms[t].id : ('t' + (widthTe + t + 1));
|
||||||
const inp = eqBottomInputs[t] || null;
|
|
||||||
|
// Input/Output per Terminal-ID finden
|
||||||
|
const out = bundledBottom || eqBottomOutputs.find(o => o.source_terminal_id === termId) || eqBottomOutputs[t] || null;
|
||||||
|
const inp = eqBottomInputs.find(i => i.target_terminal_id === termId) || null;
|
||||||
|
|
||||||
if (bundledBottom && widthTe > 1) {
|
if (bundledBottom && widthTe > 1) {
|
||||||
// Gebündelter Abgang: Pfeil nur beim ersten Terminal, Rest leer
|
|
||||||
if (t === 0) {
|
if (t === 0) {
|
||||||
const phaseColor = bundledBottom.color || getPhaseColor(bundledBottom.connection_type);
|
const phaseColor = bundledBottom.color || getPhaseColor(bundledBottom.connection_type);
|
||||||
const bundledStyle = posTe > 0
|
const bundledStyle = posTe > 0
|
||||||
? `grid-row:4; grid-column: ${posTe} / span ${bottomTerminalCount}`
|
? `grid-row:4; grid-column: ${posTe} / span ${widthTe}`
|
||||||
: `grid-row:4; grid-column: span ${bottomTerminalCount}`;
|
: `grid-row:4; grid-column: span ${widthTe}`;
|
||||||
html += `<span class="terminal-point terminal-output terminal-row-bottom bundled-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="bottom" data-connection-id="${bundledBottom.id}" style="${bundledStyle}">`;
|
html += `<span class="terminal-point terminal-output terminal-row-bottom bundled-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="bottom" data-connection-id="${bundledBottom.id}" style="${bundledStyle}">`;
|
||||||
html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`;
|
html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`;
|
||||||
html += `<span class="terminal-phase">${escapeHtml(bundledBottom.connection_type || '')}</span>`;
|
html += `<span class="terminal-phase">${escapeHtml(bundledBottom.connection_type || '')}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
}
|
}
|
||||||
// Restliche Terminals überspringen (grid-column: span hat sie schon)
|
} else if (out && out.output_label && (!out.bundled_terminals || widthTe <= 1)) {
|
||||||
} else if (out && (!out.bundled_terminals || widthTe <= 1)) {
|
// Output MIT Label → Pfeil (echter Abgang)
|
||||||
// Normaler Abgang ODER bundled bei 1 TE (Bundle macht bei 1 TE keinen Unterschied)
|
|
||||||
const phaseColor = out.color || getPhaseColor(out.connection_type);
|
const phaseColor = out.color || getPhaseColor(out.connection_type);
|
||||||
html += `<span class="terminal-point terminal-output terminal-row-bottom" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="bottom" data-connection-id="${out.id}" style="${style}">`;
|
html += `<span class="terminal-point terminal-output terminal-row-bottom" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="bottom" data-connection-id="${out.id}" style="${style}">`;
|
||||||
html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`;
|
html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`;
|
||||||
|
|
@ -1062,13 +1111,22 @@
|
||||||
html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`;
|
html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
} else {
|
} else {
|
||||||
// Leerer Terminal - neutral, Position "bottom"
|
// Phasenfarbe aus Propagierung
|
||||||
html += `<span class="terminal-point terminal-empty terminal-row-bottom" data-equipment-id="${eq.id}" data-terminal-position="bottom" data-connection-id="" style="${style}">`;
|
const propColor = (App.terminalColorMap[eq.id] || {})[termId];
|
||||||
html += `<span class="terminal-dot terminal-dot-empty"></span>`;
|
const propPhase = (App.terminalPhaseMap[eq.id] || {})[termId];
|
||||||
html += `</span>`;
|
if (propColor) {
|
||||||
|
html += `<span class="terminal-point terminal-propagated terminal-row-bottom" data-equipment-id="${eq.id}" data-terminal-position="bottom" data-connection-id="" style="${style}">`;
|
||||||
|
html += `<span class="terminal-dot" style="background:${propColor}"></span>`;
|
||||||
|
html += `<span class="terminal-phase">${escapeHtml(propPhase || '')}</span>`;
|
||||||
|
html += `</span>`;
|
||||||
|
} else {
|
||||||
|
html += `<span class="terminal-point terminal-empty terminal-row-bottom" data-equipment-id="${eq.id}" data-terminal-position="bottom" data-connection-id="" style="${style}">`;
|
||||||
|
html += `<span class="terminal-dot terminal-dot-empty"></span>`;
|
||||||
|
html += `</span>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Leere Zellen für restliche TE-Breite (ohne Terminal-Punkte)
|
// Leere Zellen für restliche TE-Breite
|
||||||
for (let t = bottomTerminalCount; t < widthTe; t++) {
|
for (let t = bottomTerminalCount; t < widthTe; t++) {
|
||||||
const colPos = posTe > 0 ? posTe + t : 0;
|
const colPos = posTe > 0 ? posTe + t : 0;
|
||||||
const style = `grid-row:4;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
const style = `grid-row:4;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
||||||
|
|
@ -1098,21 +1156,26 @@
|
||||||
html += `<span class="terminal-label-cell label-row-bottom bundled-label" style="${gridColStyle}" data-connection-id="${bundledBottom.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
html += `<span class="terminal-label-cell label-row-bottom bundled-label" style="${gridColStyle}" data-connection-id="${bundledBottom.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
||||||
if (bundledBottom.output_label) {
|
if (bundledBottom.output_label) {
|
||||||
html += `<span class="terminal-label">${escapeHtml(bundledBottom.output_label)}`;
|
html += `<span class="terminal-label">${escapeHtml(bundledBottom.output_label)}`;
|
||||||
|
if (bundledBottom.output_location) html += `<span class="output-location">${escapeHtml(bundledBottom.output_location)}</span>`;
|
||||||
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
}
|
}
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
} else {
|
} else {
|
||||||
// Normale einzelne Labels pro Terminal - nur für tatsächliche Terminals
|
// Normale einzelne Labels pro Terminal - per Terminal-ID matchen
|
||||||
|
const eqTerminals = getTerminals(eq);
|
||||||
|
const botTerms = eqTerminals.filter(tm => tm.pos === 'bottom');
|
||||||
for (let t = 0; t < bottomTerminalCount; t++) {
|
for (let t = 0; t < bottomTerminalCount; t++) {
|
||||||
const colPos = posTe > 0 ? posTe + t : 0;
|
const colPos = posTe > 0 ? posTe + t : 0;
|
||||||
const style = `grid-row:5;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
const style = `grid-row:5;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
|
||||||
const out = eqBottomOutputs[t] || null;
|
const termId = botTerms[t] ? botTerms[t].id : ('t' + (widthTe + t + 1));
|
||||||
|
const out = eqBottomOutputs.find(o => o.source_terminal_id === termId) || eqBottomOutputs[t] || null;
|
||||||
|
|
||||||
if (out && out.output_label && (!out.bundled_terminals || widthTe <= 1)) {
|
if (out && out.output_label && (!out.bundled_terminals || widthTe <= 1)) {
|
||||||
const cableInfo = buildCableInfo(out);
|
const cableInfo = buildCableInfo(out);
|
||||||
html += `<span class="terminal-label-cell label-row-bottom" style="${style}" data-connection-id="${out.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
html += `<span class="terminal-label-cell label-row-bottom" style="${style}" data-connection-id="${out.id}" data-equipment-id="${eq.id}" data-direction="output">`;
|
||||||
html += `<span class="terminal-label">${escapeHtml(out.output_label)}`;
|
html += `<span class="terminal-label">${escapeHtml(out.output_label)}`;
|
||||||
|
if (out.output_location) html += `<span class="output-location">${escapeHtml(out.output_location)}</span>`;
|
||||||
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
html += `</span>`;
|
html += `</span>`;
|
||||||
|
|
@ -1152,14 +1215,25 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render SVG connection lines from path_data
|
* Render SVG connection lines between equipment
|
||||||
* Only shows connections that were manually drawn on the website
|
* PWA uses different layout than desktop, so we calculate positions dynamically
|
||||||
*/
|
*/
|
||||||
function renderConnectionLines() {
|
function renderConnectionLines() {
|
||||||
|
// Remove existing SVG overlays first
|
||||||
|
$('.connection-lines-svg').remove();
|
||||||
|
|
||||||
|
// Only render if setting is enabled
|
||||||
|
if (!App.showConnectionLines) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!App.connections || App.connections.length === 0) {
|
if (!App.connections || App.connections.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Desktop reference dimensions
|
||||||
|
const DESKTOP_TE_WIDTH = 56;
|
||||||
|
|
||||||
// Für jede Hutschiene ein SVG-Overlay erstellen
|
// Für jede Hutschiene ein SVG-Overlay erstellen
|
||||||
$('.carrier-card').each(function() {
|
$('.carrier-card').each(function() {
|
||||||
const $carrier = $(this);
|
const $carrier = $(this);
|
||||||
|
|
@ -1172,6 +1246,18 @@
|
||||||
const carrierEquipment = App.equipment.filter(e => e.fk_carrier == carrierId);
|
const carrierEquipment = App.equipment.filter(e => e.fk_carrier == carrierId);
|
||||||
const equipmentIds = carrierEquipment.map(e => e.id);
|
const equipmentIds = carrierEquipment.map(e => e.id);
|
||||||
|
|
||||||
|
// Carrier-Daten für Total-TE
|
||||||
|
const carrier = App.carriers.find(c => c.id == carrierId);
|
||||||
|
const totalTe = carrier ? (parseInt(carrier.total_te) || 12) : 12;
|
||||||
|
|
||||||
|
// PWA TE-Breite berechnen
|
||||||
|
const carrierWidth = $content.width();
|
||||||
|
const pwaTeWidth = carrierWidth / (totalTe + 1); // +1 für den Add-Button
|
||||||
|
|
||||||
|
// Scale factor: PWA-Breite / Desktop-Breite
|
||||||
|
const scaleX = pwaTeWidth / DESKTOP_TE_WIDTH;
|
||||||
|
const scaleY = scaleX * 0.8; // Y etwas weniger skalieren (PWA ist kompakter)
|
||||||
|
|
||||||
// Verbindungen filtern die zu dieser Hutschiene gehören
|
// Verbindungen filtern die zu dieser Hutschiene gehören
|
||||||
const carrierConnections = App.connections.filter(c =>
|
const carrierConnections = App.connections.filter(c =>
|
||||||
equipmentIds.includes(parseInt(c.fk_source)) ||
|
equipmentIds.includes(parseInt(c.fk_source)) ||
|
||||||
|
|
@ -1195,16 +1281,88 @@
|
||||||
|
|
||||||
const color = conn.color || getPhaseColor(conn.connection_type);
|
const color = conn.color || getPhaseColor(conn.connection_type);
|
||||||
|
|
||||||
|
// Transform path_data coordinates to PWA scale
|
||||||
|
const scaledPath = transformPathData(conn.path_data, scaleX, scaleY);
|
||||||
|
|
||||||
// Schatten-Pfad für bessere Sichtbarkeit
|
// Schatten-Pfad für bessere Sichtbarkeit
|
||||||
svgContent += `<path class="connection-shadow" d="${conn.path_data}" />`;
|
svgContent += `<path class="connection-shadow" d="${scaledPath}" />`;
|
||||||
// Hauptpfad
|
// Hauptpfad
|
||||||
svgContent += `<path class="connection-line" d="${conn.path_data}" style="stroke:${color}" data-connection-id="${conn.id}" />`;
|
svgContent += `<path class="connection-line" d="${scaledPath}" style="stroke:${color}" data-connection-id="${conn.id}" />`;
|
||||||
|
|
||||||
|
// Label falls vorhanden
|
||||||
|
if (conn.output_label) {
|
||||||
|
const labelPos = getPathMidpoint(scaledPath);
|
||||||
|
if (labelPos) {
|
||||||
|
const labelWidth = Math.min(conn.output_label.length * 6 + 10, 80);
|
||||||
|
svgContent += `<rect x="${labelPos.x - labelWidth/2}" y="${labelPos.y - 8}" width="${labelWidth}" height="16" rx="3" fill="#1a1a1a" stroke="${color}" stroke-width="1"/>`;
|
||||||
|
svgContent += `<text x="${labelPos.x}" y="${labelPos.y + 4}" text-anchor="middle" fill="${color}" font-size="10" font-weight="bold">${escapeHtml(conn.output_label)}</text>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$svg.html(svgContent);
|
$svg.html(svgContent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform path data coordinates by scale factors
|
||||||
|
*/
|
||||||
|
function transformPathData(pathData, scaleX, scaleY) {
|
||||||
|
if (!pathData) return '';
|
||||||
|
|
||||||
|
// Parse and transform coordinates
|
||||||
|
return pathData.replace(/([ML])\s*([\d.-]+)\s+([\d.-]+)/gi, function(match, cmd, x, y) {
|
||||||
|
const newX = (parseFloat(x) * scaleX).toFixed(1);
|
||||||
|
const newY = (parseFloat(y) * scaleY).toFixed(1);
|
||||||
|
return `${cmd} ${newX} ${newY}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get midpoint of a path for label positioning
|
||||||
|
*/
|
||||||
|
function getPathMidpoint(pathData) {
|
||||||
|
if (!pathData) return null;
|
||||||
|
|
||||||
|
const points = [];
|
||||||
|
const regex = /[ML]\s*([\d.-]+)\s+([\d.-]+)/gi;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = regex.exec(pathData)) !== null) {
|
||||||
|
points.push({ x: parseFloat(match[1]), y: parseFloat(match[2]) });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (points.length < 2) return null;
|
||||||
|
|
||||||
|
// Calculate midpoint along path
|
||||||
|
let totalLength = 0;
|
||||||
|
const segments = [];
|
||||||
|
|
||||||
|
for (let i = 1; i < points.length; i++) {
|
||||||
|
const dx = points[i].x - points[i-1].x;
|
||||||
|
const dy = points[i].y - points[i-1].y;
|
||||||
|
const len = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
segments.push({ start: points[i-1], end: points[i], length: len });
|
||||||
|
totalLength += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
const halfLength = totalLength / 2;
|
||||||
|
let accumulated = 0;
|
||||||
|
|
||||||
|
for (const seg of segments) {
|
||||||
|
if (accumulated + seg.length >= halfLength) {
|
||||||
|
const t = (halfLength - accumulated) / seg.length;
|
||||||
|
return {
|
||||||
|
x: seg.start.x + t * (seg.end.x - seg.start.x),
|
||||||
|
y: seg.start.y + t * (seg.end.y - seg.start.y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
accumulated += seg.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: (points[0].x + points[points.length-1].x) / 2, y: (points[0].y + points[points.length-1].y) / 2 };
|
||||||
|
}
|
||||||
|
|
||||||
function renderTypeGrid() {
|
function renderTypeGrid() {
|
||||||
const categoryLabels = {
|
const categoryLabels = {
|
||||||
'automat': 'Leitungsschutz',
|
'automat': 'Leitungsschutz',
|
||||||
|
|
@ -1238,6 +1396,27 @@
|
||||||
$('#type-grid').html(html);
|
$('#type-grid').html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// WIRE DISPLAY TOGGLE
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
function handleToggleWires() {
|
||||||
|
App.showConnectionLines = !App.showConnectionLines;
|
||||||
|
|
||||||
|
// Update button appearance
|
||||||
|
const $btn = $('#btn-toggle-wires');
|
||||||
|
if (App.showConnectionLines) {
|
||||||
|
$btn.addClass('active');
|
||||||
|
$btn.attr('title', 'Leitungen ausblenden');
|
||||||
|
} else {
|
||||||
|
$btn.removeClass('active');
|
||||||
|
$btn.attr('title', 'Leitungen einblenden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-render connection lines
|
||||||
|
renderConnectionLines();
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// PANEL (FELD) ACTIONS
|
// PANEL (FELD) ACTIONS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
@ -1319,7 +1498,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalTe = parseInt(teBtn.data('te'));
|
const totalTe = parseInt(teBtn.data('te'));
|
||||||
const label = $('#carrier-label').val().trim() || 'Hutschiene';
|
const panelId = App.editCarrierId
|
||||||
|
? (App.carriers.find(c => c.id == App.editCarrierId)?.fk_panel || App.currentPanelId)
|
||||||
|
: App.currentPanelId;
|
||||||
|
const panelCarrierCount = App.carriers.filter(c => c.fk_panel == panelId).length;
|
||||||
|
const inputLabel = $('#carrier-label').val().trim();
|
||||||
|
// CREATE: +1 weil neuer Carrier noch nicht in der Liste; UPDATE: inkl. sich selbst gezählt
|
||||||
|
const label = inputLabel || (App.editCarrierId ? 'R' + panelCarrierCount : 'R' + (panelCarrierCount + 1));
|
||||||
|
|
||||||
closeModal('add-carrier');
|
closeModal('add-carrier');
|
||||||
|
|
||||||
|
|
@ -1614,7 +1799,9 @@
|
||||||
html += `<option value="">--</option>`;
|
html += `<option value="">--</option>`;
|
||||||
if (field.options) {
|
if (field.options) {
|
||||||
field.options.split('|').forEach(opt => {
|
field.options.split('|').forEach(opt => {
|
||||||
const selected = (opt === val) ? ' selected' : '';
|
opt = opt.trim();
|
||||||
|
if (!opt) return;
|
||||||
|
const selected = (opt === val.trim()) ? ' selected' : '';
|
||||||
html += `<option value="${escapeHtml(opt)}"${selected}>${escapeHtml(opt)}</option>`;
|
html += `<option value="${escapeHtml(opt)}"${selected}>${escapeHtml(opt)}</option>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -2233,6 +2420,7 @@
|
||||||
$('#btn-delete-connection').removeClass('hidden');
|
$('#btn-delete-connection').removeClass('hidden');
|
||||||
$('#conn-color').val(conn.color || '#3498db');
|
$('#conn-color').val(conn.color || '#3498db');
|
||||||
$('#conn-label').val(conn.output_label || '');
|
$('#conn-label').val(conn.output_label || '');
|
||||||
|
$('#conn-location').val(conn.output_location || '');
|
||||||
$('#conn-medium-length').val(conn.medium_length || '');
|
$('#conn-medium-length').val(conn.medium_length || '');
|
||||||
|
|
||||||
// Medium-Typen laden und Select befüllen
|
// Medium-Typen laden und Select befüllen
|
||||||
|
|
@ -2255,7 +2443,8 @@
|
||||||
|
|
||||||
// Side-Buttons immer zeigen
|
// Side-Buttons immer zeigen
|
||||||
$('#conn-side-fields').show();
|
$('#conn-side-fields').show();
|
||||||
// Medium-Felder nur bei Abgang zeigen
|
// Räumlichkeit und Medium-Felder nur bei Abgang zeigen
|
||||||
|
$('#conn-location-fields').toggle(direction === 'output');
|
||||||
$('#conn-output-fields').toggle(direction === 'output');
|
$('#conn-output-fields').toggle(direction === 'output');
|
||||||
|
|
||||||
// Bundle-Option: Nur bei Abgang + Equipment mit mehr als 1 Terminal
|
// Bundle-Option: Nur bei Abgang + Equipment mit mehr als 1 Terminal
|
||||||
|
|
@ -2274,7 +2463,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phasen-Optionen wie auf der Website
|
// Phasen-Optionen wie auf der Website
|
||||||
const INPUT_PHASES = ['L1', 'L2', 'L3', '3P', '3P+N', 'PE'];
|
const INPUT_PHASES = ['L1', 'L2', 'L3', 'N', 'PE'];
|
||||||
const OUTPUT_PHASES = ['LN', 'N', '3P+N', 'PE', 'DATA'];
|
const OUTPUT_PHASES = ['LN', 'N', '3P+N', 'PE', 'DATA'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2337,6 +2526,200 @@
|
||||||
return colors[idx];
|
return colors[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminals eines Equipment ermitteln (aus terminals_config oder Fallback)
|
||||||
|
* @param {object} eq - Equipment-Objekt
|
||||||
|
* @returns {Array} [{id: 't1', pos: 'top'}, ...]
|
||||||
|
*/
|
||||||
|
function getTerminals(eq) {
|
||||||
|
const type = App.equipmentTypes ? App.equipmentTypes.find(t => t.id == eq.fk_equipment_type) : null;
|
||||||
|
if (type && type.terminals_config) {
|
||||||
|
try {
|
||||||
|
const configStr = typeof type.terminals_config === 'string'
|
||||||
|
? type.terminals_config.replace(/\\r\\n|\\r|\\n/g, ' ')
|
||||||
|
: '';
|
||||||
|
const config = typeof type.terminals_config === 'string'
|
||||||
|
? JSON.parse(configStr)
|
||||||
|
: type.terminals_config;
|
||||||
|
if (config.terminals && Array.isArray(config.terminals)) {
|
||||||
|
return config.terminals;
|
||||||
|
}
|
||||||
|
} catch (e) { /* Parse-Fehler ignorieren */ }
|
||||||
|
}
|
||||||
|
// Fallback: Fortlaufende t1, t2, t3... IDs (gleiche Konvention wie Website)
|
||||||
|
// Standard-LS: t1 (top), t2 (bottom)
|
||||||
|
// Breiteres Equipment: t1..tN (top), t(N+1)..t(2N) (bottom)
|
||||||
|
const widthTe = parseFloat(eq.width_te) || 1;
|
||||||
|
const terminals = [];
|
||||||
|
for (let i = 0; i < widthTe; i++) {
|
||||||
|
terminals.push({id: 't' + (i + 1), pos: 'top', col: i});
|
||||||
|
}
|
||||||
|
for (let i = 0; i < widthTe; i++) {
|
||||||
|
terminals.push({id: 't' + (widthTe + i + 1), pos: 'bottom', col: i});
|
||||||
|
}
|
||||||
|
return terminals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phasen-Labels aus Kürzel parsen (z.B. "3P" → ["L1","L2","L3"])
|
||||||
|
*/
|
||||||
|
function parsePhaseLabels(phases) {
|
||||||
|
if (!phases) return [];
|
||||||
|
const p = phases.toUpperCase();
|
||||||
|
if (p === '3P' || p === 'L1L2L3') return ['L1', 'L2', 'L3'];
|
||||||
|
if (p === '3P+N' || p === '3PN') return ['L1', 'L2', 'L3', 'N'];
|
||||||
|
if (p === '3P+N+PE' || p === '3PNPE') return ['L1', 'L2', 'L3', 'N', 'PE'];
|
||||||
|
if (p === 'L1N' || p === 'L1+N') return ['L1', 'N'];
|
||||||
|
if (p === 'L1') return ['L1'];
|
||||||
|
if (p === 'L2') return ['L2'];
|
||||||
|
if (p === 'L3') return ['L3'];
|
||||||
|
if (p === 'N') return ['N'];
|
||||||
|
if (p === 'PE') return ['PE'];
|
||||||
|
if (p.indexOf('+') !== -1) return p.split('+');
|
||||||
|
if (p.indexOf(',') !== -1) return p.split(',');
|
||||||
|
return [phases];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminal-Farbpropagierung aufbauen
|
||||||
|
* Setzt Phasen direkt anhand der Verbindungsdaten — KEINE Block-Durchreichung.
|
||||||
|
*
|
||||||
|
* Ablauf:
|
||||||
|
* 1. Inputs setzen Phase auf ihr Ziel-Terminal
|
||||||
|
* 2. Outputs setzen Phase auf ihr Quell-Terminal
|
||||||
|
* 3. Wires setzen Phase auf BEIDE Enden (connection_type der Leitung)
|
||||||
|
* 4. Busbars verteilen Phasen an überlappende Equipment-Terminals
|
||||||
|
*/
|
||||||
|
function buildTerminalPhaseMap() {
|
||||||
|
const phaseMap = {}; // {eqId: {termId: "L1"}}
|
||||||
|
const colorMap = {}; // {eqId: {termId: "#hex"}}
|
||||||
|
|
||||||
|
function setPhase(eqId, termId, phase, color) {
|
||||||
|
if (!phaseMap[eqId]) phaseMap[eqId] = {};
|
||||||
|
if (!colorMap[eqId]) colorMap[eqId] = {};
|
||||||
|
if (phaseMap[eqId][termId]) return false; // Bereits gesetzt
|
||||||
|
phaseMap[eqId][termId] = phase;
|
||||||
|
colorMap[eqId][termId] = color || getPhaseColor(phase);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function forcePhase(eqId, termId, phase, color) {
|
||||||
|
if (!phaseMap[eqId]) phaseMap[eqId] = {};
|
||||||
|
if (!colorMap[eqId]) colorMap[eqId] = {};
|
||||||
|
if (phaseMap[eqId][termId] === phase) return false;
|
||||||
|
phaseMap[eqId][termId] = phase;
|
||||||
|
colorMap[eqId][termId] = color || getPhaseColor(phase);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schritt 1: Inputs setzen Phase auf Ziel-Terminal
|
||||||
|
if (App.inputs) {
|
||||||
|
App.inputs.forEach(function(inp) {
|
||||||
|
if (!inp.fk_target || !inp.target_terminal_id) return;
|
||||||
|
var phase = (inp.connection_type || '').toUpperCase();
|
||||||
|
if (!phase) return;
|
||||||
|
setPhase(inp.fk_target, inp.target_terminal_id, phase, inp.color || getPhaseColor(phase));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schritt 2: Outputs setzen Phase auf Quell-Terminal
|
||||||
|
if (App.outputs) {
|
||||||
|
App.outputs.forEach(function(out) {
|
||||||
|
if (!out.fk_source || !out.source_terminal_id) return;
|
||||||
|
var phase = (out.connection_type || '').toUpperCase();
|
||||||
|
if (!phase) return;
|
||||||
|
setPhase(out.fk_source, out.source_terminal_id, phase, out.color || getPhaseColor(phase));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schritt 3: Wires setzen Phase auf BEIDE Enden
|
||||||
|
// connection_type der Leitung bestimmt die Phase direkt
|
||||||
|
if (App.connections) {
|
||||||
|
App.connections.forEach(function(conn) {
|
||||||
|
if (!conn.fk_source || !conn.fk_target) return;
|
||||||
|
var phase = (conn.connection_type || '').toUpperCase();
|
||||||
|
if (!phase) return;
|
||||||
|
var wireColor = conn.color || getPhaseColor(phase);
|
||||||
|
if (conn.source_terminal_id) {
|
||||||
|
setPhase(conn.fk_source, conn.source_terminal_id, phase, wireColor);
|
||||||
|
}
|
||||||
|
if (conn.target_terminal_id) {
|
||||||
|
setPhase(conn.fk_target, conn.target_terminal_id, phase, wireColor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schritt 4: Busbars verteilen Phasen an überlappende Equipment-Terminals
|
||||||
|
if (App.busbars && App.equipment) {
|
||||||
|
App.busbars.forEach(function(busbar) {
|
||||||
|
var railStart = busbar.rail_start_te || 1;
|
||||||
|
var railEnd = busbar.rail_end_te || railStart;
|
||||||
|
var targetPos = (busbar.position_y === 0) ? 'top' : 'bottom';
|
||||||
|
|
||||||
|
// Phase-Labels ermitteln
|
||||||
|
var phaseLabels;
|
||||||
|
if (busbar.phases_config && Array.isArray(busbar.phases_config) && busbar.phases_config.length > 0) {
|
||||||
|
phaseLabels = busbar.phases_config;
|
||||||
|
} else {
|
||||||
|
phaseLabels = parsePhaseLabels(busbar.rail_phases || busbar.connection_type || '');
|
||||||
|
}
|
||||||
|
if (phaseLabels.length === 0) return;
|
||||||
|
|
||||||
|
// Prüfen ob mindestens eine Phase eingespeist wird
|
||||||
|
var anyPhaseFed = false;
|
||||||
|
App.equipment.forEach(function(eq) {
|
||||||
|
if (String(eq.fk_carrier) !== String(busbar.fk_carrier)) return;
|
||||||
|
var eqPos = parseFloat(eq.position_te) || 1;
|
||||||
|
var eqWidth = parseFloat(eq.width_te) || 1;
|
||||||
|
if (!(eqPos < railEnd + 1 && railStart < eqPos + eqWidth)) return;
|
||||||
|
|
||||||
|
var terms = getTerminals(eq);
|
||||||
|
terms.filter(function(t) { return t.pos === targetPos; }).forEach(function(term) {
|
||||||
|
if ((phaseMap[eq.id] || {})[term.id]) {
|
||||||
|
anyPhaseFed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nur verteilen wenn mindestens eine Phase anliegt
|
||||||
|
if (!anyPhaseFed) return;
|
||||||
|
|
||||||
|
// Excluded TEs
|
||||||
|
var excludedTEs = busbar.excluded_te
|
||||||
|
? busbar.excluded_te.split(',').map(function(t) { return parseInt(t.trim()); }).filter(function(t) { return !isNaN(t); })
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Phasen auf Equipment verteilen
|
||||||
|
App.equipment.forEach(function(eq) {
|
||||||
|
if (String(eq.fk_carrier) !== String(busbar.fk_carrier)) return;
|
||||||
|
var eqPos = parseFloat(eq.position_te) || 1;
|
||||||
|
var eqWidth = parseFloat(eq.width_te) || 1;
|
||||||
|
if (!(eqPos < railEnd + 1 && railStart < eqPos + eqWidth)) return;
|
||||||
|
|
||||||
|
var terms = getTerminals(eq);
|
||||||
|
var posTerminals = terms.filter(function(t) { return t.pos === targetPos; });
|
||||||
|
|
||||||
|
posTerminals.forEach(function(term, idx) {
|
||||||
|
var teIndex = term.col !== undefined ? term.col : (idx % eqWidth);
|
||||||
|
var absoluteTE = Math.round(eqPos + teIndex);
|
||||||
|
|
||||||
|
if (excludedTEs.indexOf(absoluteTE) !== -1) return;
|
||||||
|
if (absoluteTE < railStart || absoluteTE > railEnd) return;
|
||||||
|
|
||||||
|
var teOffset = absoluteTE - railStart;
|
||||||
|
var phase = phaseLabels[teOffset % phaseLabels.length];
|
||||||
|
var phaseColor = getPhaseColor(phase);
|
||||||
|
forcePhase(eq.id, term.id, phase, phaseColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Als App-State speichern
|
||||||
|
App.terminalPhaseMap = phaseMap;
|
||||||
|
App.terminalColorMap = colorMap;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abgangsseite-Button setzen
|
* Abgangsseite-Button setzen
|
||||||
*/
|
*/
|
||||||
|
|
@ -2452,7 +2835,7 @@
|
||||||
<div class="terminal-context-menu" style="position:fixed;left:${x}px;top:${y}px;z-index:10001;">
|
<div class="terminal-context-menu" style="position:fixed;left:${x}px;top:${y}px;z-index:10001;">
|
||||||
<div class="tcm-item tcm-input" data-type="input">
|
<div class="tcm-item tcm-input" data-type="input">
|
||||||
<span class="tcm-icon" style="color:#f39c12;">▼</span>
|
<span class="tcm-icon" style="color:#f39c12;">▼</span>
|
||||||
<span>Anschlusspunkt (L1/L2/L3)</span>
|
<span>Anschlusspunkt (Einspeisung)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tcm-item tcm-output" data-type="output">
|
<div class="tcm-item tcm-output" data-type="output">
|
||||||
<span class="tcm-icon" style="color:#3498db;">▲</span>
|
<span class="tcm-icon" style="color:#3498db;">▲</span>
|
||||||
|
|
@ -2576,6 +2959,7 @@
|
||||||
$('#btn-delete-connection').addClass('hidden');
|
$('#btn-delete-connection').addClass('hidden');
|
||||||
$('#conn-color').val('#3498db');
|
$('#conn-color').val('#3498db');
|
||||||
$('#conn-label').val('');
|
$('#conn-label').val('');
|
||||||
|
$('#conn-location').val('');
|
||||||
$('#conn-medium-length').val('');
|
$('#conn-medium-length').val('');
|
||||||
|
|
||||||
// Medium-Typen laden und Select befüllen
|
// Medium-Typen laden und Select befüllen
|
||||||
|
|
@ -2588,7 +2972,8 @@
|
||||||
|
|
||||||
// Side-Buttons immer zeigen (Automaten haben keine feste Richtung)
|
// Side-Buttons immer zeigen (Automaten haben keine feste Richtung)
|
||||||
$('#conn-side-fields').show();
|
$('#conn-side-fields').show();
|
||||||
// Medium-Felder nur bei Abgang zeigen
|
// Räumlichkeit und Medium-Felder nur bei Abgang zeigen
|
||||||
|
$('#conn-location-fields').toggle(direction === 'output');
|
||||||
$('#conn-output-fields').toggle(direction === 'output');
|
$('#conn-output-fields').toggle(direction === 'output');
|
||||||
|
|
||||||
// Bundle-Option: Nur bei Abgang + Equipment mit mehr als 1 Terminal
|
// Bundle-Option: Nur bei Abgang + Equipment mit mehr als 1 Terminal
|
||||||
|
|
@ -2612,6 +2997,7 @@
|
||||||
const connectionType = $('#conn-type').val() || '';
|
const connectionType = $('#conn-type').val() || '';
|
||||||
const color = $('#conn-color').val() || '#3498db';
|
const color = $('#conn-color').val() || '#3498db';
|
||||||
const outputLabel = $('#conn-label').val().trim();
|
const outputLabel = $('#conn-label').val().trim();
|
||||||
|
const outputLocation = $('#conn-location').val().trim();
|
||||||
const isOutput = App.connectionDirection === 'output';
|
const isOutput = App.connectionDirection === 'output';
|
||||||
const mediumType = isOutput ? ($('#conn-medium-type').val().trim() || '') : '';
|
const mediumType = isOutput ? ($('#conn-medium-type').val().trim() || '') : '';
|
||||||
const mediumSpec = isOutput ? ($('#conn-medium-spec').val().trim() || '') : '';
|
const mediumSpec = isOutput ? ($('#conn-medium-spec').val().trim() || '') : '';
|
||||||
|
|
@ -2640,6 +3026,7 @@
|
||||||
connection_type: connectionType,
|
connection_type: connectionType,
|
||||||
color: color,
|
color: color,
|
||||||
output_label: outputLabel,
|
output_label: outputLabel,
|
||||||
|
output_location: outputLocation,
|
||||||
medium_type: mediumType,
|
medium_type: mediumType,
|
||||||
medium_spec: mediumSpec,
|
medium_spec: mediumSpec,
|
||||||
medium_length: mediumLength,
|
medium_length: mediumLength,
|
||||||
|
|
@ -2653,6 +3040,7 @@
|
||||||
conn.connection_type = connectionType;
|
conn.connection_type = connectionType;
|
||||||
conn.color = color;
|
conn.color = color;
|
||||||
conn.output_label = outputLabel;
|
conn.output_label = outputLabel;
|
||||||
|
conn.output_location = outputLocation;
|
||||||
conn.medium_type = mediumType;
|
conn.medium_type = mediumType;
|
||||||
conn.medium_spec = mediumSpec;
|
conn.medium_spec = mediumSpec;
|
||||||
conn.medium_length = mediumLength;
|
conn.medium_length = mediumLength;
|
||||||
|
|
@ -2668,7 +3056,13 @@
|
||||||
const response = await apiCall('ajax/pwa_api.php', data);
|
const response = await apiCall('ajax/pwa_api.php', data);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
||||||
updateLocal(list.find(c => c.id == App.editConnectionId));
|
const conn = list.find(c => c.id == App.editConnectionId);
|
||||||
|
updateLocal(conn);
|
||||||
|
// Farbpropagierung bei Input
|
||||||
|
if (App.connectionDirection === 'input' && connectionType) {
|
||||||
|
const eqId = conn ? conn.fk_target : null;
|
||||||
|
await propagateInputColor(eqId, connectionType, color);
|
||||||
|
}
|
||||||
renderEditor();
|
renderEditor();
|
||||||
showToast('Verbindung aktualisiert', 'success');
|
showToast('Verbindung aktualisiert', 'success');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2681,7 +3075,13 @@
|
||||||
} else {
|
} else {
|
||||||
queueOfflineAction(data);
|
queueOfflineAction(data);
|
||||||
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
||||||
updateLocal(list.find(c => c.id == App.editConnectionId));
|
const conn = list.find(c => c.id == App.editConnectionId);
|
||||||
|
updateLocal(conn);
|
||||||
|
// Farbpropagierung bei Input (offline)
|
||||||
|
if (App.connectionDirection === 'input' && connectionType) {
|
||||||
|
const eqId = conn ? conn.fk_target : null;
|
||||||
|
propagateInputColor(eqId, connectionType, color);
|
||||||
|
}
|
||||||
renderEditor();
|
renderEditor();
|
||||||
showToast('Wird synchronisiert...', 'warning');
|
showToast('Wird synchronisiert...', 'warning');
|
||||||
}
|
}
|
||||||
|
|
@ -2694,6 +3094,7 @@
|
||||||
connection_type: connectionType,
|
connection_type: connectionType,
|
||||||
color: color,
|
color: color,
|
||||||
output_label: outputLabel,
|
output_label: outputLabel,
|
||||||
|
output_location: outputLocation,
|
||||||
medium_type: mediumType,
|
medium_type: mediumType,
|
||||||
medium_spec: mediumSpec,
|
medium_spec: mediumSpec,
|
||||||
medium_length: mediumLength,
|
medium_length: mediumLength,
|
||||||
|
|
@ -2707,6 +3108,7 @@
|
||||||
connection_type: connectionType,
|
connection_type: connectionType,
|
||||||
color: color,
|
color: color,
|
||||||
output_label: outputLabel,
|
output_label: outputLabel,
|
||||||
|
output_location: outputLocation,
|
||||||
medium_type: mediumType,
|
medium_type: mediumType,
|
||||||
medium_spec: mediumSpec,
|
medium_spec: mediumSpec,
|
||||||
medium_length: mediumLength,
|
medium_length: mediumLength,
|
||||||
|
|
@ -2724,6 +3126,10 @@
|
||||||
if (App.connectionDirection === 'input') {
|
if (App.connectionDirection === 'input') {
|
||||||
newConn.fk_target = App.connectionEquipmentId;
|
newConn.fk_target = App.connectionEquipmentId;
|
||||||
App.inputs.push(newConn);
|
App.inputs.push(newConn);
|
||||||
|
// Farbpropagierung bei neuem Input
|
||||||
|
if (connectionType) {
|
||||||
|
await propagateInputColor(App.connectionEquipmentId, connectionType, color);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newConn.fk_source = App.connectionEquipmentId;
|
newConn.fk_source = App.connectionEquipmentId;
|
||||||
App.outputs.push(newConn);
|
App.outputs.push(newConn);
|
||||||
|
|
@ -2743,6 +3149,10 @@
|
||||||
if (App.connectionDirection === 'input') {
|
if (App.connectionDirection === 'input') {
|
||||||
newConn.fk_target = App.connectionEquipmentId;
|
newConn.fk_target = App.connectionEquipmentId;
|
||||||
App.inputs.push(newConn);
|
App.inputs.push(newConn);
|
||||||
|
// Farbpropagierung bei neuem Input (offline)
|
||||||
|
if (connectionType) {
|
||||||
|
propagateInputColor(App.connectionEquipmentId, connectionType, color);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newConn.fk_source = App.connectionEquipmentId;
|
newConn.fk_source = App.connectionEquipmentId;
|
||||||
App.outputs.push(newConn);
|
App.outputs.push(newConn);
|
||||||
|
|
@ -2755,6 +3165,60 @@
|
||||||
App.editConnectionId = null;
|
App.editConnectionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Farbpropagierung: Wenn Input-Phase gesetzt, Farbe auf Outputs übertragen
|
||||||
|
* @param {number} equipmentId - Equipment-ID
|
||||||
|
* @param {string} phase - Phase (L1, L2, L3, N, PE)
|
||||||
|
* @param {string} color - Farbe der Einspeisung
|
||||||
|
*/
|
||||||
|
async function propagateInputColor(equipmentId, phase, color) {
|
||||||
|
if (!equipmentId || !phase || !color) return;
|
||||||
|
|
||||||
|
// Finde alle Outputs dieses Equipment
|
||||||
|
const outputs = App.outputs.filter(o => o.fk_source == equipmentId);
|
||||||
|
if (outputs.length === 0) return;
|
||||||
|
|
||||||
|
// Update Outputs mit passender Phase
|
||||||
|
for (const output of outputs) {
|
||||||
|
// Phase-Matching: L1 -> L1, LN, L1N; L2 -> L2; etc.
|
||||||
|
const outputType = output.connection_type || '';
|
||||||
|
let matches = false;
|
||||||
|
|
||||||
|
if (phase === 'L1' && (outputType.includes('L1') || outputType === 'LN')) matches = true;
|
||||||
|
else if (phase === 'L2' && outputType.includes('L2')) matches = true;
|
||||||
|
else if (phase === 'L3' && outputType.includes('L3')) matches = true;
|
||||||
|
else if (phase === 'N' && outputType.includes('N')) matches = true;
|
||||||
|
else if (phase === 'PE' && outputType.includes('PE')) matches = true;
|
||||||
|
|
||||||
|
if (matches && output.color !== color) {
|
||||||
|
output.color = color;
|
||||||
|
|
||||||
|
// Backend aktualisieren
|
||||||
|
if (App.isOnline) {
|
||||||
|
try {
|
||||||
|
await apiCall('ajax/pwa_api.php', {
|
||||||
|
action: 'update_connection',
|
||||||
|
connection_id: output.id,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
queueOfflineAction({
|
||||||
|
action: 'update_connection',
|
||||||
|
connection_id: output.id,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queueOfflineAction({
|
||||||
|
action: 'update_connection',
|
||||||
|
connection_id: output.id,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection löschen (mit Bestätigung)
|
* Connection löschen (mit Bestätigung)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -555,3 +555,28 @@ ViewModes = Verfuegbare Ansichten
|
||||||
ViewModesBoth = Baum & Graph
|
ViewModesBoth = Baum & Graph
|
||||||
ViewModesTreeOnly = Nur Baum
|
ViewModesTreeOnly = Nur Baum
|
||||||
ViewModesGraphOnly = Nur Graph
|
ViewModesGraphOnly = Nur Graph
|
||||||
|
|
||||||
|
# Ausgebaut-Status
|
||||||
|
Decommissioned = Ausgebaut
|
||||||
|
Decommission = Ausbauen
|
||||||
|
Recommission = Wieder einbauen
|
||||||
|
ShowDecommissioned = Ausgebaute Elemente anzeigen
|
||||||
|
ShowDecommissionedDefault = Ausgebaute Elemente standardmäßig anzeigen
|
||||||
|
|
||||||
|
# Eigener Betrieb & Zubehör
|
||||||
|
CompanyTools = Mein Betrieb
|
||||||
|
HasAccessories = Hat Zubehör
|
||||||
|
HasAccessoriesHelp = Ermöglicht die Zuordnung von Zubehör und Ersatzteilen
|
||||||
|
HasProduct = Produkt-Zuordnung
|
||||||
|
HasProductHelp = Ermöglicht die Verknüpfung mit einem Dolibarr-Produkt
|
||||||
|
Accessories = Zubehör / Ersatzteile
|
||||||
|
NoAccessories = Kein Zubehör zugeordnet
|
||||||
|
SearchProduct = Produkt suchen
|
||||||
|
SelectSupplier = Lieferant auswählen
|
||||||
|
CreateSupplierOrder = Lieferantenbestellung erstellen
|
||||||
|
OrderAccessories = Zubehör bestellen
|
||||||
|
OrderGeneratedFromAccessories = Bestellung aus Anlagen-Zubehör generiert
|
||||||
|
ConfirmDeleteAccessory = Dieses Zubehör wirklich entfernen?
|
||||||
|
NoToolsYet = Noch keine Werkzeuge erfasst
|
||||||
|
AddFirstTool = Erstes Werkzeug hinzufügen
|
||||||
|
GoToTypeAdmin = Zur Typverwaltung
|
||||||
|
|
|
||||||
|
|
@ -303,3 +303,28 @@ ViewModes = Available Views
|
||||||
ViewModesBoth = Tree & Graph
|
ViewModesBoth = Tree & Graph
|
||||||
ViewModesTreeOnly = Tree Only
|
ViewModesTreeOnly = Tree Only
|
||||||
ViewModesGraphOnly = Graph Only
|
ViewModesGraphOnly = Graph Only
|
||||||
|
|
||||||
|
# Decommissioned status
|
||||||
|
Decommissioned = Decommissioned
|
||||||
|
Decommission = Decommission
|
||||||
|
Recommission = Recommission
|
||||||
|
ShowDecommissioned = Show decommissioned elements
|
||||||
|
ShowDecommissionedDefault = Show decommissioned elements by default
|
||||||
|
|
||||||
|
# Own Company & Accessories
|
||||||
|
CompanyTools = My Company
|
||||||
|
HasAccessories = Has Accessories
|
||||||
|
HasAccessoriesHelp = Allows assigning accessories and spare parts
|
||||||
|
HasProduct = Product Assignment
|
||||||
|
HasProductHelp = Allows linking to a Dolibarr product
|
||||||
|
Accessories = Accessories / Spare Parts
|
||||||
|
NoAccessories = No accessories assigned
|
||||||
|
SearchProduct = Search product
|
||||||
|
SelectSupplier = Select supplier
|
||||||
|
CreateSupplierOrder = Create supplier order
|
||||||
|
OrderAccessories = Order accessories
|
||||||
|
OrderGeneratedFromAccessories = Order generated from installation accessories
|
||||||
|
ConfirmDeleteAccessory = Really remove this accessory?
|
||||||
|
NoToolsYet = No tools registered yet
|
||||||
|
AddFirstTool = Add first tool
|
||||||
|
GoToTypeAdmin = Go to type administration
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ function kundenkarte_graph_print_container($params)
|
||||||
print '<a class="ctx-item ctx-add-child" data-action="add-child"><i class="fa fa-plus"></i> '.$langs->trans('AddChild').'</a>';
|
print '<a class="ctx-item ctx-add-child" data-action="add-child"><i class="fa fa-plus"></i> '.$langs->trans('AddChild').'</a>';
|
||||||
print '<a class="ctx-item ctx-edit" data-action="edit"><i class="fa fa-edit"></i> '.$langs->trans('Edit').'</a>';
|
print '<a class="ctx-item ctx-edit" data-action="edit"><i class="fa fa-edit"></i> '.$langs->trans('Edit').'</a>';
|
||||||
print '<a class="ctx-item ctx-copy" data-action="copy"><i class="fa fa-copy"></i> '.$langs->trans('Copy').'</a>';
|
print '<a class="ctx-item ctx-copy" data-action="copy"><i class="fa fa-copy"></i> '.$langs->trans('Copy').'</a>';
|
||||||
|
print '<a class="ctx-item ctx-decommission" data-action="toggle-decommissioned"><i class="fa fa-power-off"></i> <span class="ctx-decommission-label">'.$langs->trans('Decommission').'</span></a>';
|
||||||
}
|
}
|
||||||
if ($permissiontodelete) {
|
if ($permissiontodelete) {
|
||||||
print '<a class="ctx-item ctx-delete" data-action="delete"><i class="fa fa-trash"></i> '.$langs->trans('Delete').'</a>';
|
print '<a class="ctx-item ctx-delete" data-action="delete"><i class="fa fa-trash"></i> '.$langs->trans('Delete').'</a>';
|
||||||
|
|
|
||||||
2131
lib/wiring_diagram.lib.php
Normal file
2131
lib/wiring_diagram.lib.php
Normal file
File diff suppressed because it is too large
Load diff
0
manifest.json
Normal file → Executable file
0
manifest.json
Normal file → Executable file
40
pwa.php
Normal file → Executable file
40
pwa.php
Normal file → Executable file
|
|
@ -44,8 +44,37 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="img/pwa-icon-192.png">
|
<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="apple-touch-icon" href="img/pwa-icon-192.png">
|
||||||
<link rel="stylesheet" href="css/pwa.css?v=5.3">
|
<link rel="stylesheet" href="css/pwa.css?v=5.9">
|
||||||
<style>:root { --primary: <?php echo $themeColor; ?>; }</style>
|
<style>:root { --primary: <?php echo $themeColor; ?>; }</style>
|
||||||
|
<script>
|
||||||
|
// Einmaliger Cache-Reset (v12.4) — löscht alte Service Worker + Caches + Editor-Daten
|
||||||
|
(function() {
|
||||||
|
var REQUIRED_VERSION = 'v12.4';
|
||||||
|
if (localStorage.getItem('sw_version') !== REQUIRED_VERSION) {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.getRegistrations().then(function(regs) {
|
||||||
|
regs.forEach(function(r) { r.unregister(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ('caches' in window) {
|
||||||
|
caches.keys().then(function(names) {
|
||||||
|
names.forEach(function(n) { caches.delete(n); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Gecachte Editor-Daten löschen (kundenkarte_data_*)
|
||||||
|
var keysToRemove = [];
|
||||||
|
for (var i = 0; i < localStorage.length; i++) {
|
||||||
|
var key = localStorage.key(i);
|
||||||
|
if (key && key.indexOf('kundenkarte_data_') === 0) {
|
||||||
|
keysToRemove.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keysToRemove.forEach(function(k) { localStorage.removeItem(k); });
|
||||||
|
localStorage.setItem('sw_version', REQUIRED_VERSION);
|
||||||
|
setTimeout(function() { location.reload(true); }, 500);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app" class="app">
|
<div id="app" class="app">
|
||||||
|
|
@ -133,6 +162,9 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
||||||
<svg viewBox="0 0 24 24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
|
<svg viewBox="0 0 24 24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
|
||||||
<span id="sync-badge" class="sync-badge hidden">0</span>
|
<span id="sync-badge" class="sync-badge hidden">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="btn-toggle-wires" class="btn-icon" title="Leitungen ein-/ausblenden">
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" class="wire-icon-off"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17l-4-4 1.4-1.4 2.6 2.6 6.6-6.6L17 9l-8 8z" class="wire-icon-on hidden"/></svg>
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="editor-content" class="editor-content">
|
<div id="editor-content" class="editor-content">
|
||||||
|
|
@ -243,6 +275,10 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
||||||
<label>Bezeichnung</label>
|
<label>Bezeichnung</label>
|
||||||
<input type="text" id="conn-label" class="form-input" placeholder="z.B. Küche Steckdosen">
|
<input type="text" id="conn-label" class="form-input" placeholder="z.B. Küche Steckdosen">
|
||||||
</div>
|
</div>
|
||||||
|
<div id="conn-location-fields" class="form-group">
|
||||||
|
<label>Räumlichkeit</label>
|
||||||
|
<input type="text" id="conn-location" class="form-input" placeholder="z.B. Küche, Bad OG, Keller">
|
||||||
|
</div>
|
||||||
<!-- Anschlussseite: Immer sichtbar (Automaten haben keine feste Richtung) -->
|
<!-- Anschlussseite: Immer sichtbar (Automaten haben keine feste Richtung) -->
|
||||||
<div id="conn-side-fields" class="form-group">
|
<div id="conn-side-fields" class="form-group">
|
||||||
<label>Anschlussseite</label>
|
<label>Anschlussseite</label>
|
||||||
|
|
@ -374,6 +410,6 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
||||||
window.DOLIBARR_URL = '<?php echo DOL_URL_ROOT; ?>';
|
window.DOLIBARR_URL = '<?php echo DOL_URL_ROOT; ?>';
|
||||||
window.MODULE_URL = '<?php echo DOL_URL_ROOT; ?>/custom/kundenkarte';
|
window.MODULE_URL = '<?php echo DOL_URL_ROOT; ?>/custom/kundenkarte';
|
||||||
</script>
|
</script>
|
||||||
<script src="js/pwa.js?v=5.1"></script>
|
<script src="js/pwa.js?v=5.9"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
0
pwa_auth.php
Normal file → Executable file
0
pwa_auth.php
Normal file → Executable file
12
sql/llx_kundenkarte_anlage_accessory.key.sql
Executable file
12
sql/llx_kundenkarte_anlage_accessory.key.sql
Executable file
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- Copyright (C) 2024 Data IT Solution
|
||||||
|
--
|
||||||
|
-- This program is free software: you can redistribute it and/or modify
|
||||||
|
-- it under the terms of the GNU General Public License as published by
|
||||||
|
-- the Free Software Foundation, either version 3 of the License, or
|
||||||
|
-- (at your option) any later version.
|
||||||
|
-- ========================================================================
|
||||||
|
|
||||||
|
ALTER TABLE llx_kundenkarte_anlage_accessory ADD UNIQUE INDEX uk_anlage_accessory (fk_anlage, fk_product);
|
||||||
|
ALTER TABLE llx_kundenkarte_anlage_accessory ADD INDEX idx_accessory_anlage (fk_anlage);
|
||||||
|
ALTER TABLE llx_kundenkarte_anlage_accessory ADD CONSTRAINT fk_accessory_anlage FOREIGN KEY (fk_anlage) REFERENCES llx_kundenkarte_anlage(rowid) ON DELETE CASCADE;
|
||||||
19
sql/llx_kundenkarte_anlage_accessory.sql
Executable file
19
sql/llx_kundenkarte_anlage_accessory.sql
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- Copyright (C) 2024 Data IT Solution
|
||||||
|
--
|
||||||
|
-- This program is free software: you can redistribute it and/or modify
|
||||||
|
-- it under the terms of the GNU General Public License as published by
|
||||||
|
-- the Free Software Foundation, either version 3 of the License, or
|
||||||
|
-- (at your option) any later version.
|
||||||
|
-- ========================================================================
|
||||||
|
|
||||||
|
CREATE TABLE llx_kundenkarte_anlage_accessory (
|
||||||
|
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
fk_anlage integer NOT NULL,
|
||||||
|
fk_product integer NOT NULL,
|
||||||
|
qty double DEFAULT 1,
|
||||||
|
rang integer DEFAULT 0,
|
||||||
|
note varchar(255) DEFAULT NULL,
|
||||||
|
date_creation datetime DEFAULT NULL,
|
||||||
|
fk_user_creat integer DEFAULT NULL
|
||||||
|
) ENGINE=InnoDB;
|
||||||
7
sw.js
Normal file → Executable file
7
sw.js
Normal file → Executable file
|
|
@ -3,8 +3,8 @@
|
||||||
* Offline-First für Schaltschrank-Dokumentation
|
* Offline-First für Schaltschrank-Dokumentation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE_NAME = 'kundenkarte-pwa-v6.1';
|
const CACHE_NAME = 'kundenkarte-pwa-v12.5';
|
||||||
const OFFLINE_CACHE = 'kundenkarte-offline-v6.1';
|
const OFFLINE_CACHE = 'kundenkarte-offline-v12.4';
|
||||||
|
|
||||||
// Statische Assets die immer gecached werden (ohne Query-String)
|
// Statische Assets die immer gecached werden (ohne Query-String)
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
|
|
@ -50,6 +50,9 @@ self.addEventListener('activate', event => {
|
||||||
self.addEventListener('fetch', event => {
|
self.addEventListener('fetch', event => {
|
||||||
const url = new URL(event.request.url);
|
const url = new URL(event.request.url);
|
||||||
|
|
||||||
|
// Nur http/https cachen - chrome-extension:// etc. überspringen
|
||||||
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') return;
|
||||||
|
|
||||||
// PWA Auth Endpoints - immer Netzwerk
|
// PWA Auth Endpoints - immer Netzwerk
|
||||||
if (url.pathname.includes('pwa_auth.php')) {
|
if (url.pathname.includes('pwa_auth.php')) {
|
||||||
event.respondWith(fetch(event.request));
|
event.respondWith(fetch(event.request));
|
||||||
|
|
|
||||||
357
tabs/anlagen.php
357
tabs/anlagen.php
|
|
@ -29,6 +29,7 @@ dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||||
dol_include_once('/kundenkarte/class/equipmentpanel.class.php');
|
dol_include_once('/kundenkarte/class/equipmentpanel.class.php');
|
||||||
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
|
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
|
||||||
dol_include_once('/kundenkarte/class/equipment.class.php');
|
dol_include_once('/kundenkarte/class/equipment.class.php');
|
||||||
|
dol_include_once('/kundenkarte/class/anlageaccessory.class.php');
|
||||||
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
||||||
|
|
||||||
// Load translation files
|
// Load translation files
|
||||||
|
|
@ -177,6 +178,7 @@ if ($action == 'add' && $permissiontoadd) {
|
||||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||||
$anlage->fk_system = $systemId;
|
$anlage->fk_system = $systemId;
|
||||||
|
$anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null;
|
||||||
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
||||||
$anlage->status = 1;
|
$anlage->status = 1;
|
||||||
|
|
||||||
|
|
@ -219,6 +221,7 @@ if ($action == 'update' && $permissiontoadd) {
|
||||||
$anlage->label = GETPOST('label', 'alphanohtml');
|
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||||
|
$anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null;
|
||||||
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
||||||
|
|
||||||
// Get type - but keep current system for GLOBAL types (buildings)
|
// Get type - but keep current system for GLOBAL types (buildings)
|
||||||
|
|
@ -507,6 +510,10 @@ if ($isTreeView) {
|
||||||
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
|
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
|
||||||
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
|
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
$showDecomm = getDolGlobalInt('KUNDENKARTE_SHOW_DECOMMISSIONED', 0);
|
||||||
|
print '<button type="button" class="button small'.($showDecomm ? ' active' : '').'" id="btn-toggle-decommissioned" title="'.$langs->trans('ShowDecommissioned').'">';
|
||||||
|
print '<i class="fa '.($showDecomm ? 'fa-eye' : 'fa-eye-slash').'"></i> <span>'.$langs->trans('Decommissioned').'</span>';
|
||||||
|
print '</button>';
|
||||||
if ($systemId > 0) {
|
if ($systemId > 0) {
|
||||||
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$id.'&system='.$systemId;
|
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$id.'&system='.$systemId;
|
||||||
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
|
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
|
||||||
|
|
@ -587,13 +594,28 @@ if (empty($customerSystems)) {
|
||||||
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
|
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
|
||||||
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
|
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
|
||||||
|
|
||||||
|
// Zugeordnetes Produkt (nur wenn Typ es erlaubt)
|
||||||
|
if ($type->has_product && $anlage->fk_product > 0) {
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
$product = new Product($db);
|
||||||
|
if ($product->fetch($anlage->fk_product) > 0) {
|
||||||
|
print '<tr><td>'.$langs->trans('Product').'</td>';
|
||||||
|
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$product->id.'">'.dol_escape_htmltag($product->ref).'</a>';
|
||||||
|
print ' - '.dol_escape_htmltag($product->label);
|
||||||
|
if ($product->price > 0) {
|
||||||
|
print ' <span class="opacitymedium">('.price($product->price).' €)</span>';
|
||||||
|
}
|
||||||
|
print '</td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamic fields - all fields come from type definition
|
// Dynamic fields - all fields come from type definition
|
||||||
$fieldValues = $anlage->getFieldValues();
|
$fieldValues = $anlage->getFieldValues();
|
||||||
$typeFieldsList = $type->fetchFields();
|
$typeFieldsList = $type->fetchFields();
|
||||||
foreach ($typeFieldsList as $field) {
|
foreach ($typeFieldsList as $field) {
|
||||||
if ($field->field_type === 'header') {
|
if ($field->field_type === 'header') {
|
||||||
// Section header
|
// Section header
|
||||||
print '<tr class="liste_titre"><th colspan="2" style="background:#f0f0f0;padding:8px;">'.dol_escape_htmltag($field->field_label).'</th></tr>';
|
print '<tr class="liste_titre"><th colspan="2" style="padding:8px;">'.dol_escape_htmltag($field->field_label).'</th></tr>';
|
||||||
} else {
|
} else {
|
||||||
$value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : '';
|
$value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : '';
|
||||||
if ($value !== '') {
|
if ($value !== '') {
|
||||||
|
|
@ -627,6 +649,16 @@ if (empty($customerSystems)) {
|
||||||
print '<td>'.dol_print_date($anlage->tms, 'dayhour').'</td></tr>';
|
print '<td>'.dol_print_date($anlage->tms, 'dayhour').'</td></tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ausgebaut-Status
|
||||||
|
if (!empty($anlage->decommissioned)) {
|
||||||
|
print '<tr><td>'.$langs->trans('Decommissioned').'</td>';
|
||||||
|
print '<td><span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$langs->trans('Decommissioned').'</span>';
|
||||||
|
if (!empty($anlage->date_decommissioned)) {
|
||||||
|
print ' '.dol_print_date(strtotime($anlage->date_decommissioned), 'day');
|
||||||
|
}
|
||||||
|
print '</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
print '</table>';
|
print '</table>';
|
||||||
|
|
||||||
// Files section
|
// Files section
|
||||||
|
|
@ -749,6 +781,9 @@ if (empty($customerSystems)) {
|
||||||
print '<button type="button" class="schematic-add-busbar" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#f39c12;cursor:pointer;" title="Phasenschiene hinzufügen">';
|
print '<button type="button" class="schematic-add-busbar" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#f39c12;cursor:pointer;" title="Phasenschiene hinzufügen">';
|
||||||
print '<i class="fa fa-arrows-h"></i> Phasenschiene';
|
print '<i class="fa fa-arrows-h"></i> Phasenschiene';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
print '<button type="button" class="schematic-straighten-connections" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#2ecc71;cursor:pointer;" title="Diagonale Leitungen begradigen (nur rechte Winkel)">';
|
||||||
|
print '<i class="fa fa-align-justify"></i> Begradigen';
|
||||||
|
print '</button>';
|
||||||
print '<button type="button" class="schematic-clear-connections" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#e74c3c;cursor:pointer;">';
|
print '<button type="button" class="schematic-clear-connections" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#e74c3c;cursor:pointer;">';
|
||||||
print '<i class="fa fa-trash"></i> Alle Verbindungen löschen';
|
print '<i class="fa fa-trash"></i> Alle Verbindungen löschen';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
|
@ -760,11 +795,20 @@ if (empty($customerSystems)) {
|
||||||
print '<button type="button" class="schematic-audit-log" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#95a5a6;cursor:pointer;" title="Änderungsprotokoll anzeigen">';
|
print '<button type="button" class="schematic-audit-log" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#95a5a6;cursor:pointer;" title="Änderungsprotokoll anzeigen">';
|
||||||
print '<i class="fa fa-history"></i> Protokoll';
|
print '<i class="fa fa-history"></i> Protokoll';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
// Display Settings button
|
||||||
|
print '<button type="button" class="schematic-settings-btn" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;" title="Anzeigeeinstellungen (Leitungsfarben, -stärken, Terminal-Stil)">';
|
||||||
|
print '<i class="fa fa-cog"></i> Anzeige';
|
||||||
|
print '</button>';
|
||||||
// PDF Export button
|
// PDF Export button
|
||||||
$pdfExportUrl = dol_buildpath('/kundenkarte/ajax/export_schematic_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A4&orientation=L';
|
$pdfExportUrl = dol_buildpath('/kundenkarte/ajax/export_schematic_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A4&orientation=L';
|
||||||
print '<a href="'.$pdfExportUrl.'" target="_blank" class="schematic-export-pdf" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:5px;" title="PDF Export (Leitungslaufplan nach DIN EN 61082)">';
|
print '<a href="'.$pdfExportUrl.'" target="_blank" class="schematic-export-pdf" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:5px;" title="PDF Export (Leitungslaufplan nach DIN EN 61082)">';
|
||||||
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
|
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
|
||||||
print '</a>';
|
print '</a>';
|
||||||
|
// Leitungslaufplan PDF-Export (separates Feature)
|
||||||
|
$wiringUrl = dol_buildpath('/kundenkarte/ajax/export_wiring_diagram_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A3&orientation=L';
|
||||||
|
print '<a href="'.$wiringUrl.'" target="_blank" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#27ae60;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:5px;" title="Leitungslaufplan (DIN EN 61082)">';
|
||||||
|
print '<i class="fa fa-sitemap"></i> Leitungslaufplan';
|
||||||
|
print '</a>';
|
||||||
print '</div>';
|
print '</div>';
|
||||||
print '</div>';
|
print '</div>';
|
||||||
print '<div class="schematic-message info">Bereit</div>';
|
print '<div class="schematic-message info">Bereit</div>';
|
||||||
|
|
@ -784,6 +828,75 @@ if (empty($customerSystems)) {
|
||||||
</script>';
|
</script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zubehör-Bereich (nur wenn Typ has_accessories hat)
|
||||||
|
if (!empty($type->has_accessories)) {
|
||||||
|
$accessoryObj = new AnlageAccessory($db);
|
||||||
|
$accessories = $accessoryObj->fetchAllByAnlage($anlageId);
|
||||||
|
|
||||||
|
print '<br><h4><i class="fa fa-puzzle-piece"></i> '.$langs->trans('Accessories').'</h4>';
|
||||||
|
|
||||||
|
if (!empty($accessories)) {
|
||||||
|
print '<div class="div-table-responsive">';
|
||||||
|
print '<table class="tagtable liste">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th>'.$langs->trans('ProductRef').'</th>';
|
||||||
|
print '<th>'.$langs->trans('Label').'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans('Qty').'</th>';
|
||||||
|
print '<th>'.$langs->trans('Note').'</th>';
|
||||||
|
if ($permissiontodelete) {
|
||||||
|
print '<th class="right">'.$langs->trans('Action').'</th>';
|
||||||
|
}
|
||||||
|
print '</tr>';
|
||||||
|
foreach ($accessories as $acc) {
|
||||||
|
print '<tr>';
|
||||||
|
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$acc->fk_product.'">'.dol_escape_htmltag($acc->product_ref).'</a></td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($acc->product_label).'</td>';
|
||||||
|
print '<td class="right">'.$acc->qty.'</td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($acc->note).'</td>';
|
||||||
|
if ($permissiontodelete) {
|
||||||
|
print '<td class="right"><a href="#" class="btn-delete-accessory" data-id="'.$acc->id.'" title="'.$langs->trans('Delete').'"><i class="fa fa-trash"></i></a></td>';
|
||||||
|
}
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
print '</table>';
|
||||||
|
print '</div>';
|
||||||
|
} else {
|
||||||
|
print '<p class="opacitymedium">'.$langs->trans('NoAccessories').'</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zubehör hinzufügen
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
print '<div id="add-accessory-form" style="margin-top:10px;">';
|
||||||
|
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
|
||||||
|
print '<input type="text" id="accessory_product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'...">';
|
||||||
|
print '<input type="hidden" id="accessory_product_id" value="">';
|
||||||
|
print '<input type="number" id="accessory_qty" class="flat" value="1" min="1" style="width:80px;">';
|
||||||
|
print '<button type="button" id="btn-add-accessory" class="button small" disabled>'.$langs->trans('Add').'</button>';
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bestellfunktion
|
||||||
|
if ($permissiontoadd && !empty($accessories)) {
|
||||||
|
print '<div style="margin-top:15px;padding:10px;border:1px solid #444;border-radius:4px;">';
|
||||||
|
print '<h5 style="margin:0 0 10px 0;"><i class="fa fa-shopping-cart"></i> '.$langs->trans('OrderAccessories').'</h5>';
|
||||||
|
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
|
||||||
|
print '<select id="supplier_select" class="flat minwidth200">';
|
||||||
|
print '<option value="">'.$langs->trans('SelectSupplier').'</option>';
|
||||||
|
$sqlSupp = "SELECT s.rowid, s.nom FROM ".MAIN_DB_PREFIX."societe s WHERE s.fournisseur = 1 AND s.status = 1 ORDER BY s.nom";
|
||||||
|
$resSupp = $db->query($sqlSupp);
|
||||||
|
if ($resSupp) {
|
||||||
|
while ($objSupp = $db->fetch_object($resSupp)) {
|
||||||
|
print '<option value="'.$objSupp->rowid.'">'.dol_escape_htmltag($objSupp->nom).'</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '</select>';
|
||||||
|
print '<button type="button" id="btn-order-accessories" class="button small">'.$langs->trans('CreateSupplierOrder').'</button>';
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
print '<div class="tabsAction">';
|
print '<div class="tabsAction">';
|
||||||
if ($permissiontoadd) {
|
if ($permissiontoadd) {
|
||||||
|
|
@ -883,7 +996,7 @@ if (empty($customerSystems)) {
|
||||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||||
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||||
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||||
print '<option value="'.$t->id.'" data-category="building" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
print '<option value="'.$t->id.'" data-category="building" data-icon="'.$picto.'" data-color="'.$color.'" data-has-product="'.($t->has_product ? '1' : '0').'" data-has-accessories="'.($t->has_accessories ? '1' : '0').'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||||
}
|
}
|
||||||
if ($lastGroup !== '') print '</optgroup>';
|
if ($lastGroup !== '') print '</optgroup>';
|
||||||
}
|
}
|
||||||
|
|
@ -895,7 +1008,7 @@ if (empty($customerSystems)) {
|
||||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||||
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||||
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||||
print '<option value="'.$t->id.'" data-category="element" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
print '<option value="'.$t->id.'" data-category="element" data-icon="'.$picto.'" data-color="'.$color.'" data-has-product="'.($t->has_product ? '1' : '0').'" data-has-accessories="'.($t->has_accessories ? '1' : '0').'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||||
}
|
}
|
||||||
print '</optgroup>';
|
print '</optgroup>';
|
||||||
}
|
}
|
||||||
|
|
@ -916,6 +1029,23 @@ if (empty($customerSystems)) {
|
||||||
printTreeOptions($tree, $selectedParent, $excludeId);
|
printTreeOptions($tree, $selectedParent, $excludeId);
|
||||||
print '</select></td></tr>';
|
print '</select></td></tr>';
|
||||||
|
|
||||||
|
// Produkt-Zuordnung (wird per JS ein-/ausgeblendet je nach Typ)
|
||||||
|
$productValue = '';
|
||||||
|
$productId = 0;
|
||||||
|
if (($isEdit || $isCopy) && $anlage->fk_product > 0) {
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
$product = new Product($db);
|
||||||
|
if ($product->fetch($anlage->fk_product) > 0) {
|
||||||
|
$productValue = $product->ref.' - '.$product->label;
|
||||||
|
$productId = $product->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '<tr id="row_product" style="display:none;"><td>'.$langs->trans('Product').'</td>';
|
||||||
|
print '<td>';
|
||||||
|
print '<input type="text" id="product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'..." value="'.dol_escape_htmltag($productValue).'">';
|
||||||
|
print '<input type="hidden" name="fk_product" id="fk_product" value="'.$productId.'">';
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
// Dynamic fields will be inserted here via JavaScript
|
// Dynamic fields will be inserted here via JavaScript
|
||||||
print '<tbody id="dynamic_fields"></tbody>';
|
print '<tbody id="dynamic_fields"></tbody>';
|
||||||
|
|
||||||
|
|
@ -1005,15 +1135,15 @@ if (empty($customerSystems)) {
|
||||||
$typeSelect.prop("disabled", false);
|
$typeSelect.prop("disabled", false);
|
||||||
$("#row_type").show();
|
$("#row_type").show();
|
||||||
|
|
||||||
// Wert wiederherstellen falls noch vorhanden
|
|
||||||
if (currentVal && $typeSelect.find("option[value=\"" + currentVal + "\"]").length) {
|
|
||||||
$typeSelect.val(currentVal);
|
|
||||||
} else {
|
|
||||||
$typeSelect.val("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select2 neu initialisieren
|
// Select2 neu initialisieren
|
||||||
initSelect2();
|
initSelect2();
|
||||||
|
|
||||||
|
// Wert wiederherstellen falls noch vorhanden (nach Select2-Init mit trigger)
|
||||||
|
if (currentVal && $typeSelect.find("option[value=\"" + currentVal + "\"]").length) {
|
||||||
|
$typeSelect.val(currentVal).trigger("change");
|
||||||
|
} else {
|
||||||
|
$typeSelect.val("").trigger("change");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$catSelect.on("change", function() {
|
$catSelect.on("change", function() {
|
||||||
|
|
@ -1022,9 +1152,24 @@ if (empty($customerSystems)) {
|
||||||
$typeSelect.trigger("change");
|
$typeSelect.trigger("change");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Produkt-Zeile ein-/ausblenden je nach Typ-Flag has_product
|
||||||
|
function updateProductRow() {
|
||||||
|
var $selected = $typeSelect.find("option:selected");
|
||||||
|
var hasProduct = $selected.data("has-product");
|
||||||
|
if (hasProduct == 1) {
|
||||||
|
$("#row_product").show();
|
||||||
|
} else {
|
||||||
|
$("#row_product").hide();
|
||||||
|
$("#fk_product").val("");
|
||||||
|
$("#product_search").val("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$typeSelect.on("change", updateProductRow);
|
||||||
|
|
||||||
// Initial filtern
|
// Initial filtern
|
||||||
if ($catSelect.val()) {
|
if ($catSelect.val()) {
|
||||||
filterTypes();
|
filterTypes();
|
||||||
|
updateProductRow();
|
||||||
} else {
|
} else {
|
||||||
$typeSelect.prop("disabled", true);
|
$typeSelect.prop("disabled", true);
|
||||||
$("#row_type").hide();
|
$("#row_type").hide();
|
||||||
|
|
@ -1092,7 +1237,7 @@ if (empty($customerSystems)) {
|
||||||
$connectionsByTarget = array();
|
$connectionsByTarget = array();
|
||||||
|
|
||||||
if (!empty($tree)) {
|
if (!empty($tree)) {
|
||||||
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$id.'">';
|
print '<div class="kundenkarte-tree'.($showDecomm ? ' show-decommissioned' : '').'" data-system="'.$systemId.'" data-socid="'.$id.'">';
|
||||||
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
|
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
|
||||||
print '</div>';
|
print '</div>';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1109,6 +1254,168 @@ print dol_get_fiche_end();
|
||||||
// Tooltip container
|
// Tooltip container
|
||||||
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
|
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
|
||||||
|
|
||||||
|
// Produkt-Autocomplete + Zubehör-AJAX (nur wenn Formular oder Detailansicht aktiv)
|
||||||
|
if (in_array($action, array('create', 'edit', 'copy', 'view'))) {
|
||||||
|
print '<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
var baseUrl = "'.dol_escape_js(dol_buildpath('/kundenkarte', 1)).'";
|
||||||
|
|
||||||
|
// Produkt-Autocomplete
|
||||||
|
function initProductAutocomplete(inputSelector, hiddenSelector) {
|
||||||
|
var $input = $(inputSelector);
|
||||||
|
var $hidden = $(hiddenSelector);
|
||||||
|
if (!$input.length) return;
|
||||||
|
|
||||||
|
var searchTimeout;
|
||||||
|
$input.on("input", function() {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
var term = $(this).val();
|
||||||
|
if (term.length < 2) {
|
||||||
|
$hidden.val("");
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
searchTimeout = setTimeout(function() {
|
||||||
|
$.get(baseUrl + "/ajax/equipment.php", {
|
||||||
|
action: "get_products",
|
||||||
|
term: term,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}, function(data) {
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
if (data.success && data.products && data.products.length > 0) {
|
||||||
|
var $dropdown = $("<div class=\"product-autocomplete-dropdown\"></div>");
|
||||||
|
$.each(data.products, function(i, p) {
|
||||||
|
var label = p.ref + " - " + p.label;
|
||||||
|
if (p.price > 0) label += " (" + p.price + " \u20ac)";
|
||||||
|
$dropdown.append(
|
||||||
|
$("<div class=\"product-autocomplete-item\"></div>")
|
||||||
|
.text(label)
|
||||||
|
.data("id", p.id)
|
||||||
|
.data("ref", p.ref)
|
||||||
|
.data("label", p.label)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
$input.after($dropdown);
|
||||||
|
$dropdown.on("click", ".product-autocomplete-item", function() {
|
||||||
|
$input.val($(this).data("ref") + " - " + $(this).data("label"));
|
||||||
|
$hidden.val($(this).data("id"));
|
||||||
|
$dropdown.remove();
|
||||||
|
if (inputSelector === "#accessory_product_search") {
|
||||||
|
$("#btn-add-accessory").prop("disabled", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("click", function(e) {
|
||||||
|
if (!$(e.target).closest(inputSelector + ", .product-autocomplete-dropdown").length) {
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on("change", function() {
|
||||||
|
if (!$(this).val()) {
|
||||||
|
$hidden.val("");
|
||||||
|
if (inputSelector === "#accessory_product_search") {
|
||||||
|
$("#btn-add-accessory").prop("disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initProductAutocomplete("#product_search", "#fk_product");
|
||||||
|
initProductAutocomplete("#accessory_product_search", "#accessory_product_id");
|
||||||
|
|
||||||
|
// Zubehör hinzufügen
|
||||||
|
$("#btn-add-accessory").on("click", function() {
|
||||||
|
var productId = $("#accessory_product_id").val();
|
||||||
|
var qty = $("#accessory_qty").val() || 1;
|
||||||
|
var anlageId = '.((int) $anlageId).';
|
||||||
|
if (!productId || !anlageId) return;
|
||||||
|
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "add",
|
||||||
|
fk_anlage: anlageId,
|
||||||
|
fk_product: productId,
|
||||||
|
qty: qty,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success) { location.reload(); }
|
||||||
|
else { alert(data.error || "Fehler"); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zubehör löschen
|
||||||
|
$(".btn-delete-accessory").on("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!confirm("'.$langs->trans('ConfirmDeleteAccessory').'")) return;
|
||||||
|
var accId = $(this).data("id");
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "delete",
|
||||||
|
id: accId,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success) { location.reload(); }
|
||||||
|
else { alert(data.error || "Fehler"); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lieferantenbestellung
|
||||||
|
$("#btn-order-accessories").on("click", function() {
|
||||||
|
var supplierId = $("#supplier_select").val();
|
||||||
|
var anlageId = '.((int) $anlageId).';
|
||||||
|
if (!supplierId) { alert("Bitte Lieferant auswählen"); return; }
|
||||||
|
|
||||||
|
var ids = [];
|
||||||
|
$(".btn-delete-accessory").each(function() { ids.push($(this).data("id")); });
|
||||||
|
if (ids.length === 0) return;
|
||||||
|
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "order",
|
||||||
|
fk_anlage: anlageId,
|
||||||
|
supplier_id: supplierId,
|
||||||
|
"ids[]": ids,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success && data.order_id) {
|
||||||
|
window.location.href = "'.DOL_URL_ROOT.'/fourn/commande/card.php?id=" + data.order_id;
|
||||||
|
} else {
|
||||||
|
alert(data.error || "Fehler beim Erstellen der Bestellung");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>';
|
||||||
|
|
||||||
|
// CSS für Autocomplete
|
||||||
|
print '<style>
|
||||||
|
.product-autocomplete-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
background: var(--colorbackbody, #fff);
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 300px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
.product-autocomplete-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
.product-autocomplete-item:hover {
|
||||||
|
background: var(--colorbacklinepairhover, #333);
|
||||||
|
}
|
||||||
|
.product-autocomplete-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
</style>';
|
||||||
|
}
|
||||||
|
|
||||||
llxFooter();
|
llxFooter();
|
||||||
$db->close();
|
$db->close();
|
||||||
|
|
||||||
|
|
@ -1227,6 +1534,9 @@ function printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $lev
|
||||||
if (!$hasConnection && $level > 0) {
|
if (!$hasConnection && $level > 0) {
|
||||||
$nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel
|
$nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel
|
||||||
}
|
}
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$nodeClass .= ' decommissioned';
|
||||||
|
}
|
||||||
if ($node->type_can_have_equipment) {
|
if ($node->type_can_have_equipment) {
|
||||||
$nodeClass .= ' node-equipment'; // Geräte-Container (Schaltschrank, Verteiler)
|
$nodeClass .= ' node-equipment'; // Geräte-Container (Schaltschrank, Verteiler)
|
||||||
} elseif ($node->type_can_have_children) {
|
} elseif ($node->type_can_have_children) {
|
||||||
|
|
@ -1262,6 +1572,14 @@ function printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $lev
|
||||||
}
|
}
|
||||||
print '</span>';
|
print '</span>';
|
||||||
|
|
||||||
|
// Ausgebaut-Badge mit Datum
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$decommDate = !empty($node->date_decommissioned) ? dol_print_date(strtotime($node->date_decommissioned), 'day') : '';
|
||||||
|
$decommText = $langs->trans('Decommissioned');
|
||||||
|
if ($decommDate) $decommText .= ' '.$decommDate;
|
||||||
|
print ' <span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$decommText.'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
// Spacer to push badges to the right
|
// Spacer to push badges to the right
|
||||||
print '<span class="kundenkarte-tree-spacer"></span>';
|
print '<span class="kundenkarte-tree-spacer"></span>';
|
||||||
|
|
||||||
|
|
@ -1304,6 +1622,9 @@ function printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $lev
|
||||||
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
|
$decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission');
|
||||||
|
$decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off';
|
||||||
|
print '<a href="#" class="btn-toggle-decommissioned" data-anlage-id="'.$node->id.'" title="'.$decommLabel.'"><i class="fa '.$decommIcon.'"></i></a>';
|
||||||
}
|
}
|
||||||
if ($canDelete) {
|
if ($canDelete) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
||||||
|
|
@ -1473,6 +1794,9 @@ function printTreeWithCableLines($nodes, $socid, $systemId, $canEdit, $canDelete
|
||||||
if (!$hasConnection && $level > 0) {
|
if (!$hasConnection && $level > 0) {
|
||||||
$nodeClass .= ' no-cable';
|
$nodeClass .= ' no-cable';
|
||||||
}
|
}
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$nodeClass .= ' decommissioned';
|
||||||
|
}
|
||||||
|
|
||||||
print '<div class="'.$nodeClass.'">';
|
print '<div class="'.$nodeClass.'">';
|
||||||
|
|
||||||
|
|
@ -1529,6 +1853,14 @@ function printTreeWithCableLines($nodes, $socid, $systemId, $canEdit, $canDelete
|
||||||
}
|
}
|
||||||
print '</span>';
|
print '</span>';
|
||||||
|
|
||||||
|
// Ausgebaut-Badge mit Datum
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$decommDate = !empty($node->date_decommissioned) ? dol_print_date(strtotime($node->date_decommissioned), 'day') : '';
|
||||||
|
$decommText = $langs->trans('Decommissioned');
|
||||||
|
if ($decommDate) $decommText .= ' '.$decommDate;
|
||||||
|
print ' <span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$decommText.'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
// Spacer to push badges to the right
|
// Spacer to push badges to the right
|
||||||
print '<span class="kundenkarte-tree-spacer"></span>';
|
print '<span class="kundenkarte-tree-spacer"></span>';
|
||||||
|
|
||||||
|
|
@ -1568,6 +1900,9 @@ function printTreeWithCableLines($nodes, $socid, $systemId, $canEdit, $canDelete
|
||||||
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
|
$decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission');
|
||||||
|
$decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off';
|
||||||
|
print '<a href="#" class="btn-toggle-decommissioned" data-anlage-id="'.$node->id.'" title="'.$decommLabel.'"><i class="fa '.$decommIcon.'"></i></a>';
|
||||||
}
|
}
|
||||||
if ($canDelete) {
|
if ($canDelete) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||||
dol_include_once('/kundenkarte/class/equipmentpanel.class.php');
|
dol_include_once('/kundenkarte/class/equipmentpanel.class.php');
|
||||||
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
|
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
|
||||||
dol_include_once('/kundenkarte/class/equipment.class.php');
|
dol_include_once('/kundenkarte/class/equipment.class.php');
|
||||||
|
dol_include_once('/kundenkarte/class/anlageaccessory.class.php');
|
||||||
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
||||||
|
|
||||||
// Load translation files
|
// Load translation files
|
||||||
|
|
@ -178,6 +179,7 @@ if ($action == 'add' && $permissiontoadd) {
|
||||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||||
$anlage->fk_system = $systemId;
|
$anlage->fk_system = $systemId;
|
||||||
|
$anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null;
|
||||||
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
||||||
$anlage->status = 1;
|
$anlage->status = 1;
|
||||||
|
|
||||||
|
|
@ -220,6 +222,7 @@ if ($action == 'update' && $permissiontoadd) {
|
||||||
$anlage->label = GETPOST('label', 'alphanohtml');
|
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||||
|
$anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null;
|
||||||
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
||||||
|
|
||||||
// Get type - but keep current system for GLOBAL types (buildings)
|
// Get type - but keep current system for GLOBAL types (buildings)
|
||||||
|
|
@ -505,6 +508,10 @@ if ($isTreeView) {
|
||||||
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
|
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
|
||||||
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
|
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
$showDecomm = getDolGlobalInt('KUNDENKARTE_SHOW_DECOMMISSIONED', 0);
|
||||||
|
print '<button type="button" class="button small'.($showDecomm ? ' active' : '').'" id="btn-toggle-decommissioned" title="'.$langs->trans('ShowDecommissioned').'">';
|
||||||
|
print '<i class="fa '.($showDecomm ? 'fa-eye' : 'fa-eye-slash').'"></i> <span>'.$langs->trans('Decommissioned').'</span>';
|
||||||
|
print '</button>';
|
||||||
if ($systemId > 0) {
|
if ($systemId > 0) {
|
||||||
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$object->socid.'&contactid='.$id.'&system='.$systemId;
|
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$object->socid.'&contactid='.$id.'&system='.$systemId;
|
||||||
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
|
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
|
||||||
|
|
@ -585,13 +592,28 @@ if (empty($customerSystems)) {
|
||||||
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
|
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
|
||||||
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
|
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
|
||||||
|
|
||||||
|
// Zugeordnetes Produkt (nur wenn Typ es erlaubt)
|
||||||
|
if ($type->has_product && $anlage->fk_product > 0) {
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
$product = new Product($db);
|
||||||
|
if ($product->fetch($anlage->fk_product) > 0) {
|
||||||
|
print '<tr><td>'.$langs->trans('Product').'</td>';
|
||||||
|
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$product->id.'">'.dol_escape_htmltag($product->ref).'</a>';
|
||||||
|
print ' - '.dol_escape_htmltag($product->label);
|
||||||
|
if ($product->price > 0) {
|
||||||
|
print ' <span class="opacitymedium">('.price($product->price).' €)</span>';
|
||||||
|
}
|
||||||
|
print '</td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamic fields - all fields come from type definition
|
// Dynamic fields - all fields come from type definition
|
||||||
$fieldValues = $anlage->getFieldValues();
|
$fieldValues = $anlage->getFieldValues();
|
||||||
$typeFieldsList = $type->fetchFields();
|
$typeFieldsList = $type->fetchFields();
|
||||||
foreach ($typeFieldsList as $field) {
|
foreach ($typeFieldsList as $field) {
|
||||||
if ($field->field_type === 'header') {
|
if ($field->field_type === 'header') {
|
||||||
// Section header
|
// Section header
|
||||||
print '<tr class="liste_titre"><th colspan="2" style="background:#f0f0f0;padding:8px;">'.dol_escape_htmltag($field->field_label).'</th></tr>';
|
print '<tr class="liste_titre"><th colspan="2" style="padding:8px;">'.dol_escape_htmltag($field->field_label).'</th></tr>';
|
||||||
} else {
|
} else {
|
||||||
$value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : '';
|
$value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : '';
|
||||||
if ($value !== '') {
|
if ($value !== '') {
|
||||||
|
|
@ -625,6 +647,16 @@ if (empty($customerSystems)) {
|
||||||
print '<td>'.dol_print_date($anlage->tms, 'dayhour').'</td></tr>';
|
print '<td>'.dol_print_date($anlage->tms, 'dayhour').'</td></tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ausgebaut-Status
|
||||||
|
if (!empty($anlage->decommissioned)) {
|
||||||
|
print '<tr><td>'.$langs->trans('Decommissioned').'</td>';
|
||||||
|
print '<td><span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$langs->trans('Decommissioned').'</span>';
|
||||||
|
if (!empty($anlage->date_decommissioned)) {
|
||||||
|
print ' '.dol_print_date(strtotime($anlage->date_decommissioned), 'day');
|
||||||
|
}
|
||||||
|
print '</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
print '</table>';
|
print '</table>';
|
||||||
|
|
||||||
// Files section
|
// Files section
|
||||||
|
|
@ -747,6 +779,9 @@ if (empty($customerSystems)) {
|
||||||
print '<button type="button" class="schematic-add-busbar" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#f39c12;cursor:pointer;" title="Phasenschiene hinzufügen">';
|
print '<button type="button" class="schematic-add-busbar" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#f39c12;cursor:pointer;" title="Phasenschiene hinzufügen">';
|
||||||
print '<i class="fa fa-arrows-h"></i> Phasenschiene';
|
print '<i class="fa fa-arrows-h"></i> Phasenschiene';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
print '<button type="button" class="schematic-straighten-connections" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#2ecc71;cursor:pointer;" title="Diagonale Leitungen begradigen (nur rechte Winkel)">';
|
||||||
|
print '<i class="fa fa-align-justify"></i> Begradigen';
|
||||||
|
print '</button>';
|
||||||
print '<button type="button" class="schematic-clear-connections" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#e74c3c;cursor:pointer;">';
|
print '<button type="button" class="schematic-clear-connections" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#e74c3c;cursor:pointer;">';
|
||||||
print '<i class="fa fa-trash"></i> Alle Verbindungen löschen';
|
print '<i class="fa fa-trash"></i> Alle Verbindungen löschen';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
|
@ -758,11 +793,20 @@ if (empty($customerSystems)) {
|
||||||
print '<button type="button" class="schematic-audit-log" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#95a5a6;cursor:pointer;" title="Änderungsprotokoll anzeigen">';
|
print '<button type="button" class="schematic-audit-log" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#95a5a6;cursor:pointer;" title="Änderungsprotokoll anzeigen">';
|
||||||
print '<i class="fa fa-history"></i> Protokoll';
|
print '<i class="fa fa-history"></i> Protokoll';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
// Display Settings button
|
||||||
|
print '<button type="button" class="schematic-settings-btn" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;" title="Anzeigeeinstellungen (Leitungsfarben, -stärken, Terminal-Stil)">';
|
||||||
|
print '<i class="fa fa-cog"></i> Anzeige';
|
||||||
|
print '</button>';
|
||||||
// PDF Export button
|
// PDF Export button
|
||||||
$pdfExportUrl = dol_buildpath('/kundenkarte/ajax/export_schematic_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A4&orientation=L';
|
$pdfExportUrl = dol_buildpath('/kundenkarte/ajax/export_schematic_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A4&orientation=L';
|
||||||
print '<a href="'.$pdfExportUrl.'" target="_blank" class="schematic-export-pdf" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:5px;" title="PDF Export (Leitungslaufplan nach DIN EN 61082)">';
|
print '<a href="'.$pdfExportUrl.'" target="_blank" class="schematic-export-pdf" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:5px;" title="PDF Export (Leitungslaufplan nach DIN EN 61082)">';
|
||||||
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
|
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
|
||||||
print '</a>';
|
print '</a>';
|
||||||
|
// Leitungslaufplan PDF-Export (separates Feature)
|
||||||
|
$wiringUrl = dol_buildpath('/kundenkarte/ajax/export_wiring_diagram_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A3&orientation=L';
|
||||||
|
print '<a href="'.$wiringUrl.'" target="_blank" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#27ae60;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:5px;" title="Leitungslaufplan (DIN EN 61082)">';
|
||||||
|
print '<i class="fa fa-sitemap"></i> Leitungslaufplan';
|
||||||
|
print '</a>';
|
||||||
print '</div>';
|
print '</div>';
|
||||||
print '</div>';
|
print '</div>';
|
||||||
print '<div class="schematic-message info">Bereit</div>';
|
print '<div class="schematic-message info">Bereit</div>';
|
||||||
|
|
@ -782,6 +826,73 @@ if (empty($customerSystems)) {
|
||||||
</script>';
|
</script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zubehör-Bereich (nur wenn Typ has_accessories hat)
|
||||||
|
if (!empty($type->has_accessories)) {
|
||||||
|
$accessoryObj = new AnlageAccessory($db);
|
||||||
|
$accessories = $accessoryObj->fetchAllByAnlage($anlageId);
|
||||||
|
|
||||||
|
print '<br><h4><i class="fa fa-puzzle-piece"></i> '.$langs->trans('Accessories').'</h4>';
|
||||||
|
|
||||||
|
if (!empty($accessories)) {
|
||||||
|
print '<div class="div-table-responsive">';
|
||||||
|
print '<table class="tagtable liste">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th>'.$langs->trans('ProductRef').'</th>';
|
||||||
|
print '<th>'.$langs->trans('Label').'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans('Qty').'</th>';
|
||||||
|
print '<th>'.$langs->trans('Note').'</th>';
|
||||||
|
if ($permissiontodelete) {
|
||||||
|
print '<th class="right">'.$langs->trans('Action').'</th>';
|
||||||
|
}
|
||||||
|
print '</tr>';
|
||||||
|
foreach ($accessories as $acc) {
|
||||||
|
print '<tr>';
|
||||||
|
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$acc->fk_product.'">'.dol_escape_htmltag($acc->product_ref).'</a></td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($acc->product_label).'</td>';
|
||||||
|
print '<td class="right">'.$acc->qty.'</td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($acc->note).'</td>';
|
||||||
|
if ($permissiontodelete) {
|
||||||
|
print '<td class="right"><a href="#" class="btn-delete-accessory" data-id="'.$acc->id.'" title="'.$langs->trans('Delete').'"><i class="fa fa-trash"></i></a></td>';
|
||||||
|
}
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
print '</table>';
|
||||||
|
print '</div>';
|
||||||
|
} else {
|
||||||
|
print '<p class="opacitymedium">'.$langs->trans('NoAccessories').'</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
print '<div id="add-accessory-form" style="margin-top:10px;">';
|
||||||
|
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
|
||||||
|
print '<input type="text" id="accessory_product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'...">';
|
||||||
|
print '<input type="hidden" id="accessory_product_id" value="">';
|
||||||
|
print '<input type="number" id="accessory_qty" class="flat" value="1" min="1" style="width:80px;">';
|
||||||
|
print '<button type="button" id="btn-add-accessory" class="button small" disabled>'.$langs->trans('Add').'</button>';
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($permissiontoadd && !empty($accessories)) {
|
||||||
|
print '<div style="margin-top:15px;padding:10px;border:1px solid #444;border-radius:4px;">';
|
||||||
|
print '<h5 style="margin:0 0 10px 0;"><i class="fa fa-shopping-cart"></i> '.$langs->trans('OrderAccessories').'</h5>';
|
||||||
|
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
|
||||||
|
print '<select id="supplier_select" class="flat minwidth200">';
|
||||||
|
print '<option value="">'.$langs->trans('SelectSupplier').'</option>';
|
||||||
|
$sqlSupp = "SELECT s.rowid, s.nom FROM ".MAIN_DB_PREFIX."societe s WHERE s.fournisseur = 1 AND s.status = 1 ORDER BY s.nom";
|
||||||
|
$resSupp = $db->query($sqlSupp);
|
||||||
|
if ($resSupp) {
|
||||||
|
while ($objSupp = $db->fetch_object($resSupp)) {
|
||||||
|
print '<option value="'.$objSupp->rowid.'">'.dol_escape_htmltag($objSupp->nom).'</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '</select>';
|
||||||
|
print '<button type="button" id="btn-order-accessories" class="button small">'.$langs->trans('CreateSupplierOrder').'</button>';
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
print '<div class="tabsAction">';
|
print '<div class="tabsAction">';
|
||||||
if ($permissiontoadd) {
|
if ($permissiontoadd) {
|
||||||
|
|
@ -881,7 +992,7 @@ if (empty($customerSystems)) {
|
||||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||||
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||||
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||||
print '<option value="'.$t->id.'" data-category="building" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
print '<option value="'.$t->id.'" data-category="building" data-icon="'.$picto.'" data-color="'.$color.'" data-has-product="'.($t->has_product ? '1' : '0').'" data-has-accessories="'.($t->has_accessories ? '1' : '0').'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||||
}
|
}
|
||||||
if ($lastGroup !== '') print '</optgroup>';
|
if ($lastGroup !== '') print '</optgroup>';
|
||||||
}
|
}
|
||||||
|
|
@ -893,7 +1004,7 @@ if (empty($customerSystems)) {
|
||||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||||
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||||
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||||
print '<option value="'.$t->id.'" data-category="element" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
print '<option value="'.$t->id.'" data-category="element" data-icon="'.$picto.'" data-color="'.$color.'" data-has-product="'.($t->has_product ? '1' : '0').'" data-has-accessories="'.($t->has_accessories ? '1' : '0').'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||||
}
|
}
|
||||||
print '</optgroup>';
|
print '</optgroup>';
|
||||||
}
|
}
|
||||||
|
|
@ -914,6 +1025,23 @@ if (empty($customerSystems)) {
|
||||||
printTreeOptions($tree, $selectedParent, $excludeId);
|
printTreeOptions($tree, $selectedParent, $excludeId);
|
||||||
print '</select></td></tr>';
|
print '</select></td></tr>';
|
||||||
|
|
||||||
|
// Produkt-Zuordnung (wird per JS ein-/ausgeblendet je nach Typ)
|
||||||
|
$productValue = '';
|
||||||
|
$productId = 0;
|
||||||
|
if (($isEdit || $isCopy) && $anlage->fk_product > 0) {
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
$product = new Product($db);
|
||||||
|
if ($product->fetch($anlage->fk_product) > 0) {
|
||||||
|
$productValue = $product->ref.' - '.$product->label;
|
||||||
|
$productId = $product->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '<tr id="row_product" style="display:none;"><td>'.$langs->trans('Product').'</td>';
|
||||||
|
print '<td>';
|
||||||
|
print '<input type="text" id="product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'..." value="'.dol_escape_htmltag($productValue).'">';
|
||||||
|
print '<input type="hidden" name="fk_product" id="fk_product" value="'.$productId.'">';
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
// Dynamic fields will be inserted here via JavaScript
|
// Dynamic fields will be inserted here via JavaScript
|
||||||
print '<tbody id="dynamic_fields"></tbody>';
|
print '<tbody id="dynamic_fields"></tbody>';
|
||||||
|
|
||||||
|
|
@ -1003,15 +1131,15 @@ if (empty($customerSystems)) {
|
||||||
$typeSelect.prop("disabled", false);
|
$typeSelect.prop("disabled", false);
|
||||||
$("#row_type").show();
|
$("#row_type").show();
|
||||||
|
|
||||||
// Wert wiederherstellen falls noch vorhanden
|
|
||||||
if (currentVal && $typeSelect.find("option[value=\"" + currentVal + "\"]").length) {
|
|
||||||
$typeSelect.val(currentVal);
|
|
||||||
} else {
|
|
||||||
$typeSelect.val("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select2 neu initialisieren
|
// Select2 neu initialisieren
|
||||||
initSelect2();
|
initSelect2();
|
||||||
|
|
||||||
|
// Wert wiederherstellen falls noch vorhanden (nach Select2-Init mit trigger)
|
||||||
|
if (currentVal && $typeSelect.find("option[value=\"" + currentVal + "\"]").length) {
|
||||||
|
$typeSelect.val(currentVal).trigger("change");
|
||||||
|
} else {
|
||||||
|
$typeSelect.val("").trigger("change");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$catSelect.on("change", function() {
|
$catSelect.on("change", function() {
|
||||||
|
|
@ -1020,9 +1148,24 @@ if (empty($customerSystems)) {
|
||||||
$typeSelect.trigger("change");
|
$typeSelect.trigger("change");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Produkt-Zeile ein-/ausblenden je nach Typ-Flag has_product
|
||||||
|
function updateProductRow() {
|
||||||
|
var $selected = $typeSelect.find("option:selected");
|
||||||
|
var hasProduct = $selected.data("has-product");
|
||||||
|
if (hasProduct == 1) {
|
||||||
|
$("#row_product").show();
|
||||||
|
} else {
|
||||||
|
$("#row_product").hide();
|
||||||
|
$("#fk_product").val("");
|
||||||
|
$("#product_search").val("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$typeSelect.on("change", updateProductRow);
|
||||||
|
|
||||||
// Initial filtern
|
// Initial filtern
|
||||||
if ($catSelect.val()) {
|
if ($catSelect.val()) {
|
||||||
filterTypes();
|
filterTypes();
|
||||||
|
updateProductRow();
|
||||||
} else {
|
} else {
|
||||||
$typeSelect.prop("disabled", true);
|
$typeSelect.prop("disabled", true);
|
||||||
$("#row_type").hide();
|
$("#row_type").hide();
|
||||||
|
|
@ -1090,7 +1233,7 @@ if (empty($customerSystems)) {
|
||||||
$connectionsByTarget = array();
|
$connectionsByTarget = array();
|
||||||
|
|
||||||
if (!empty($tree)) {
|
if (!empty($tree)) {
|
||||||
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$object->socid.'">';
|
print '<div class="kundenkarte-tree'.($showDecomm ? ' show-decommissioned' : '').'" data-system="'.$systemId.'" data-socid="'.$object->socid.'">';
|
||||||
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
|
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
|
||||||
print '</div>';
|
print '</div>';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1107,6 +1250,163 @@ print dol_get_fiche_end();
|
||||||
// Tooltip container
|
// Tooltip container
|
||||||
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
|
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
|
||||||
|
|
||||||
|
// Produkt-Autocomplete + Zubehör-AJAX (nur wenn Formular oder Detailansicht aktiv)
|
||||||
|
if (in_array($action, array('create', 'edit', 'copy', 'view'))) {
|
||||||
|
print '<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
var baseUrl = "'.dol_escape_js(dol_buildpath('/kundenkarte', 1)).'";
|
||||||
|
|
||||||
|
function initProductAutocomplete(inputSelector, hiddenSelector) {
|
||||||
|
var $input = $(inputSelector);
|
||||||
|
var $hidden = $(hiddenSelector);
|
||||||
|
if (!$input.length) return;
|
||||||
|
|
||||||
|
var searchTimeout;
|
||||||
|
$input.on("input", function() {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
var term = $(this).val();
|
||||||
|
if (term.length < 2) {
|
||||||
|
$hidden.val("");
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
searchTimeout = setTimeout(function() {
|
||||||
|
$.get(baseUrl + "/ajax/equipment.php", {
|
||||||
|
action: "get_products",
|
||||||
|
term: term,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}, function(data) {
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
if (data.success && data.products && data.products.length > 0) {
|
||||||
|
var $dropdown = $("<div class=\"product-autocomplete-dropdown\"></div>");
|
||||||
|
$.each(data.products, function(i, p) {
|
||||||
|
var label = p.ref + " - " + p.label;
|
||||||
|
if (p.price > 0) label += " (" + p.price + " \u20ac)";
|
||||||
|
$dropdown.append(
|
||||||
|
$("<div class=\"product-autocomplete-item\"></div>")
|
||||||
|
.text(label)
|
||||||
|
.data("id", p.id)
|
||||||
|
.data("ref", p.ref)
|
||||||
|
.data("label", p.label)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
$input.after($dropdown);
|
||||||
|
$dropdown.on("click", ".product-autocomplete-item", function() {
|
||||||
|
$input.val($(this).data("ref") + " - " + $(this).data("label"));
|
||||||
|
$hidden.val($(this).data("id"));
|
||||||
|
$dropdown.remove();
|
||||||
|
if (inputSelector === "#accessory_product_search") {
|
||||||
|
$("#btn-add-accessory").prop("disabled", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("click", function(e) {
|
||||||
|
if (!$(e.target).closest(inputSelector + ", .product-autocomplete-dropdown").length) {
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on("change", function() {
|
||||||
|
if (!$(this).val()) {
|
||||||
|
$hidden.val("");
|
||||||
|
if (inputSelector === "#accessory_product_search") {
|
||||||
|
$("#btn-add-accessory").prop("disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initProductAutocomplete("#product_search", "#fk_product");
|
||||||
|
initProductAutocomplete("#accessory_product_search", "#accessory_product_id");
|
||||||
|
|
||||||
|
$("#btn-add-accessory").on("click", function() {
|
||||||
|
var productId = $("#accessory_product_id").val();
|
||||||
|
var qty = $("#accessory_qty").val() || 1;
|
||||||
|
var anlageId = '.((int) $anlageId).';
|
||||||
|
if (!productId || !anlageId) return;
|
||||||
|
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "add",
|
||||||
|
fk_anlage: anlageId,
|
||||||
|
fk_product: productId,
|
||||||
|
qty: qty,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success) { location.reload(); }
|
||||||
|
else { alert(data.error || "Fehler"); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".btn-delete-accessory").on("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!confirm("'.$langs->trans('ConfirmDeleteAccessory').'")) return;
|
||||||
|
var accId = $(this).data("id");
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "delete",
|
||||||
|
id: accId,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success) { location.reload(); }
|
||||||
|
else { alert(data.error || "Fehler"); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btn-order-accessories").on("click", function() {
|
||||||
|
var supplierId = $("#supplier_select").val();
|
||||||
|
var anlageId = '.((int) $anlageId).';
|
||||||
|
if (!supplierId) { alert("Bitte Lieferant auswählen"); return; }
|
||||||
|
|
||||||
|
var ids = [];
|
||||||
|
$(".btn-delete-accessory").each(function() { ids.push($(this).data("id")); });
|
||||||
|
if (ids.length === 0) return;
|
||||||
|
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "order",
|
||||||
|
fk_anlage: anlageId,
|
||||||
|
supplier_id: supplierId,
|
||||||
|
"ids[]": ids,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success && data.order_id) {
|
||||||
|
window.location.href = "'.DOL_URL_ROOT.'/fourn/commande/card.php?id=" + data.order_id;
|
||||||
|
} else {
|
||||||
|
alert(data.error || "Fehler beim Erstellen der Bestellung");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>';
|
||||||
|
|
||||||
|
print '<style>
|
||||||
|
.product-autocomplete-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
background: var(--colorbackbody, #fff);
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 300px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
.product-autocomplete-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
.product-autocomplete-item:hover {
|
||||||
|
background: var(--colorbacklinepairhover, #333);
|
||||||
|
}
|
||||||
|
.product-autocomplete-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
</style>';
|
||||||
|
}
|
||||||
|
|
||||||
llxFooter();
|
llxFooter();
|
||||||
$db->close();
|
$db->close();
|
||||||
|
|
||||||
|
|
@ -1225,6 +1525,9 @@ function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs,
|
||||||
if (!$hasConnection && $level > 0) {
|
if (!$hasConnection && $level > 0) {
|
||||||
$nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel
|
$nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel
|
||||||
}
|
}
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$nodeClass .= ' decommissioned';
|
||||||
|
}
|
||||||
if ($node->type_can_have_equipment) {
|
if ($node->type_can_have_equipment) {
|
||||||
$nodeClass .= ' node-equipment'; // Geräte-Container (Schaltschrank, Verteiler)
|
$nodeClass .= ' node-equipment'; // Geräte-Container (Schaltschrank, Verteiler)
|
||||||
} elseif ($node->type_can_have_children) {
|
} elseif ($node->type_can_have_children) {
|
||||||
|
|
@ -1260,6 +1563,14 @@ function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs,
|
||||||
}
|
}
|
||||||
print '</span>';
|
print '</span>';
|
||||||
|
|
||||||
|
// Ausgebaut-Badge mit Datum
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$decommDate = !empty($node->date_decommissioned) ? dol_print_date(strtotime($node->date_decommissioned), 'day') : '';
|
||||||
|
$decommText = $langs->trans('Decommissioned');
|
||||||
|
if ($decommDate) $decommText .= ' '.$decommDate;
|
||||||
|
print ' <span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$decommText.'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
// Spacer to push badges to the right
|
// Spacer to push badges to the right
|
||||||
print '<span class="kundenkarte-tree-spacer"></span>';
|
print '<span class="kundenkarte-tree-spacer"></span>';
|
||||||
|
|
||||||
|
|
@ -1302,6 +1613,9 @@ function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs,
|
||||||
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
|
$decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission');
|
||||||
|
$decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off';
|
||||||
|
print '<a href="#" class="btn-toggle-decommissioned" data-anlage-id="'.$node->id.'" title="'.$decommLabel.'"><i class="fa '.$decommIcon.'"></i></a>';
|
||||||
}
|
}
|
||||||
if ($canDelete) {
|
if ($canDelete) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
||||||
|
|
@ -1503,6 +1817,9 @@ function printTreeWithCableLines($nodes, $contactid, $systemId, $canEdit, $canDe
|
||||||
if (!$hasConnection && $level > 0) {
|
if (!$hasConnection && $level > 0) {
|
||||||
$nodeClass .= ' no-cable';
|
$nodeClass .= ' no-cable';
|
||||||
}
|
}
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$nodeClass .= ' decommissioned';
|
||||||
|
}
|
||||||
|
|
||||||
print '<div class="'.$nodeClass.'">';
|
print '<div class="'.$nodeClass.'">';
|
||||||
|
|
||||||
|
|
@ -1559,6 +1876,14 @@ function printTreeWithCableLines($nodes, $contactid, $systemId, $canEdit, $canDe
|
||||||
}
|
}
|
||||||
print '</span>';
|
print '</span>';
|
||||||
|
|
||||||
|
// Ausgebaut-Badge mit Datum
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$decommDate = !empty($node->date_decommissioned) ? dol_print_date(strtotime($node->date_decommissioned), 'day') : '';
|
||||||
|
$decommText = $langs->trans('Decommissioned');
|
||||||
|
if ($decommDate) $decommText .= ' '.$decommDate;
|
||||||
|
print ' <span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$decommText.'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
// Spacer to push badges to the right
|
// Spacer to push badges to the right
|
||||||
print '<span class="kundenkarte-tree-spacer"></span>';
|
print '<span class="kundenkarte-tree-spacer"></span>';
|
||||||
|
|
||||||
|
|
@ -1598,6 +1923,9 @@ function printTreeWithCableLines($nodes, $contactid, $systemId, $canEdit, $canDe
|
||||||
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
|
$decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission');
|
||||||
|
$decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off';
|
||||||
|
print '<a href="#" class="btn-toggle-decommissioned" data-anlage-id="'.$node->id.'" title="'.$decommLabel.'"><i class="fa '.$decommIcon.'"></i></a>';
|
||||||
}
|
}
|
||||||
if ($canDelete) {
|
if ($canDelete) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
||||||
|
|
|
||||||
982
werkzeuge.php
Executable file
982
werkzeuge.php
Executable file
|
|
@ -0,0 +1,982 @@
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Alles Watt lauft
|
||||||
|
*
|
||||||
|
* Mein Betrieb: Baumansicht für eigene Maschinen, Werkzeuge und Geräte
|
||||||
|
* Multi-System mit System-Tabs (wie Kunden-Anlagen)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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("Include of main fails");
|
||||||
|
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||||
|
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||||
|
dol_include_once('/kundenkarte/class/anlagetype.class.php');
|
||||||
|
dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||||
|
dol_include_once('/kundenkarte/class/anlageaccessory.class.php');
|
||||||
|
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
||||||
|
|
||||||
|
// Übersetzungen
|
||||||
|
$langs->loadLangs(array('companies', 'kundenkarte@kundenkarte'));
|
||||||
|
|
||||||
|
// Berechtigungen
|
||||||
|
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||||
|
accessforbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$permissiontoread = $user->hasRight('kundenkarte', 'read');
|
||||||
|
$permissiontoadd = $user->hasRight('kundenkarte', 'write');
|
||||||
|
$permissiontodelete = $user->hasRight('kundenkarte', 'delete');
|
||||||
|
|
||||||
|
$action = GETPOST('action', 'aZ09');
|
||||||
|
$confirm = GETPOST('confirm', 'alpha');
|
||||||
|
$systemId = GETPOSTINT('system');
|
||||||
|
$anlageId = GETPOSTINT('anlage_id');
|
||||||
|
$parentId = GETPOSTINT('parent_id');
|
||||||
|
|
||||||
|
// Virtuelle Firma-ID für "Mein Betrieb" - braucht keinen echten Societe-Eintrag
|
||||||
|
// Die Anlage-Tabelle verwendet diese ID nur als Gruppierung
|
||||||
|
$socId = 99999999;
|
||||||
|
|
||||||
|
// ALLE verfügbaren Systeme laden
|
||||||
|
$allSystems = array();
|
||||||
|
$sql = "SELECT rowid, code, label, picto, color FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE active = 1 ORDER BY position ASC";
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
$allSystems[$obj->rowid] = $obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Für diesen virtuellen Betrieb aktivierte Systeme laden
|
||||||
|
$customerSystems = array();
|
||||||
|
$sql = "SELECT ss.rowid, ss.fk_system, s.code, s.label, s.picto, s.color";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system ss";
|
||||||
|
$sql .= " JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system s ON s.rowid = ss.fk_system";
|
||||||
|
$sql .= " WHERE ss.fk_soc = ".((int) $socId)." AND (ss.fk_contact IS NULL OR ss.fk_contact = 0) AND ss.active = 1 AND s.active = 1";
|
||||||
|
$sql .= " AND s.code != 'GLOBAL'";
|
||||||
|
$sql .= " ORDER BY s.position ASC";
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
$customerSystems[$obj->fk_system] = $obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard: Erstes aktiviertes System falls nicht angegeben
|
||||||
|
if (empty($systemId) && !empty($customerSystems)) {
|
||||||
|
$systemId = array_key_first($customerSystems);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Objekte initialisieren
|
||||||
|
$form = new Form($db);
|
||||||
|
$anlage = new Anlage($db);
|
||||||
|
$anlageType = new AnlageType($db);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Actions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// System hinzufügen
|
||||||
|
if ($action == 'add_system' && $permissiontoadd) {
|
||||||
|
$newSystemId = GETPOSTINT('new_system_id');
|
||||||
|
if ($newSystemId > 0 && !isset($customerSystems[$newSystemId])) {
|
||||||
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_societe_system";
|
||||||
|
$sql .= " (entity, fk_soc, fk_contact, fk_system, date_creation, fk_user_creat, active)";
|
||||||
|
$sql .= " VALUES (".$conf->entity.", ".((int) $socId).", 0, ".((int) $newSystemId).", NOW(), ".((int) $user->id).", 1)";
|
||||||
|
$result = $db->query($sql);
|
||||||
|
if ($result) {
|
||||||
|
setEventMessages($langs->trans('SystemAdded'), null, 'mesgs');
|
||||||
|
header('Location: '.$_SERVER['PHP_SELF'].'?system='.$newSystemId);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
setEventMessages($db->lasterror(), null, 'errors');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$action = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// System entfernen
|
||||||
|
if ($action == 'confirm_remove_system' && $confirm == 'yes' && $permissiontodelete) {
|
||||||
|
$removeSystemId = GETPOSTINT('remove_system_id');
|
||||||
|
if ($removeSystemId > 0) {
|
||||||
|
// Prüfen ob System noch Elemente hat
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_anlage WHERE fk_soc = ".((int) $socId)." AND (fk_contact IS NULL OR fk_contact = 0) AND fk_system = ".((int) $removeSystemId);
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
$obj = $db->fetch_object($resql);
|
||||||
|
|
||||||
|
if ($obj->cnt > 0) {
|
||||||
|
setEventMessages($langs->trans('ErrorSystemHasElements'), null, 'errors');
|
||||||
|
} else {
|
||||||
|
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system WHERE fk_soc = ".((int) $socId)." AND (fk_contact IS NULL OR fk_contact = 0) AND fk_system = ".((int) $removeSystemId);
|
||||||
|
$db->query($sql);
|
||||||
|
setEventMessages($langs->trans('SystemRemoved'), null, 'mesgs');
|
||||||
|
|
||||||
|
unset($customerSystems[$removeSystemId]);
|
||||||
|
if (!empty($customerSystems)) {
|
||||||
|
$systemId = array_key_first($customerSystems);
|
||||||
|
} else {
|
||||||
|
$systemId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header('Location: '.$_SERVER['PHP_SELF'].($systemId ? '?system='.$systemId : ''));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action == 'add' && $permissiontoadd) {
|
||||||
|
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||||
|
$anlage->fk_soc = $socId;
|
||||||
|
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||||
|
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||||
|
$anlage->fk_system = $systemId;
|
||||||
|
$anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null;
|
||||||
|
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
||||||
|
$anlage->status = 1;
|
||||||
|
|
||||||
|
// Dynamische Felder
|
||||||
|
$type = new AnlageType($db);
|
||||||
|
if ($type->fetch($anlage->fk_anlage_type) > 0) {
|
||||||
|
$fieldValues = array();
|
||||||
|
$fields = $type->fetchFields();
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if ($field->field_type === 'header') continue;
|
||||||
|
$value = GETPOST('field_'.$field->field_code, 'alphanohtml');
|
||||||
|
if ($value !== '') {
|
||||||
|
$fieldValues[$field->field_code] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$anlage->setFieldValues($fieldValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $anlage->create($user);
|
||||||
|
if ($result > 0) {
|
||||||
|
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||||
|
header('Location: '.$_SERVER['PHP_SELF'].'?system='.$systemId);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||||
|
$action = 'create';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action == 'update' && $permissiontoadd) {
|
||||||
|
$anlage->fetch($anlageId);
|
||||||
|
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||||
|
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||||
|
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||||
|
$anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null;
|
||||||
|
$anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : '';
|
||||||
|
|
||||||
|
// Dynamische Felder
|
||||||
|
$type = new AnlageType($db);
|
||||||
|
if ($type->fetch($anlage->fk_anlage_type) > 0) {
|
||||||
|
$fieldValues = array();
|
||||||
|
$fields = $type->fetchFields();
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if ($field->field_type === 'header') continue;
|
||||||
|
$value = GETPOST('field_'.$field->field_code, 'alphanohtml');
|
||||||
|
if ($value !== '') {
|
||||||
|
$fieldValues[$field->field_code] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$anlage->setFieldValues($fieldValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $anlage->update($user);
|
||||||
|
if ($result > 0) {
|
||||||
|
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||||
|
header('Location: '.$_SERVER['PHP_SELF'].'?system='.$systemId);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||||
|
$action = 'edit';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) {
|
||||||
|
$anlage->fetch($anlageId);
|
||||||
|
$result = $anlage->delete($user);
|
||||||
|
if ($result > 0) {
|
||||||
|
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||||
|
} else {
|
||||||
|
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||||
|
}
|
||||||
|
header('Location: '.$_SERVER['PHP_SELF']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* View
|
||||||
|
*/
|
||||||
|
|
||||||
|
$title = $langs->trans('CompanyTools');
|
||||||
|
$jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time());
|
||||||
|
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
|
||||||
|
|
||||||
|
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
|
||||||
|
|
||||||
|
print load_fiche_titre($title, '', 'fa-wrench');
|
||||||
|
|
||||||
|
print '<div class="fichecenter">';
|
||||||
|
|
||||||
|
// Bestätigungsdialoge
|
||||||
|
if ($action == 'delete') {
|
||||||
|
print $form->formconfirm(
|
||||||
|
$_SERVER['PHP_SELF'].'?system='.$systemId.'&anlage_id='.$anlageId,
|
||||||
|
$langs->trans('DeleteElement'),
|
||||||
|
$langs->trans('ConfirmDeleteElement'),
|
||||||
|
'confirm_delete',
|
||||||
|
'',
|
||||||
|
'yes',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action == 'remove_system') {
|
||||||
|
$removeSystemId = GETPOSTINT('remove_system_id');
|
||||||
|
$sysLabel = isset($customerSystems[$removeSystemId]) ? $customerSystems[$removeSystemId]->label : '';
|
||||||
|
print $form->formconfirm(
|
||||||
|
$_SERVER['PHP_SELF'].'?remove_system_id='.$removeSystemId,
|
||||||
|
$langs->trans('RemoveSystem'),
|
||||||
|
$langs->trans('ConfirmRemoveSystem', $sysLabel),
|
||||||
|
'confirm_remove_system',
|
||||||
|
'',
|
||||||
|
'yes',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// System-Tabs
|
||||||
|
print '<div class="kundenkarte-system-tabs-wrapper">';
|
||||||
|
print '<div class="kundenkarte-system-tabs">';
|
||||||
|
foreach ($customerSystems as $sysId => $sys) {
|
||||||
|
$activeClass = ($sysId == $systemId) ? ' active' : '';
|
||||||
|
print '<div class="kundenkarte-system-tab'.$activeClass.'" data-system="'.$sysId.'">';
|
||||||
|
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$sysId.'" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:8px;">';
|
||||||
|
if ($sys->picto) {
|
||||||
|
print '<span class="kundenkarte-system-tab-icon" style="color:'.$sys->color.';">'.kundenkarte_render_icon($sys->picto).'</span>';
|
||||||
|
}
|
||||||
|
print '<span>'.dol_escape_htmltag($sys->label).'</span>';
|
||||||
|
print '</a>';
|
||||||
|
// Entfernen-Button (nur beim aktiven Tab)
|
||||||
|
if ($permissiontodelete && $sysId == $systemId) {
|
||||||
|
print ' <a href="'.$_SERVER['PHP_SELF'].'?action=remove_system&remove_system_id='.$sysId.'" class="kundenkarte-system-remove" title="'.$langs->trans('RemoveSystem').'"><i class="fa fa-times"></i></a>';
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// System hinzufügen Button
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
$availableSystems = array_diff_key($allSystems, $customerSystems);
|
||||||
|
// GLOBAL ausschließen
|
||||||
|
foreach ($availableSystems as $k => $v) {
|
||||||
|
if ($v->code === 'GLOBAL') unset($availableSystems[$k]);
|
||||||
|
}
|
||||||
|
if (!empty($availableSystems)) {
|
||||||
|
print '<button type="button" class="button small kundenkarte-add-system-btn" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">';
|
||||||
|
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
|
||||||
|
print '</button>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
// Steuerungs-Buttons (nur in Baumansicht)
|
||||||
|
$isTreeView = !in_array($action, array('create', 'edit', 'view'));
|
||||||
|
if ($isTreeView && $systemId > 0) {
|
||||||
|
print '<div class="kundenkarte-tree-controls">';
|
||||||
|
print '<button type="button" class="kundenkarte-view-toggle" id="btn-compact-mode" title="Kompakte Ansicht">';
|
||||||
|
print '<i class="fa fa-compress"></i> <span>Kompakt</span>';
|
||||||
|
print '</button>';
|
||||||
|
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">';
|
||||||
|
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll');
|
||||||
|
print '</button>';
|
||||||
|
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
|
||||||
|
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
|
||||||
|
print '</button>';
|
||||||
|
$showDecomm = getDolGlobalInt('KUNDENKARTE_SHOW_DECOMMISSIONED', 0);
|
||||||
|
print '<button type="button" class="button small'.($showDecomm ? ' active' : '').'" id="btn-toggle-decommissioned" title="'.$langs->trans('ShowDecommissioned').'">';
|
||||||
|
print '<i class="fa '.($showDecomm ? 'fa-eye' : 'fa-eye-slash').'"></i> <span>'.$langs->trans('Decommissioned').'</span>';
|
||||||
|
print '</button>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</div>'; // End system-tabs-wrapper
|
||||||
|
|
||||||
|
// System-Hinzufügen-Formular (versteckt)
|
||||||
|
if ($permissiontoadd && !empty($availableSystems)) {
|
||||||
|
print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;">';
|
||||||
|
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
|
||||||
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||||
|
print '<input type="hidden" name="action" value="add_system">';
|
||||||
|
print '<strong>'.$langs->trans('SelectSystemToAdd').':</strong> ';
|
||||||
|
print '<select name="new_system_id" class="flat">';
|
||||||
|
print '<option value="">'.$langs->trans('Select').'</option>';
|
||||||
|
foreach ($availableSystems as $avSys) {
|
||||||
|
print '<option value="'.$avSys->rowid.'">'.dol_escape_htmltag($avSys->label).'</option>';
|
||||||
|
}
|
||||||
|
print '</select>';
|
||||||
|
print ' <button type="submit" class="button small">'.$langs->trans('Add').'</button>';
|
||||||
|
print ' <button type="button" class="button small" onclick="document.getElementById(\'add-system-form\').style.display=\'none\';">'.$langs->trans('Cancel').'</button>';
|
||||||
|
print '</form>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Systeme konfiguriert sind
|
||||||
|
if (empty($customerSystems)) {
|
||||||
|
print '<div class="opacitymedium" style="padding:20px;text-align:center;">';
|
||||||
|
print '<i class="fa fa-info-circle" style="font-size:24px;margin-bottom:10px;"></i><br>';
|
||||||
|
print $langs->trans('NoSystemsConfigured').'<br><br>';
|
||||||
|
if ($permissiontoadd && !empty($allSystems)) {
|
||||||
|
print $langs->trans('ClickAddSystemToStart');
|
||||||
|
} else {
|
||||||
|
print $langs->trans('ContactAdminToAddSystems');
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
} elseif ($systemId > 0) {
|
||||||
|
|
||||||
|
// Typen für ausgewähltes System laden
|
||||||
|
$types = $anlageType->fetchAllBySystem($systemId, 1, 1);
|
||||||
|
|
||||||
|
if (in_array($action, array('create', 'edit', 'view'))) {
|
||||||
|
// Formular oder Detail-Ansicht
|
||||||
|
|
||||||
|
if ($action != 'create' && $anlageId > 0) {
|
||||||
|
$anlage->fetch($anlageId);
|
||||||
|
$type = new AnlageType($db);
|
||||||
|
$type->fetch($anlage->fk_anlage_type);
|
||||||
|
$type->fetchFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
print '<div class="kundenkarte-element-form">';
|
||||||
|
|
||||||
|
if ($action == 'view') {
|
||||||
|
// Detail-Ansicht
|
||||||
|
print '<h3>'.dol_escape_htmltag($anlage->label).'</h3>';
|
||||||
|
|
||||||
|
print '<table class="border centpercent">';
|
||||||
|
|
||||||
|
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
|
||||||
|
|
||||||
|
// Zugeordnetes Produkt (nur wenn Typ es erlaubt)
|
||||||
|
if ($type->has_product && $anlage->fk_product > 0) {
|
||||||
|
$product = new Product($db);
|
||||||
|
if ($product->fetch($anlage->fk_product) > 0) {
|
||||||
|
print '<tr><td>'.$langs->trans('Product').'</td>';
|
||||||
|
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$product->id.'">'.dol_escape_htmltag($product->ref).'</a>';
|
||||||
|
print ' - '.dol_escape_htmltag($product->label);
|
||||||
|
if ($product->price > 0) {
|
||||||
|
print ' <span class="opacitymedium">('.price($product->price).' €)</span>';
|
||||||
|
}
|
||||||
|
print '</td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamische Felder
|
||||||
|
$fieldValues = $anlage->getFieldValues();
|
||||||
|
$typeFieldsList = $type->fetchFields();
|
||||||
|
foreach ($typeFieldsList as $field) {
|
||||||
|
if ($field->field_type === 'header') {
|
||||||
|
print '<tr class="liste_titre"><th colspan="2" style="padding:8px;">'.dol_escape_htmltag($field->field_label).'</th></tr>';
|
||||||
|
} else {
|
||||||
|
$value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : '';
|
||||||
|
if ($value !== '') {
|
||||||
|
print '<tr><td>'.dol_escape_htmltag($field->field_label).'</td>';
|
||||||
|
if ($field->field_type === 'date' && $value) {
|
||||||
|
print '<td>'.dol_print_date(strtotime($value), 'day').'</td></tr>';
|
||||||
|
} elseif ($field->field_type === 'checkbox') {
|
||||||
|
print '<td>'.($value ? $langs->trans('Yes') : $langs->trans('No')).'</td></tr>';
|
||||||
|
} else {
|
||||||
|
print '<td>'.dol_escape_htmltag($value).'</td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($anlage->note_private) {
|
||||||
|
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
|
||||||
|
print '<td>'.dol_htmlentitiesbr($anlage->note_private).'</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ausgebaut-Status
|
||||||
|
if (!empty($anlage->decommissioned)) {
|
||||||
|
print '<tr><td>'.$langs->trans('Decommissioned').'</td>';
|
||||||
|
print '<td><span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$langs->trans('Decommissioned').'</span>';
|
||||||
|
if (!empty($anlage->date_decommissioned)) {
|
||||||
|
print ' '.dol_print_date(strtotime($anlage->date_decommissioned), 'day');
|
||||||
|
}
|
||||||
|
print '</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
|
||||||
|
// Zubehör-Bereich (nur wenn Typ has_accessories hat)
|
||||||
|
if ($type->has_accessories) {
|
||||||
|
$accessoryObj = new AnlageAccessory($db);
|
||||||
|
$accessories = $accessoryObj->fetchAllByAnlage($anlageId);
|
||||||
|
|
||||||
|
print '<br><h4><i class="fa fa-puzzle-piece"></i> '.$langs->trans('Accessories').'</h4>';
|
||||||
|
|
||||||
|
if (!empty($accessories)) {
|
||||||
|
print '<div class="div-table-responsive">';
|
||||||
|
print '<table class="tagtable liste">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th>'.$langs->trans('ProductRef').'</th>';
|
||||||
|
print '<th>'.$langs->trans('Label').'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans('Qty').'</th>';
|
||||||
|
print '<th>'.$langs->trans('Note').'</th>';
|
||||||
|
if ($permissiontodelete) {
|
||||||
|
print '<th class="right">'.$langs->trans('Action').'</th>';
|
||||||
|
}
|
||||||
|
print '</tr>';
|
||||||
|
foreach ($accessories as $acc) {
|
||||||
|
print '<tr>';
|
||||||
|
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$acc->fk_product.'">'.dol_escape_htmltag($acc->product_ref).'</a></td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($acc->product_label).'</td>';
|
||||||
|
print '<td class="right">'.$acc->qty.'</td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($acc->note).'</td>';
|
||||||
|
if ($permissiontodelete) {
|
||||||
|
print '<td class="right"><a href="#" class="btn-delete-accessory" data-id="'.$acc->id.'" title="'.$langs->trans('Delete').'"><i class="fa fa-trash"></i></a></td>';
|
||||||
|
}
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
print '</table>';
|
||||||
|
print '</div>';
|
||||||
|
} else {
|
||||||
|
print '<p class="opacitymedium">'.$langs->trans('NoAccessories').'</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zubehör hinzufügen
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
print '<div id="add-accessory-form" style="margin-top:10px;">';
|
||||||
|
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
|
||||||
|
print '<input type="text" id="accessory_product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'...">';
|
||||||
|
print '<input type="hidden" id="accessory_product_id" value="">';
|
||||||
|
print '<input type="number" id="accessory_qty" class="flat" value="1" min="1" style="width:80px;">';
|
||||||
|
print '<button type="button" id="btn-add-accessory" class="button small" disabled>'.$langs->trans('Add').'</button>';
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bestellfunktion
|
||||||
|
if ($permissiontoadd && !empty($accessories)) {
|
||||||
|
print '<div style="margin-top:15px;padding:10px;border:1px solid #444;border-radius:4px;">';
|
||||||
|
print '<h5 style="margin:0 0 10px 0;"><i class="fa fa-shopping-cart"></i> '.$langs->trans('OrderAccessories').'</h5>';
|
||||||
|
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
|
||||||
|
print '<select id="supplier_select" class="flat minwidth200">';
|
||||||
|
print '<option value="">'.$langs->trans('SelectSupplier').'</option>';
|
||||||
|
// Lieferanten laden
|
||||||
|
$sqlSupp = "SELECT s.rowid, s.nom FROM ".MAIN_DB_PREFIX."societe s WHERE s.fournisseur = 1 AND s.status = 1 ORDER BY s.nom";
|
||||||
|
$resSupp = $db->query($sqlSupp);
|
||||||
|
if ($resSupp) {
|
||||||
|
while ($objSupp = $db->fetch_object($resSupp)) {
|
||||||
|
print '<option value="'.$objSupp->rowid.'">'.dol_escape_htmltag($objSupp->nom).'</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '</select>';
|
||||||
|
print '<button type="button" id="btn-order-accessories" class="button small">'.$langs->trans('CreateSupplierOrder').'</button>';
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktions-Buttons
|
||||||
|
print '<div class="tabsAction">';
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=edit&anlage_id='.$anlageId.'">'.$langs->trans('Modify').'</a>';
|
||||||
|
}
|
||||||
|
if ($permissiontodelete) {
|
||||||
|
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=delete&anlage_id='.$anlageId.'">'.$langs->trans('Delete').'</a>';
|
||||||
|
}
|
||||||
|
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'">'.$langs->trans('Back').'</a>';
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Erstellen/Bearbeiten-Formular
|
||||||
|
$isEdit = ($action == 'edit');
|
||||||
|
$formAction = $isEdit ? 'update' : 'add';
|
||||||
|
|
||||||
|
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'">';
|
||||||
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||||
|
print '<input type="hidden" name="action" value="'.$formAction.'">';
|
||||||
|
print '<input type="hidden" name="system" value="'.$systemId.'">';
|
||||||
|
if ($isEdit) {
|
||||||
|
print '<input type="hidden" name="anlage_id" value="'.$anlageId.'">';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '<table class="border centpercent" id="element_form_table">';
|
||||||
|
|
||||||
|
// Label
|
||||||
|
$labelValue = $isEdit ? $anlage->label : GETPOST('label');
|
||||||
|
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('Label').'</td>';
|
||||||
|
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($labelValue).'" required></td></tr>';
|
||||||
|
|
||||||
|
// Typ
|
||||||
|
print '<tr><td class="fieldrequired">'.$langs->trans('Type').'</td>';
|
||||||
|
print '<td><select name="fk_anlage_type" class="flat minwidth200" id="select_type" required>';
|
||||||
|
print '<option value="">'.$langs->trans('SelectType').'</option>';
|
||||||
|
foreach ($types as $t) {
|
||||||
|
$selected = ($isEdit && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||||
|
print '<option value="'.$t->id.'" data-has-product="'.($t->has_product ? '1' : '0').'" data-has-accessories="'.($t->has_accessories ? '1' : '0').'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||||
|
}
|
||||||
|
print '</select>';
|
||||||
|
if (empty($types)) {
|
||||||
|
print '<br><span class="warning">'.$langs->trans('NoTypesDefinedForSystem').' <a href="'.dol_buildpath('/kundenkarte/admin/anlage_types.php', 1).'">'.$langs->trans('GoToTypeAdmin').'</a></span>';
|
||||||
|
}
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
|
// Übergeordnetes Element
|
||||||
|
$tree = $anlage->fetchTree($socId, $systemId);
|
||||||
|
$selectedParent = $isEdit ? $anlage->fk_parent : $parentId;
|
||||||
|
$excludeId = $isEdit ? $anlageId : 0;
|
||||||
|
print '<tr><td>'.$langs->trans('SelectParent').'</td>';
|
||||||
|
print '<td><select name="fk_parent" class="flat minwidth200">';
|
||||||
|
print '<option value="0">('.$langs->trans('Root').')</option>';
|
||||||
|
werkzeuge_printTreeOptions($tree, $selectedParent, $excludeId);
|
||||||
|
print '</select></td></tr>';
|
||||||
|
|
||||||
|
// Produkt-Zuordnung (wird per JS ein-/ausgeblendet je nach Typ)
|
||||||
|
$productValue = '';
|
||||||
|
$productId = 0;
|
||||||
|
if ($isEdit && $anlage->fk_product > 0) {
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
$product = new Product($db);
|
||||||
|
if ($product->fetch($anlage->fk_product) > 0) {
|
||||||
|
$productValue = $product->ref.' - '.$product->label;
|
||||||
|
$productId = $product->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '<tr id="row_product" style="display:none;"><td>'.$langs->trans('Product').'</td>';
|
||||||
|
print '<td>';
|
||||||
|
print '<input type="text" id="product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'..." value="'.dol_escape_htmltag($productValue).'">';
|
||||||
|
print '<input type="hidden" name="fk_product" id="fk_product" value="'.$productId.'">';
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
|
// Dynamische Felder werden per JS geladen
|
||||||
|
print '<tbody id="dynamic_fields"></tbody>';
|
||||||
|
|
||||||
|
// Notizen
|
||||||
|
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
|
||||||
|
$noteValue = $isEdit ? $anlage->note_private : (isset($_POST['note_private']) ? $_POST['note_private'] : '');
|
||||||
|
print '<td><textarea name="note_private" class="flat minwidth300" rows="3">'.htmlspecialchars($noteValue, ENT_QUOTES, 'UTF-8').'</textarea></td></tr>';
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
|
||||||
|
print '<div class="center" style="margin-top:20px;">';
|
||||||
|
print '<button type="submit" class="button button-save">'.$langs->trans('Save').'</button>';
|
||||||
|
print ' <a class="button button-cancel" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'">'.$langs->trans('Cancel').'</a>';
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
print '</form>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Baumansicht
|
||||||
|
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
print '<div style="margin-bottom:15px;">';
|
||||||
|
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=create">';
|
||||||
|
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
|
||||||
|
print '</a>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baum laden
|
||||||
|
$tree = $anlage->fetchTree($socId, $systemId);
|
||||||
|
|
||||||
|
// Feld-Metadaten laden
|
||||||
|
$typeFieldsMap = array();
|
||||||
|
$sql = "SELECT f.*, f.fk_anlage_type FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field f WHERE f.active = 1 ORDER BY f.position ASC";
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
if (!isset($typeFieldsMap[$obj->fk_anlage_type])) {
|
||||||
|
$typeFieldsMap[$obj->fk_anlage_type] = array();
|
||||||
|
}
|
||||||
|
$typeFieldsMap[$obj->fk_anlage_type][] = $obj;
|
||||||
|
}
|
||||||
|
$db->free($resql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($tree)) {
|
||||||
|
print '<div class="kundenkarte-tree'.($showDecomm ? ' show-decommissioned' : '').'" data-system="'.$systemId.'" data-socid="'.$socId.'">';
|
||||||
|
werkzeuge_printTree($tree, $socId, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap);
|
||||||
|
print '</div>';
|
||||||
|
} else {
|
||||||
|
print '<div class="opacitymedium" style="padding:20px;text-align:center;">';
|
||||||
|
print '<i class="fa fa-wrench" style="font-size:48px;margin-bottom:15px;color:#666;"></i><br>';
|
||||||
|
print $langs->trans('NoToolsYet').'<br><br>';
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=create"><i class="fa fa-plus"></i> '.$langs->trans('AddFirstTool').'</a>';
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Ende elseif ($systemId > 0)
|
||||||
|
|
||||||
|
print '</div>'; // fichecenter
|
||||||
|
|
||||||
|
// Tooltip Container
|
||||||
|
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
|
||||||
|
|
||||||
|
// JavaScript: Produkt-Autocomplete + Zubehör-AJAX
|
||||||
|
print '<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
var baseUrl = "'.dol_escape_js(dol_buildpath('/kundenkarte', 1)).'";
|
||||||
|
|
||||||
|
// Produkt-Zeile ein-/ausblenden je nach Typ-Flag has_product
|
||||||
|
var $typeSelect = $("#select_type");
|
||||||
|
function updateProductRow() {
|
||||||
|
var $selected = $typeSelect.find("option:selected");
|
||||||
|
var hasProduct = $selected.data("has-product");
|
||||||
|
if (hasProduct == 1) {
|
||||||
|
$("#row_product").show();
|
||||||
|
} else {
|
||||||
|
$("#row_product").hide();
|
||||||
|
$("#fk_product").val("");
|
||||||
|
$("#product_search").val("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$typeSelect.on("change", updateProductRow);
|
||||||
|
updateProductRow(); // Initial
|
||||||
|
|
||||||
|
// Produkt-Autocomplete
|
||||||
|
function initProductAutocomplete(inputSelector, hiddenSelector) {
|
||||||
|
var $input = $(inputSelector);
|
||||||
|
var $hidden = $(hiddenSelector);
|
||||||
|
if (!$input.length) return;
|
||||||
|
|
||||||
|
var searchTimeout;
|
||||||
|
$input.on("input", function() {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
var term = $(this).val();
|
||||||
|
if (term.length < 2) {
|
||||||
|
$hidden.val("");
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
searchTimeout = setTimeout(function() {
|
||||||
|
$.get(baseUrl + "/ajax/equipment.php", {
|
||||||
|
action: "get_products",
|
||||||
|
term: term,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}, function(data) {
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
if (data.success && data.products && data.products.length > 0) {
|
||||||
|
var $dropdown = $("<div class=\"product-autocomplete-dropdown\"></div>");
|
||||||
|
$.each(data.products, function(i, p) {
|
||||||
|
var label = p.ref + " - " + p.label;
|
||||||
|
if (p.price > 0) label += " (" + p.price + " €)";
|
||||||
|
$dropdown.append(
|
||||||
|
$("<div class=\"product-autocomplete-item\"></div>")
|
||||||
|
.text(label)
|
||||||
|
.data("id", p.id)
|
||||||
|
.data("ref", p.ref)
|
||||||
|
.data("label", p.label)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
$input.after($dropdown);
|
||||||
|
$dropdown.on("click", ".product-autocomplete-item", function() {
|
||||||
|
var id = $(this).data("id");
|
||||||
|
var ref = $(this).data("ref");
|
||||||
|
var label = $(this).data("label");
|
||||||
|
$input.val(ref + " - " + label);
|
||||||
|
$hidden.val(id);
|
||||||
|
$dropdown.remove();
|
||||||
|
// Zubehör: Button aktivieren
|
||||||
|
if (inputSelector === "#accessory_product_search") {
|
||||||
|
$("#btn-add-accessory").prop("disabled", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dropdown schließen bei Klick außerhalb
|
||||||
|
$(document).on("click", function(e) {
|
||||||
|
if (!$(e.target).closest(inputSelector + ", .product-autocomplete-dropdown").length) {
|
||||||
|
$(".product-autocomplete-dropdown").remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bei manuellem Leeren auch Hidden zurücksetzen
|
||||||
|
$input.on("change", function() {
|
||||||
|
if (!$(this).val()) {
|
||||||
|
$hidden.val("");
|
||||||
|
if (inputSelector === "#accessory_product_search") {
|
||||||
|
$("#btn-add-accessory").prop("disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initProductAutocomplete("#product_search", "#fk_product");
|
||||||
|
initProductAutocomplete("#accessory_product_search", "#accessory_product_id");
|
||||||
|
|
||||||
|
// Zubehör hinzufügen
|
||||||
|
$("#btn-add-accessory").on("click", function() {
|
||||||
|
var productId = $("#accessory_product_id").val();
|
||||||
|
var qty = $("#accessory_qty").val() || 1;
|
||||||
|
var anlageId = '.((int) $anlageId).';
|
||||||
|
if (!productId || !anlageId) return;
|
||||||
|
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "add",
|
||||||
|
fk_anlage: anlageId,
|
||||||
|
fk_product: productId,
|
||||||
|
qty: qty,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert(data.error || "Fehler");
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
alert("Server-Fehler");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zubehör löschen
|
||||||
|
$(".btn-delete-accessory").on("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!confirm("'.$langs->trans('ConfirmDeleteAccessory').'")) return;
|
||||||
|
var accId = $(this).data("id");
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "delete",
|
||||||
|
id: accId,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert(data.error || "Fehler");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lieferantenbestellung
|
||||||
|
$("#btn-order-accessories").on("click", function() {
|
||||||
|
var supplierId = $("#supplier_select").val();
|
||||||
|
var anlageId = '.((int) $anlageId).';
|
||||||
|
if (!supplierId) {
|
||||||
|
alert("Bitte Lieferant auswählen");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Zubehör-IDs sammeln
|
||||||
|
var ids = [];
|
||||||
|
$(".btn-delete-accessory").each(function() {
|
||||||
|
ids.push($(this).data("id"));
|
||||||
|
});
|
||||||
|
if (ids.length === 0) return;
|
||||||
|
|
||||||
|
$.post(baseUrl + "/ajax/anlage_accessory.php", {
|
||||||
|
action: "order",
|
||||||
|
fk_anlage: anlageId,
|
||||||
|
supplier_id: supplierId,
|
||||||
|
"ids[]": ids,
|
||||||
|
token: $("input[name=token]").val() || ""
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.success && data.order_id) {
|
||||||
|
window.location.href = "'.DOL_URL_ROOT.'/fourn/commande/card.php?id=" + data.order_id;
|
||||||
|
} else {
|
||||||
|
alert(data.error || "Fehler beim Erstellen der Bestellung");
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
alert("Server-Fehler");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>';
|
||||||
|
|
||||||
|
// CSS für Autocomplete
|
||||||
|
print '<style>
|
||||||
|
.product-autocomplete-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
background: var(--colorbackbody, #fff);
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 300px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
.product-autocomplete-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
.product-autocomplete-item:hover {
|
||||||
|
background: var(--colorbacklinepairhover, #333);
|
||||||
|
}
|
||||||
|
.product-autocomplete-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
</style>';
|
||||||
|
|
||||||
|
llxFooter();
|
||||||
|
$db->close();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baum rekursiv ausgeben (vereinfachte Version für Werkzeuge-Seite)
|
||||||
|
*/
|
||||||
|
function werkzeuge_printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $level = 0, $typeFieldsMap = array())
|
||||||
|
{
|
||||||
|
foreach ($nodes as $node) {
|
||||||
|
$hasChildren = !empty($node->children);
|
||||||
|
$fieldValues = $node->getFieldValues();
|
||||||
|
|
||||||
|
// Badges sammeln
|
||||||
|
$treeInfoBadges = array();
|
||||||
|
$treeInfoParentheses = array();
|
||||||
|
|
||||||
|
if (!empty($typeFieldsMap[$node->fk_anlage_type])) {
|
||||||
|
foreach ($typeFieldsMap[$node->fk_anlage_type] as $fieldDef) {
|
||||||
|
if ($fieldDef->field_type === 'header') continue;
|
||||||
|
$value = isset($fieldValues[$fieldDef->field_code]) ? $fieldValues[$fieldDef->field_code] : '';
|
||||||
|
if ($fieldDef->show_in_tree && $value !== '') {
|
||||||
|
$displayVal = $value;
|
||||||
|
if ($fieldDef->field_type === 'date' && $value) {
|
||||||
|
$displayVal = dol_print_date(strtotime($value), 'day');
|
||||||
|
}
|
||||||
|
$fieldInfo = array(
|
||||||
|
'label' => $fieldDef->field_label,
|
||||||
|
'value' => $displayVal,
|
||||||
|
'code' => $fieldDef->field_code,
|
||||||
|
'type' => $fieldDef->field_type,
|
||||||
|
'color' => $fieldDef->badge_color ?? ''
|
||||||
|
);
|
||||||
|
$displayMode = $fieldDef->tree_display_mode ?? 'badge';
|
||||||
|
if ($displayMode === 'parentheses') {
|
||||||
|
$treeInfoParentheses[] = $fieldInfo;
|
||||||
|
} else {
|
||||||
|
$treeInfoBadges[] = $fieldInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$nodeClass = 'kundenkarte-tree-node';
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$nodeClass .= ' decommissioned';
|
||||||
|
}
|
||||||
|
if ($node->type_can_have_children) {
|
||||||
|
$nodeClass .= ' node-structure';
|
||||||
|
} else {
|
||||||
|
$nodeClass .= ' node-leaf';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '<div class="'.$nodeClass.'">';
|
||||||
|
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
|
||||||
|
|
||||||
|
// Toggle
|
||||||
|
if ($hasChildren) {
|
||||||
|
print '<span class="kundenkarte-tree-toggle"><i class="fa fa-chevron-down"></i></span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="kundenkarte-tree-toggle" style="visibility:hidden;"><i class="fa fa-chevron-down"></i></span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
$picto = $node->type_picto ? $node->type_picto : 'fa-wrench';
|
||||||
|
print '<span class="kundenkarte-tree-icon">'.kundenkarte_render_icon($picto).'</span>';
|
||||||
|
|
||||||
|
// Label
|
||||||
|
$viewUrl = $_SERVER['PHP_SELF'].'?system='.$systemId.'&action=view&anlage_id='.$node->id;
|
||||||
|
print '<span class="kundenkarte-tree-label">'.dol_escape_htmltag($node->label);
|
||||||
|
if (!empty($treeInfoParentheses)) {
|
||||||
|
$infoValues = array();
|
||||||
|
foreach ($treeInfoParentheses as $info) {
|
||||||
|
$infoValues[] = dol_escape_htmltag($info['value']);
|
||||||
|
}
|
||||||
|
print ' <span class="kundenkarte-tree-label-info">('.implode(', ', $infoValues).')</span>';
|
||||||
|
}
|
||||||
|
print '</span>';
|
||||||
|
|
||||||
|
// Ausgebaut-Badge
|
||||||
|
if (!empty($node->decommissioned)) {
|
||||||
|
$decommDate = !empty($node->date_decommissioned) ? dol_print_date(strtotime($node->date_decommissioned), 'day') : '';
|
||||||
|
$decommText = $langs->trans('Decommissioned');
|
||||||
|
if ($decommDate) $decommText .= ' '.$decommDate;
|
||||||
|
print ' <span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$decommText.'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produkt-Badge
|
||||||
|
if (!empty($node->fk_product) && !empty($node->product_ref)) {
|
||||||
|
print ' <span class="badge badge-secondary"><i class="fa fa-cube"></i> '.dol_escape_htmltag($node->product_ref).'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacer
|
||||||
|
print '<span class="kundenkarte-tree-spacer"></span>';
|
||||||
|
|
||||||
|
// Badges
|
||||||
|
$defaultBadgeColor = getDolGlobalString('KUNDENKARTE_TREE_BADGE_COLOR', '#2a4a5e');
|
||||||
|
if (!empty($treeInfoBadges)) {
|
||||||
|
print '<span class="kundenkarte-tree-badges">';
|
||||||
|
foreach ($treeInfoBadges as $info) {
|
||||||
|
$badgeIcon = kundenkarte_get_field_icon($info['code'], $info['type']);
|
||||||
|
$fieldBadgeColor = !empty($info['color']) ? $info['color'] : $defaultBadgeColor;
|
||||||
|
print '<span class="kundenkarte-tree-badge" title="'.dol_escape_htmltag($info['label']).'" style="background:linear-gradient(135deg, '.$fieldBadgeColor.' 0%, '.kundenkarte_adjust_color($fieldBadgeColor, -20).' 100%);">';
|
||||||
|
print '<i class="fa '.$badgeIcon.'"></i> '.dol_escape_htmltag($info['value']);
|
||||||
|
print '</span>';
|
||||||
|
}
|
||||||
|
print '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typ-Badge
|
||||||
|
if ($node->type_short || $node->type_label) {
|
||||||
|
$typeDisplay = $node->type_short ? $node->type_short : $node->type_label;
|
||||||
|
print '<span class="kundenkarte-tree-type badge badge-secondary">'.dol_escape_htmltag($typeDisplay).'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktionen
|
||||||
|
print '<span class="kundenkarte-tree-actions">';
|
||||||
|
print '<a href="'.$viewUrl.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||||
|
if ($canEdit) {
|
||||||
|
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||||
|
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
|
$decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission');
|
||||||
|
$decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off';
|
||||||
|
print '<a href="#" class="btn-toggle-decommissioned" data-anlage-id="'.$node->id.'" title="'.$decommLabel.'"><i class="fa '.$decommIcon.'"></i></a>';
|
||||||
|
}
|
||||||
|
if ($canDelete) {
|
||||||
|
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
||||||
|
}
|
||||||
|
print '</span>';
|
||||||
|
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
// Kinder
|
||||||
|
if ($hasChildren) {
|
||||||
|
print '<div class="kundenkarte-tree-children">';
|
||||||
|
werkzeuge_printTree($node->children, $socid, $systemId, $canEdit, $canDelete, $langs, $level + 1, $typeFieldsMap);
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baum-Options für Select (vereinfacht)
|
||||||
|
*/
|
||||||
|
function werkzeuge_printTreeOptions($nodes, $selected = 0, $excludeId = 0, $prefix = '', $level = 0)
|
||||||
|
{
|
||||||
|
foreach ($nodes as $node) {
|
||||||
|
if ($node->id == $excludeId) continue;
|
||||||
|
$sel = ($node->id == $selected) ? ' selected' : '';
|
||||||
|
print '<option value="'.$node->id.'"'.$sel.'>'.$prefix.dol_escape_htmltag($node->label).'</option>';
|
||||||
|
if (!empty($node->children)) {
|
||||||
|
werkzeuge_printTreeOptions($node->children, $selected, $excludeId, $prefix.'── ', $level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue