Neuer Editor neue Idee

This commit is contained in:
Eduard Wisch 2026-02-11 06:50:23 +01:00
parent 658b2df045
commit 3de514308b
34 changed files with 11320 additions and 6 deletions

View file

@ -52,6 +52,7 @@ if ($action == 'add') {
$anlageType->can_have_children = GETPOSTINT('can_have_children');
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
$anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml'));
$anlageType->can_have_equipment = GETPOSTINT('can_have_equipment');
$anlageType->picto = GETPOST('picto', 'alphanohtml');
$anlageType->color = GETPOST('color', 'alphanohtml');
$anlageType->position = GETPOSTINT('position');
@ -101,6 +102,7 @@ if ($action == 'update') {
$anlageType->can_have_children = GETPOSTINT('can_have_children');
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
$anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml'));
$anlageType->can_have_equipment = GETPOSTINT('can_have_equipment');
$anlageType->picto = GETPOST('picto', 'alphanohtml');
$anlageType->color = GETPOST('color', 'alphanohtml');
$anlageType->position = GETPOSTINT('position');
@ -153,6 +155,7 @@ if ($action == 'copy' && $typeId > 0) {
$newType->can_have_children = $sourceType->can_have_children;
$newType->can_be_nested = $sourceType->can_be_nested;
$newType->allowed_parent_types = $sourceType->allowed_parent_types;
$newType->can_have_equipment = $sourceType->can_have_equipment;
$newType->picto = $sourceType->picto;
$newType->color = $sourceType->color;
$newType->position = $sourceType->position + 1;
@ -361,6 +364,11 @@ if (in_array($action, array('create', 'edit'))) {
print '<td><input type="checkbox" name="can_be_nested" value="1"'.($anlageType->can_be_nested ? ' checked' : '').'>';
print ' <span class="opacitymedium">('.$langs->trans('SameTypeUnderItself').')</span></td></tr>';
// Can have equipment (Hutschienen-Komponenten)
print '<tr><td>'.$langs->trans('CanHaveEquipment').'</td>';
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>';
// Allowed parent types - with multi-select UI
print '<tr><td>'.$langs->trans('AllowedParentTypes').'</td>';
print '<td>';

670
admin/equipment_types.php Normal file
View file

@ -0,0 +1,670 @@
<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* Admin page to manage equipment types (Hutschienen-Komponenten)
*/
$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/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
dol_include_once('/kundenkarte/class/equipmenttype.class.php');
$langs->loadLangs(array('admin', 'kundenkarte@kundenkarte', 'products'));
// Security check
if (!$user->admin && !$user->hasRight('kundenkarte', 'admin')) {
accessforbidden();
}
$action = GETPOST('action', 'aZ09');
$confirm = GETPOST('confirm', 'alpha');
$typeId = GETPOSTINT('typeid');
$systemFilter = GETPOSTINT('system');
$form = new Form($db);
$equipmentType = new EquipmentType($db);
// Load systems
$systems = array();
$sql = "SELECT rowid, code, label 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)) {
$systems[$obj->rowid] = $obj;
}
}
// Load products for dropdown
$products = array();
$sql = "SELECT rowid, ref, label FROM ".MAIN_DB_PREFIX."product WHERE tosell = 1 ORDER BY ref ASC";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$products[$obj->rowid] = $obj;
}
}
/*
* Actions
*/
if ($action == 'add') {
$equipmentType->ref = GETPOST('ref', 'aZ09');
$equipmentType->label = GETPOST('label', 'alphanohtml');
$equipmentType->label_short = GETPOST('label_short', 'alphanohtml');
$equipmentType->description = GETPOST('description', 'restricthtml');
$equipmentType->fk_system = GETPOSTINT('fk_system');
$equipmentType->width_te = GETPOSTINT('width_te');
$equipmentType->color = GETPOST('color', 'alphanohtml');
$equipmentType->fk_product = GETPOSTINT('fk_product');
$equipmentType->terminals_config = GETPOST('terminals_config', 'nohtml');
$equipmentType->picto = GETPOST('picto', 'alphanohtml');
$equipmentType->position = GETPOSTINT('position');
$equipmentType->active = 1;
if (empty($equipmentType->ref) || empty($equipmentType->label) || empty($equipmentType->fk_system)) {
setEventMessages($langs->trans('ErrorFieldRequired'), null, 'errors');
$action = 'create';
} else {
$result = $equipmentType->create($user);
if ($result > 0) {
// Create default fields for common equipment
$defaultFields = array(
array('code' => 'characteristic', 'label' => 'Charakteristik', 'type' => 'select', 'options' => 'B|C|D|K|Z', 'position' => 10, 'show_in_hover' => 1, 'show_on_block' => 1),
array('code' => 'ampere', 'label' => 'Nennstrom (A)', 'type' => 'select', 'options' => '6|10|13|16|20|25|32|40|50|63', 'position' => 20, 'show_in_hover' => 1, 'show_on_block' => 1),
array('code' => 'pole', 'label' => 'Polzahl', 'type' => 'select', 'options' => '1|2|3|3+N', 'position' => 30, 'show_in_hover' => 1, 'show_on_block' => 0),
array('code' => 'circuit', 'label' => 'Stromkreis', 'type' => 'text', 'options' => '', 'position' => 40, 'show_in_hover' => 1, 'show_on_block' => 0),
);
foreach ($defaultFields as $field) {
$sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field";
$sql .= " (fk_equipment_type, field_code, field_label, field_type, field_options, show_in_hover, show_on_block, required, position, active)";
$sql .= " VALUES (".((int) $equipmentType->id).", '".$db->escape($field['code'])."', '".$db->escape($field['label'])."',";
$sql .= " '".$db->escape($field['type'])."', '".$db->escape($field['options'])."', ".((int) $field['show_in_hover']).", ".((int) $field['show_on_block']).", 0, ".((int) $field['position']).", 1)";
$db->query($sql);
}
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$equipmentType->id.'&system='.$equipmentType->fk_system);
exit;
} else {
setEventMessages($equipmentType->error, $equipmentType->errors, 'errors');
$action = 'create';
}
}
}
if ($action == 'update') {
$equipmentType->fetch($typeId);
$equipmentType->ref = GETPOST('ref', 'aZ09');
$equipmentType->label = GETPOST('label', 'alphanohtml');
$equipmentType->label_short = GETPOST('label_short', 'alphanohtml');
$equipmentType->description = GETPOST('description', 'restricthtml');
$equipmentType->fk_system = GETPOSTINT('fk_system');
$equipmentType->width_te = GETPOSTINT('width_te');
$equipmentType->color = GETPOST('color', 'alphanohtml');
$equipmentType->fk_product = GETPOSTINT('fk_product');
$equipmentType->terminals_config = GETPOST('terminals_config', 'nohtml');
$equipmentType->picto = GETPOST('picto', 'alphanohtml');
$equipmentType->position = GETPOSTINT('position');
$result = $equipmentType->update($user);
if ($result > 0) {
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF'].'?system='.$equipmentType->fk_system);
exit;
} else {
setEventMessages($equipmentType->error, $equipmentType->errors, 'errors');
$action = 'edit';
}
}
if ($action == 'confirm_delete' && $confirm == 'yes') {
$equipmentType->fetch($typeId);
$result = $equipmentType->delete($user);
if ($result > 0) {
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
} else {
setEventMessages($equipmentType->error, $equipmentType->errors, 'errors');
}
$action = '';
}
if ($action == 'activate') {
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_equipment_type SET active = 1 WHERE rowid = ".((int) $typeId);
$db->query($sql);
$action = '';
}
if ($action == 'deactivate') {
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_equipment_type SET active = 0 WHERE rowid = ".((int) $typeId);
$db->query($sql);
$action = '';
}
// Copy type with all fields
if ($action == 'copy' && $typeId > 0) {
$sourceType = new EquipmentType($db);
if ($sourceType->fetch($typeId) > 0) {
$newType = new EquipmentType($db);
$newType->ref = $sourceType->ref.'_COPY';
$newType->label = $sourceType->label.' (Kopie)';
$newType->label_short = $sourceType->label_short;
$newType->description = $sourceType->description;
$newType->fk_system = $sourceType->fk_system;
$newType->width_te = $sourceType->width_te;
$newType->color = $sourceType->color;
$newType->fk_product = $sourceType->fk_product;
$newType->picto = $sourceType->picto;
$newType->position = $sourceType->position + 1;
$newType->active = 1;
$result = $newType->create($user);
if ($result > 0) {
// Copy all fields from source type
$sourceFields = $sourceType->fetchFields(0);
foreach ($sourceFields as $field) {
$sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field";
$sql .= " (fk_equipment_type, field_code, field_label, field_type, field_options, show_in_hover, show_on_block, required, position, active)";
$sql .= " VALUES (".((int) $newType->id).", '".$db->escape($field->field_code)."', '".$db->escape($field->field_label)."',";
$sql .= " '".$db->escape($field->field_type)."', '".$db->escape($field->field_options)."', ".((int) $field->show_in_hover).",";
$sql .= " ".((int) $field->show_on_block).", ".((int) $field->required).", ".((int) $field->position).", ".((int) $field->active).")";
$db->query($sql);
}
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$newType->id.'&system='.$newType->fk_system);
exit;
} else {
setEventMessages($newType->error, $newType->errors, 'errors');
}
}
$action = '';
}
// Field actions
$fieldId = GETPOSTINT('fieldid');
if ($action == 'add_field') {
$fieldCode = GETPOST('field_code', 'aZ09');
$fieldLabel = GETPOST('field_label', 'alphanohtml');
$fieldType = GETPOST('field_type', 'aZ09');
$fieldOptions = GETPOST('field_options', 'nohtml');
$showInHover = GETPOSTINT('show_in_hover');
$showOnBlock = GETPOSTINT('show_on_block');
$isRequired = GETPOSTINT('is_required');
$fieldPosition = GETPOSTINT('field_position');
if (empty($fieldCode) || empty($fieldLabel) || empty($fieldType)) {
setEventMessages($langs->trans('ErrorFieldRequired'), null, 'errors');
} else {
$sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field";
$sql .= " (fk_equipment_type, field_code, field_label, field_type, field_options, show_in_hover, show_on_block, required, position, active)";
$sql .= " VALUES (".((int) $typeId).", '".$db->escape($fieldCode)."', '".$db->escape($fieldLabel)."',";
$sql .= " '".$db->escape($fieldType)."', '".$db->escape($fieldOptions)."',";
$sql .= " ".((int) $showInHover).", ".((int) $showOnBlock).", ".((int) $isRequired).", ".((int) $fieldPosition).", 1)";
if ($db->query($sql)) {
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
} else {
setEventMessages($db->lasterror(), null, 'errors');
}
}
$action = 'edit';
}
if ($action == 'update_field') {
$fieldCode = GETPOST('field_code', 'aZ09');
$fieldLabel = GETPOST('field_label', 'alphanohtml');
$fieldType = GETPOST('field_type', 'aZ09');
$fieldOptions = GETPOST('field_options', 'nohtml');
$showInHover = GETPOSTINT('show_in_hover');
$showOnBlock = GETPOSTINT('show_on_block');
$isRequired = GETPOSTINT('is_required');
$fieldPosition = GETPOSTINT('field_position');
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field SET";
$sql .= " field_code = '".$db->escape($fieldCode)."',";
$sql .= " field_label = '".$db->escape($fieldLabel)."',";
$sql .= " field_type = '".$db->escape($fieldType)."',";
$sql .= " field_options = '".$db->escape($fieldOptions)."',";
$sql .= " show_in_hover = ".((int) $showInHover).",";
$sql .= " show_on_block = ".((int) $showOnBlock).",";
$sql .= " required = ".((int) $isRequired).",";
$sql .= " position = ".((int) $fieldPosition);
$sql .= " WHERE rowid = ".((int) $fieldId);
if ($db->query($sql)) {
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
} else {
setEventMessages($db->lasterror(), null, 'errors');
}
$action = 'edit';
}
if ($action == 'confirm_delete_field' && $confirm == 'yes') {
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field WHERE rowid = ".((int) $fieldId);
if ($db->query($sql)) {
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
} else {
setEventMessages($db->lasterror(), null, 'errors');
}
header('Location: '.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$typeId.'&system='.$systemFilter);
exit;
}
if ($action == 'activate_field') {
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field SET active = 1 WHERE rowid = ".((int) $fieldId);
$db->query($sql);
$action = 'edit';
}
if ($action == 'deactivate_field') {
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field SET active = 0 WHERE rowid = ".((int) $fieldId);
$db->query($sql);
$action = 'edit';
}
/*
* View
*/
$title = $langs->trans('EquipmentTypes');
$morejs = array('/kundenkarte/js/kundenkarte.js?v='.time());
$morecss = array('/kundenkarte/css/kundenkarte.css?v='.time());
llxHeader('', $title, '', '', 0, 0, $morejs, $morecss);
$head = kundenkarteAdminPrepareHead();
print dol_get_fiche_head($head, 'equipment_types', $langs->trans('ModuleKundenKarteName'), -1, 'fa-file');
// Confirmation for type deletion
if ($action == 'delete') {
print $form->formconfirm(
$_SERVER['PHP_SELF'].'?typeid='.$typeId.'&system='.$systemFilter,
$langs->trans('Delete'),
$langs->trans('ConfirmDeleteType'),
'confirm_delete',
'',
'yes',
1
);
}
// Confirmation for field deletion
if ($action == 'delete_field') {
print $form->formconfirm(
$_SERVER['PHP_SELF'].'?typeid='.$typeId.'&fieldid='.$fieldId.'&system='.$systemFilter,
$langs->trans('Delete'),
$langs->trans('ConfirmDeleteField'),
'confirm_delete_field',
'',
'yes',
1
);
$action = 'edit';
}
// Add/Edit form
if (in_array($action, array('create', 'edit'))) {
if ($action == 'edit' && $typeId > 0) {
$equipmentType->fetch($typeId);
}
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="'.($action == 'edit' ? 'update' : 'add').'">';
if ($action == 'edit') {
print '<input type="hidden" name="typeid" value="'.$typeId.'">';
}
print '<table class="border centpercent">';
// System
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('System').'</td>';
print '<td><select name="fk_system" class="flat minwidth200" required>';
print '<option value="">'.$langs->trans('SelectSystem').'</option>';
foreach ($systems as $sys) {
$sel = ($equipmentType->fk_system == $sys->rowid) ? ' selected' : '';
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
}
print '</select></td></tr>';
// Reference
print '<tr><td class="fieldrequired">'.$langs->trans('TypeRef').'</td>';
print '<td><input type="text" name="ref" class="flat minwidth200" value="'.dol_escape_htmltag($equipmentType->ref).'" maxlength="64" required>';
print ' <span class="opacitymedium">(UPPERCASE, no spaces)</span></td></tr>';
// Label
print '<tr><td class="fieldrequired">'.$langs->trans('TypeLabel').'</td>';
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($equipmentType->label).'" required></td></tr>';
// Short label
print '<tr><td>'.$langs->trans('TypeShortLabel').'</td>';
print '<td><input type="text" name="label_short" class="flat" value="'.dol_escape_htmltag($equipmentType->label_short).'" maxlength="32">';
print ' <span class="opacitymedium">(z.B. LS, FI, FI/LS)</span></td></tr>';
// Description
print '<tr><td>'.$langs->trans('Description').'</td>';
print '<td><textarea name="description" class="flat minwidth300" rows="2">'.dol_escape_htmltag($equipmentType->description).'</textarea></td></tr>';
// Width in TE
print '<tr><td class="fieldrequired">'.$langs->trans('WidthTE').'</td>';
print '<td><input type="number" name="width_te" class="flat" value="'.($equipmentType->width_te ?: 1).'" min="1" max="12" required>';
print ' <span class="opacitymedium">'.$langs->trans('WidthTEHelp').'</span></td></tr>';
// Color
print '<tr><td>'.$langs->trans('Color').'</td>';
print '<td><input type="color" name="color" value="'.($equipmentType->color ?: '#3498db').'" style="width:60px;height:30px;padding:0;border:1px solid #ccc;">';
print ' <span class="opacitymedium">'.$langs->trans('ColorForSVG').'</span></td></tr>';
// Product
print '<tr><td>'.$langs->trans('LinkedProduct').'</td>';
print '<td><select name="fk_product" class="flat minwidth300">';
print '<option value="0">'.$langs->trans('None').'</option>';
foreach ($products as $prod) {
$sel = ($equipmentType->fk_product == $prod->rowid) ? ' selected' : '';
print '<option value="'.$prod->rowid.'"'.$sel.'>'.dol_escape_htmltag($prod->ref.' - '.$prod->label).'</option>';
}
print '</select></td></tr>';
// Icon
print '<tr><td>'.$langs->trans('SystemPicto').'</td>';
print '<td><div class="kundenkarte-icon-picker-wrapper">';
print '<span class="kundenkarte-icon-preview">';
if ($equipmentType->picto) {
print kundenkarte_render_icon($equipmentType->picto);
}
print '</span>';
print '<input type="text" name="picto" class="flat minwidth200" value="'.dol_escape_htmltag($equipmentType->picto).'" placeholder="fa-bolt">';
print '<button type="button" class="kundenkarte-icon-picker-btn" data-input="picto"><i class="fa fa-th"></i> '.$langs->trans('SelectIcon').'</button>';
print '</div></td></tr>';
// Position
print '<tr><td>'.$langs->trans('Position').'</td>';
print '<td><input type="number" name="position" class="flat" value="'.($equipmentType->position ?: 0).'" min="0"></td></tr>';
// Terminal configuration
print '<tr><td>'.$langs->trans('TerminalConfig').'</td>';
print '<td>';
print '<div style="margin-bottom:10px;">';
print '<label style="margin-right:15px;"><strong>Vorlagen:</strong></label>';
print '<button type="button" class="button small" onclick="setTerminals(\'1P\')">1-polig</button> ';
print '<button type="button" class="button small" onclick="setTerminals(\'2P\')">2-polig (L+N)</button> ';
print '<button type="button" class="button small" onclick="setTerminals(\'3P\')">3-polig</button> ';
print '<button type="button" class="button small" onclick="setTerminals(\'4P\')">4-polig (3P+N)</button>';
print '</div>';
print '<textarea name="terminals_config" id="terminals_config" class="flat" rows="4" style="width:100%;font-family:monospace;font-size:11px;" placeholder=\'{"terminals":[{"id":"t1","label":"L","pos":"top"},{"id":"t2","label":"L","pos":"bottom"}]}\'>';
print dol_escape_htmltag($equipmentType->terminals_config);
print '</textarea>';
print '<div class="opacitymedium small" style="margin-top:5px;">';
print 'JSON-Format: <code>{"terminals":[{"id":"t1","label":"L","pos":"top"},...]}</code> - pos: "top" oder "bottom"';
print '</div>';
print '</td></tr>';
print '</table>';
// JavaScript for terminal presets
print '<script>
function setTerminals(type) {
var configs = {
"1P": {"terminals":[{"id":"t1","label":"","pos":"top"},{"id":"t2","label":"","pos":"bottom"}]},
"2P": {"terminals":[{"id":"t1","label":"L","pos":"top"},{"id":"t2","label":"N","pos":"top"},{"id":"t3","label":"L","pos":"bottom"},{"id":"t4","label":"N","pos":"bottom"}]},
"3P": {"terminals":[{"id":"t1","label":"L1","pos":"top"},{"id":"t2","label":"L2","pos":"top"},{"id":"t3","label":"L3","pos":"top"},{"id":"t4","label":"L1","pos":"bottom"},{"id":"t5","label":"L2","pos":"bottom"},{"id":"t6","label":"L3","pos":"bottom"}]},
"4P": {"terminals":[{"id":"t1","label":"L1","pos":"top"},{"id":"t2","label":"L2","pos":"top"},{"id":"t3","label":"L3","pos":"top"},{"id":"t4","label":"N","pos":"top"},{"id":"t5","label":"L1","pos":"bottom"},{"id":"t6","label":"L2","pos":"bottom"},{"id":"t7","label":"L3","pos":"bottom"},{"id":"t8","label":"N","pos":"bottom"}]}
};
if (configs[type]) {
document.getElementById("terminals_config").value = JSON.stringify(configs[type], null, 2);
}
}
</script>';
print '<div class="center" style="margin-top:20px;">';
print '<button type="submit" class="button">'.$langs->trans('Save').'</button>';
print ' <a class="button button-cancel" href="'.$_SERVER['PHP_SELF'].'?system='.$systemFilter.'">'.$langs->trans('Cancel').'</a>';
print '</div>';
print '</form>';
// Fields management for existing type
if ($action == 'edit' && $typeId > 0) {
$editFieldId = GETPOSTINT('editfield');
print '<br><br>';
print '<h3>'.$langs->trans('EquipmentTypeFields').'</h3>';
$fields = $equipmentType->fetchFields(0);
// Field types available
$fieldTypes = array(
'text' => 'Textfeld (einzeilig)',
'textarea' => 'Textfeld (mehrzeilig)',
'number' => 'Zahlenfeld',
'select' => 'Dropdown-Auswahl',
'date' => 'Datumsfeld',
'checkbox' => 'Checkbox (Ja/Nein)',
);
// Output edit forms BEFORE the table
foreach ($fields as $field) {
if ($editFieldId == $field->rowid) {
$formId = 'editfield_'.$field->rowid;
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" id="'.$formId.'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="update_field">';
print '<input type="hidden" name="typeid" value="'.$typeId.'">';
print '<input type="hidden" name="fieldid" value="'.$field->rowid.'">';
print '<input type="hidden" name="system" value="'.$systemFilter.'">';
print '</form>';
}
}
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>'.$langs->trans('FieldCode').'</th>';
print '<th>'.$langs->trans('FieldLabel').'</th>';
print '<th>'.$langs->trans('FieldType').'</th>';
print '<th>'.$langs->trans('FieldOptions').'</th>';
print '<th class="center">'.$langs->trans('ShowInHover').'</th>';
print '<th class="center">'.$langs->trans('ShowOnBlock').'</th>';
print '<th class="center">'.$langs->trans('IsRequired').'</th>';
print '<th class="center">'.$langs->trans('Position').'</th>';
print '<th class="center">'.$langs->trans('Status').'</th>';
print '<th class="center">'.$langs->trans('Actions').'</th>';
print '</tr>';
foreach ($fields as $field) {
if ($editFieldId == $field->rowid) {
$formId = 'editfield_'.$field->rowid;
print '<tr class="oddeven">';
print '<td><input type="text" name="field_code" form="'.$formId.'" class="flat minwidth100" value="'.dol_escape_htmltag($field->field_code).'" required></td>';
print '<td><input type="text" name="field_label" form="'.$formId.'" class="flat minwidth150" value="'.dol_escape_htmltag($field->field_label).'" required></td>';
print '<td><select name="field_type" form="'.$formId.'" class="flat">';
foreach ($fieldTypes as $ftype => $flabel) {
$sel = ($field->field_type == $ftype) ? ' selected' : '';
print '<option value="'.$ftype.'"'.$sel.'>'.$flabel.'</option>';
}
print '</select></td>';
print '<td><input type="text" name="field_options" form="'.$formId.'" class="flat minwidth100" value="'.dol_escape_htmltag($field->field_options).'" placeholder="opt1|opt2|opt3"></td>';
print '<td class="center"><input type="checkbox" name="show_in_hover" form="'.$formId.'" value="1"'.($field->show_in_hover ? ' checked' : '').'></td>';
print '<td class="center"><input type="checkbox" name="show_on_block" form="'.$formId.'" value="1"'.($field->show_on_block ? ' checked' : '').'></td>';
print '<td class="center"><input type="checkbox" name="is_required" form="'.$formId.'" value="1"'.($field->required ? ' checked' : '').'></td>';
print '<td class="center"><input type="number" name="field_position" form="'.$formId.'" class="flat" style="width:50px;" value="'.$field->position.'" min="0"></td>';
print '<td></td>';
print '<td class="center nowraponall">';
print '<div style="display:inline-flex;gap:8px;">';
print '<button type="submit" form="'.$formId.'" class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:#28a745;border-color:#28a745;color:#fff;" title="'.$langs->trans('Save').'"><i class="fas fa-save"></i></button>';
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$typeId.'&system='.$systemFilter.'" title="'.$langs->trans('Cancel').'"><i class="fas fa-times"></i></a>';
print '</div>';
print '</td>';
print '</tr>';
} else {
print '<tr class="oddeven">';
print '<td>'.dol_escape_htmltag($field->field_code).'</td>';
print '<td>'.dol_escape_htmltag($field->field_label).'</td>';
print '<td>'.dol_escape_htmltag($fieldTypes[$field->field_type] ?? $field->field_type).'</td>';
print '<td class="small opacitymedium">'.dol_escape_htmltag(dol_trunc($field->field_options, 20)).'</td>';
print '<td class="center">'.($field->show_in_hover ? img_picto('', 'tick') : '').'</td>';
print '<td class="center">'.($field->show_on_block ? img_picto('', 'tick') : '').'</td>';
print '<td class="center">'.($field->required ? img_picto('', 'tick') : '').'</td>';
print '<td class="center">'.$field->position.'</td>';
print '<td class="center">';
if ($field->active) {
print '<a href="'.$_SERVER['PHP_SELF'].'?action=deactivate_field&typeid='.$typeId.'&fieldid='.$field->rowid.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Enabled'), 'switch_on').'</a>';
} else {
print '<a href="'.$_SERVER['PHP_SELF'].'?action=activate_field&typeid='.$typeId.'&fieldid='.$field->rowid.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Disabled'), 'switch_off').'</a>';
}
print '</td>';
print '<td class="center nowraponall">';
print '<div style="display:inline-flex;gap:8px;">';
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$typeId.'&editfield='.$field->rowid.'&system='.$systemFilter.'" title="'.$langs->trans('Edit').'"><i class="fas fa-pen"></i></a>';
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:#dc3545;border-color:#dc3545;color:#fff;" href="'.$_SERVER['PHP_SELF'].'?action=delete_field&typeid='.$typeId.'&fieldid='.$field->rowid.'&system='.$systemFilter.'&token='.newToken().'" title="'.$langs->trans('Delete').'"><i class="fas fa-trash"></i></a>';
print '</div>';
print '</td>';
print '</tr>';
}
}
if (empty($fields)) {
print '<tr class="oddeven"><td colspan="10" class="opacitymedium">'.$langs->trans('NoFieldsDefined').'</td></tr>';
}
print '</table>';
// Add new field form
print '<div class="margintoponlyshort">';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="add_field">';
print '<input type="hidden" name="typeid" value="'.$typeId.'">';
print '<input type="hidden" name="system" value="'.$systemFilter.'">';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th colspan="10">'.$langs->trans('Add').' '.$langs->trans('Field').'</th>';
print '</tr>';
print '<tr class="oddeven">';
print '<td><input type="text" name="field_code" class="flat minwidth100" placeholder="CODE" required></td>';
print '<td><input type="text" name="field_label" class="flat minwidth150" placeholder="'.$langs->trans('FieldLabel').'" required></td>';
print '<td><select name="field_type" class="flat" required>';
print '<option value="">'.$langs->trans('Select').'</option>';
foreach ($fieldTypes as $ftype => $flabel) {
print '<option value="'.$ftype.'">'.$flabel.'</option>';
}
print '</select></td>';
print '<td><input type="text" name="field_options" class="flat minwidth100" placeholder="opt1|opt2"></td>';
print '<td class="center"><input type="checkbox" name="show_in_hover" value="1" checked></td>';
print '<td class="center"><input type="checkbox" name="show_on_block" value="1"></td>';
print '<td class="center"><input type="checkbox" name="is_required" value="1"></td>';
print '<td class="center"><input type="number" name="field_position" class="flat" style="width:50px;" value="0" min="0"></td>';
print '<td></td>';
print '<td class="center"><button type="submit" class="button buttongen"><i class="fa fa-plus"></i> '.$langs->trans('Add').'</button></td>';
print '</tr>';
print '</table>';
print '</form>';
print '</div>';
// Help box
print '<div class="info" style="margin-top:15px;">';
print '<p><strong><i class="fa fa-info-circle"></i> Hilfe: Equipment-Felder</strong></p>';
print '<table class="noborder" style="margin-top:10px;">';
print '<tr><td style="width:200px;"><strong>Show in Hover</strong></td><td>Feld wird im Tooltip angezeigt</td></tr>';
print '<tr><td><strong>Show on Block</strong></td><td>Feld wird direkt auf dem SVG-Block angezeigt (z.B. "B16")</td></tr>';
print '<tr><td><strong>Dropdown-Auswahl</strong></td><td>Optionen mit <code>|</code> trennen, z.B.: <code>B|C|D</code> oder <code>6|10|16|20|25|32</code></td></tr>';
print '</table>';
print '</div>';
}
} else {
// System filter
print '<form method="GET" action="'.$_SERVER['PHP_SELF'].'" style="margin-bottom:15px;">';
print $langs->trans('FilterBySystem').': ';
print '<select name="system" class="flat" onchange="this.form.submit();">';
print '<option value="0">'.$langs->trans('All').'</option>';
foreach ($systems as $sys) {
$sel = ($systemFilter == $sys->rowid) ? ' selected' : '';
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
}
print '</select>';
print '</form>';
// Add button
print '<div style="margin-bottom:15px;">';
print '<a class="button" href="'.$_SERVER['PHP_SELF'].'?action=create&system='.$systemFilter.'">';
print '<i class="fa fa-plus"></i> '.$langs->trans('AddEquipmentType');
print '</a>';
print '</div>';
// List
$types = $equipmentType->fetchAllBySystem($systemFilter, 0);
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>'.$langs->trans('TypeRef').'</th>';
print '<th>'.$langs->trans('TypeLabel').'</th>';
print '<th>'.$langs->trans('System').'</th>';
print '<th class="center">'.$langs->trans('WidthTE').'</th>';
print '<th class="center">'.$langs->trans('Color').'</th>';
print '<th class="center">'.$langs->trans('Position').'</th>';
print '<th class="center">'.$langs->trans('Status').'</th>';
print '<th class="center">'.$langs->trans('Actions').'</th>';
print '</tr>';
foreach ($types as $type) {
print '<tr class="oddeven">';
print '<td>';
if ($type->picto) {
print kundenkarte_render_icon($type->picto).' ';
}
print dol_escape_htmltag($type->ref).'</td>';
print '<td>'.dol_escape_htmltag($type->label);
if ($type->label_short) {
print ' <span class="opacitymedium">('.$type->label_short.')</span>';
}
print '</td>';
print '<td>'.dol_escape_htmltag($type->system_label).'</td>';
print '<td class="center">'.$type->width_te.' TE</td>';
print '<td class="center"><span style="display:inline-block;width:24px;height:24px;background:'.($type->color ?: '#3498db').';border-radius:3px;"></span></td>';
print '<td class="center">'.$type->position.'</td>';
print '<td class="center">';
if ($type->active) {
print '<a href="'.$_SERVER['PHP_SELF'].'?action=deactivate&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Enabled'), 'switch_on').'</a>';
} else {
print '<a href="'.$_SERVER['PHP_SELF'].'?action=activate&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Disabled'), 'switch_off').'</a>';
}
print '</td>';
print '<td class="center nowraponall">';
print '<div style="display:inline-flex;gap:8px;">';
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$type->id.'&system='.$systemFilter.'" title="'.$langs->trans('Edit').'"><i class="fas fa-pen"></i></a>';
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=copy&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'" title="'.$langs->trans('Copy').'"><i class="fas fa-copy"></i></a>';
if (!$type->is_system) {
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:#dc3545;border-color:#dc3545;color:#fff;" href="'.$_SERVER['PHP_SELF'].'?action=delete&typeid='.$type->id.'&system='.$systemFilter.'" title="'.$langs->trans('Delete').'" onclick="return confirm(\''.$langs->trans('ConfirmDelete').'\');"><i class="fas fa-trash"></i></a>';
}
print '</div>';
print '</td>';
print '</tr>';
}
if (empty($types)) {
print '<tr class="oddeven"><td colspan="8" class="opacitymedium">'.$langs->trans('NoRecords').'</td></tr>';
}
print '</table>';
}
print dol_get_fiche_end();
llxFooter();
$db->close();

355
ajax/equipment.php Normal file
View file

@ -0,0 +1,355 @@
<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* AJAX endpoint for equipment (Sicherungsautomaten, etc.)
*/
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");
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipment.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmenttype.class.php';
header('Content-Type: application/json; charset=UTF-8');
$action = GETPOST('action', 'aZ09');
$equipmentId = GETPOSTINT('equipment_id');
$carrierId = GETPOSTINT('carrier_id');
$equipment = new Equipment($db);
$response = array('success' => false, 'error' => '');
// Security check
if (!$user->hasRight('kundenkarte', 'read')) {
$response['error'] = 'Permission denied';
echo json_encode($response);
exit;
}
switch ($action) {
case 'get_types':
// Get all equipment types for dropdown
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmenttype.class.php';
$eqType = new EquipmentType($db);
$systemId = GETPOSTINT('system_id');
$types = $eqType->fetchAllBySystem($systemId, 1); // Filter by system if provided, only active
$result = array();
foreach ($types as $t) {
$result[] = array(
'id' => $t->id,
'label' => $t->label,
'label_short' => $t->label_short,
'width_te' => $t->width_te,
'color' => $t->color
);
}
$response['success'] = true;
$response['types'] = $result;
break;
case 'get':
// Get single equipment data
if ($equipmentId > 0 && $equipment->fetch($equipmentId) > 0) {
$response['success'] = true;
$response['equipment'] = array(
'id' => $equipment->id,
'fk_carrier' => $equipment->fk_carrier,
'type_id' => $equipment->fk_equipment_type,
'type_label' => $equipment->type_label,
'label' => $equipment->label,
'position_te' => $equipment->position_te,
'width_te' => $equipment->width_te,
'field_values' => $equipment->getFieldValues(),
'fk_product' => $equipment->fk_product,
'fk_protection' => $equipment->fk_protection,
'protection_label' => $equipment->protection_label
);
} else {
$response['error'] = 'Equipment not found';
}
break;
case 'get_protection_devices':
// Get all protection devices (FI/RCD) for an Anlage
$anlageId = GETPOSTINT('anlage_id');
if ($anlageId > 0) {
$devices = $equipment->fetchProtectionDevices($anlageId);
$result = array();
foreach ($devices as $d) {
$result[] = array(
'id' => $d->id,
'label' => $d->label ?: $d->type_label,
'type_label' => $d->type_label,
'type_label_short' => $d->type_label_short,
'display_label' => ($d->label ?: $d->type_label_short ?: $d->type_label).' (Pos. '.$d->position_te.')'
);
}
$response['success'] = true;
$response['devices'] = $result;
} else {
$response['error'] = 'Missing anlage_id';
}
break;
case 'list':
// List all equipment on a carrier
if ($carrierId > 0) {
$items = $equipment->fetchByCarrier($carrierId);
$result = array();
foreach ($items as $eq) {
$result[] = array(
'id' => $eq->id,
'type_id' => $eq->fk_equipment_type,
'type_label' => $eq->type_label,
'type_label_short' => $eq->type_label_short,
'type_ref' => $eq->type_ref,
'type_color' => $eq->type_color,
'terminals_config' => $eq->terminals_config,
'label' => $eq->label,
'position_te' => $eq->position_te,
'width_te' => $eq->width_te,
'block_label' => $eq->getBlockLabel(),
'block_color' => $eq->getBlockColor(),
'field_values' => $eq->getFieldValues(),
'fk_product' => $eq->fk_product
);
}
$response['success'] = true;
$response['equipment'] = $result;
} else {
$response['error'] = 'Missing carrier_id';
}
break;
case 'create':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
$equipment->fk_carrier = $carrierId;
$equipment->fk_equipment_type = GETPOSTINT('type_id');
$equipment->label = GETPOST('label', 'alphanohtml');
$equipment->position_te = GETPOSTINT('position_te');
$equipment->width_te = GETPOSTINT('width_te');
$equipment->fk_product = GETPOSTINT('fk_product');
$equipment->fk_protection = GETPOSTINT('fk_protection');
$equipment->protection_label = GETPOST('protection_label', 'alphanohtml');
// Field values
$fieldValues = GETPOST('field_values', 'nohtml');
if ($fieldValues) {
$equipment->field_values = $fieldValues;
}
// If no width specified, get from type
if (empty($equipment->width_te)) {
$type = new EquipmentType($db);
if ($type->fetch($equipment->fk_equipment_type) > 0) {
$equipment->width_te = $type->width_te;
} else {
$equipment->width_te = 1;
}
}
// Check carrier and position
$carrier = new EquipmentCarrier($db);
if ($carrier->fetch($carrierId) > 0) {
// If no position specified, find next free position (1-based)
if (empty($equipment->position_te)) {
$equipment->position_te = $carrier->getNextFreePosition($equipment->width_te);
if ($equipment->position_te < 0) {
$response['error'] = 'No free position available on carrier';
break;
}
}
// Check if position is available
if (!$carrier->isPositionAvailable($equipment->position_te, $equipment->width_te)) {
$response['error'] = 'Position not available';
break;
}
}
$result = $equipment->create($user);
if ($result > 0) {
$response['success'] = true;
$response['equipment_id'] = $result;
$response['block_label'] = $equipment->getBlockLabel();
} else {
$response['error'] = $equipment->error;
}
break;
case 'update':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($equipment->fetch($equipmentId) > 0) {
$newPosition = GETPOSTINT('position_te');
$newWidth = GETPOSTINT('width_te') ?: $equipment->width_te;
// Check if new position is available (excluding current equipment)
if ($newPosition != $equipment->position_te || $newWidth != $equipment->width_te) {
$carrier = new EquipmentCarrier($db);
if ($carrier->fetch($equipment->fk_carrier) > 0) {
if (!$carrier->isPositionAvailable($newPosition, $newWidth, $equipmentId)) {
$response['error'] = 'Position not available';
break;
}
}
}
$equipment->fk_equipment_type = GETPOSTINT('type_id') ?: $equipment->fk_equipment_type;
$equipment->label = GETPOST('label', 'alphanohtml');
$equipment->position_te = $newPosition;
$equipment->width_te = $newWidth;
$equipment->fk_product = GETPOSTINT('fk_product');
$equipment->fk_protection = GETPOSTINT('fk_protection');
$equipment->protection_label = GETPOST('protection_label', 'alphanohtml');
$fieldValues = GETPOST('field_values', 'nohtml');
if ($fieldValues) {
$equipment->field_values = $fieldValues;
}
$result = $equipment->update($user);
if ($result > 0) {
$response['success'] = true;
$response['block_label'] = $equipment->getBlockLabel();
} else {
$response['error'] = $equipment->error;
}
} else {
$response['error'] = 'Equipment not found';
}
break;
case 'update_position':
// Quick position update for drag-drop
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($equipment->fetch($equipmentId) > 0) {
$newPosition = GETPOSTINT('position_te');
// Check if new position is available
if ($newPosition != $equipment->position_te) {
$carrier = new EquipmentCarrier($db);
if ($carrier->fetch($equipment->fk_carrier) > 0) {
if (!$carrier->isPositionAvailable($newPosition, $equipment->width_te, $equipmentId)) {
$response['error'] = 'Position not available';
break;
}
}
}
$equipment->position_te = $newPosition;
$result = $equipment->update($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $equipment->error;
}
} else {
$response['error'] = 'Equipment not found';
}
break;
case 'delete':
if (!$user->hasRight('kundenkarte', 'delete')) {
$response['error'] = 'Permission denied';
break;
}
if ($equipment->fetch($equipmentId) > 0) {
$result = $equipment->delete($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $equipment->error;
}
} else {
$response['error'] = 'Equipment not found';
}
break;
case 'duplicate':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($equipment->fetch($equipmentId) > 0) {
$newId = $equipment->duplicate($user);
if ($newId > 0) {
$response['success'] = true;
$response['equipment_id'] = $newId;
// Fetch the new equipment to return its data
$newEquipment = new Equipment($db);
if ($newEquipment->fetch($newId) > 0) {
$response['equipment'] = array(
'id' => $newEquipment->id,
'type_id' => $newEquipment->fk_equipment_type,
'type_label' => $newEquipment->type_label,
'type_color' => $newEquipment->type_color,
'label' => $newEquipment->label,
'position_te' => $newEquipment->position_te,
'width_te' => $newEquipment->width_te,
'block_label' => $newEquipment->getBlockLabel(),
'block_color' => $newEquipment->getBlockColor()
);
}
} else {
$response['error'] = $equipment->error ?: 'Duplication failed';
}
} else {
$response['error'] = 'Equipment not found';
}
break;
case 'move':
// Move equipment to new position (for drag & drop)
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($equipment->fetch($equipmentId) > 0) {
$newPosition = GETPOSTINT('position_te');
$carrier = new EquipmentCarrier($db);
if ($carrier->fetch($equipment->fk_carrier) > 0) {
if ($carrier->isPositionAvailable($newPosition, $equipment->width_te, $equipmentId)) {
$equipment->position_te = $newPosition;
$result = $equipment->update($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $equipment->error;
}
} else {
$response['error'] = 'Position not available';
}
}
} else {
$response['error'] = 'Equipment not found';
}
break;
default:
$response['error'] = 'Unknown action';
}
echo json_encode($response);
$db->close();

188
ajax/equipment_carrier.php Normal file
View file

@ -0,0 +1,188 @@
<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* AJAX endpoint for equipment carriers (Hutschienen)
*/
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");
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipment.class.php';
header('Content-Type: application/json; charset=UTF-8');
$action = GETPOST('action', 'aZ09');
$anlageId = GETPOSTINT('anlage_id');
$carrierId = GETPOSTINT('carrier_id');
$panelId = GETPOSTINT('panel_id');
$carrier = new EquipmentCarrier($db);
$response = array('success' => false, 'error' => '');
// Security check
if (!$user->hasRight('kundenkarte', 'read')) {
$response['error'] = 'Permission denied';
echo json_encode($response);
exit;
}
switch ($action) {
case 'get':
// Get single carrier data
if ($carrierId > 0 && $carrier->fetch($carrierId) > 0) {
$response['success'] = true;
$response['carrier'] = array(
'id' => $carrier->id,
'fk_anlage' => $carrier->fk_anlage,
'fk_panel' => $carrier->fk_panel,
'label' => $carrier->label,
'total_te' => $carrier->total_te,
'position' => $carrier->position,
'panel_label' => $carrier->panel_label
);
} else {
$response['error'] = 'Carrier not found';
}
break;
case 'list':
// List all carriers for an Anlage
if ($anlageId > 0) {
$carriers = $carrier->fetchByAnlage($anlageId);
$result = array();
foreach ($carriers as $c) {
$c->fetchEquipment();
$equipment = array();
foreach ($c->equipment as $eq) {
$equipment[] = array(
'id' => $eq->id,
'type_id' => $eq->fk_equipment_type,
'type_label' => $eq->type_label,
'type_label_short' => $eq->type_label_short,
'type_color' => $eq->type_color,
'label' => $eq->label,
'position_te' => $eq->position_te,
'width_te' => $eq->width_te,
'block_label' => $eq->getBlockLabel(),
'block_color' => $eq->getBlockColor(),
'field_values' => $eq->getFieldValues()
);
}
$result[] = array(
'id' => $c->id,
'label' => $c->label,
'total_te' => $c->total_te,
'used_te' => $c->getUsedTE(),
'free_te' => $c->getFreeTE(),
'position' => $c->position,
'equipment' => $equipment
);
}
$response['success'] = true;
$response['carriers'] = $result;
} else {
$response['error'] = 'Missing anlage_id';
}
break;
case 'create':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
$carrier->fk_anlage = $anlageId;
$carrier->fk_panel = $panelId > 0 ? $panelId : null;
$carrier->label = GETPOST('label', 'alphanohtml');
$carrier->total_te = GETPOSTINT('total_te') ?: 12;
$result = $carrier->create($user);
if ($result > 0) {
$response['success'] = true;
$response['carrier_id'] = $result;
} else {
$response['error'] = $carrier->error;
}
break;
case 'update':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($carrier->fetch($carrierId) > 0) {
$carrier->label = GETPOST('label', 'alphanohtml');
$carrier->total_te = GETPOSTINT('total_te') ?: $carrier->total_te;
$carrier->position = GETPOSTINT('position');
// Allow changing panel (0 or empty = no panel)
$newPanelId = GETPOSTINT('panel_id');
$carrier->fk_panel = $newPanelId > 0 ? $newPanelId : null;
$result = $carrier->update($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $carrier->error;
}
} else {
$response['error'] = 'Carrier not found';
}
break;
case 'delete':
if (!$user->hasRight('kundenkarte', 'delete')) {
$response['error'] = 'Permission denied';
break;
}
if ($carrier->fetch($carrierId) > 0) {
$result = $carrier->delete($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $carrier->error;
}
} else {
$response['error'] = 'Carrier not found';
}
break;
case 'duplicate':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($carrier->fetch($carrierId) > 0) {
// Create a copy of the carrier
$newCarrier = new EquipmentCarrier($db);
$newCarrier->fk_anlage = $carrier->fk_anlage;
$newCarrier->fk_panel = $carrier->fk_panel;
$newCarrier->label = $carrier->label;
$newCarrier->total_te = $carrier->total_te;
$newCarrier->note_private = $carrier->note_private;
$result = $newCarrier->create($user);
if ($result > 0) {
$response['success'] = true;
$response['carrier_id'] = $result;
} else {
$response['error'] = $newCarrier->error;
}
} else {
$response['error'] = 'Carrier not found';
}
break;
default:
$response['error'] = 'Unknown action';
}
echo json_encode($response);
$db->close();

View file

@ -0,0 +1,348 @@
<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* AJAX endpoint for equipment connections (generic for all system types)
*/
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");
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentconnection.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipment.class.php';
header('Content-Type: application/json; charset=UTF-8');
$action = GETPOST('action', 'aZ09');
$connectionId = GETPOSTINT('connection_id');
$carrierId = GETPOSTINT('carrier_id');
$equipmentId = GETPOSTINT('equipment_id');
$connection = new EquipmentConnection($db);
$response = array('success' => false, 'error' => '');
// Security check
if (!$user->hasRight('kundenkarte', 'read')) {
$response['error'] = 'Permission denied';
echo json_encode($response);
exit;
}
switch ($action) {
case 'get':
// Get single connection data
if ($connectionId > 0 && $connection->fetch($connectionId) > 0) {
$response['success'] = true;
$response['connection'] = array(
'id' => $connection->id,
'fk_source' => $connection->fk_source,
'source_terminal' => $connection->source_terminal,
'fk_target' => $connection->fk_target,
'target_terminal' => $connection->target_terminal,
'connection_type' => $connection->connection_type,
'color' => $connection->color,
'output_label' => $connection->output_label,
'medium_type' => $connection->medium_type,
'medium_spec' => $connection->medium_spec,
'medium_length' => $connection->medium_length,
'is_rail' => $connection->is_rail,
'rail_start_te' => $connection->rail_start_te,
'rail_end_te' => $connection->rail_end_te,
'rail_phases' => $connection->rail_phases,
'excluded_te' => $connection->excluded_te,
'fk_carrier' => $connection->fk_carrier,
'position_y' => $connection->position_y,
'source_label' => $connection->source_label,
'target_label' => $connection->target_label
);
} else {
$response['error'] = 'Connection not found';
}
break;
case 'list':
// List all connections for a carrier
if ($carrierId > 0) {
$connections = $connection->fetchByCarrier($carrierId);
$result = array();
foreach ($connections as $c) {
$result[] = array(
'id' => $c->id,
'fk_source' => $c->fk_source,
'source_terminal' => $c->source_terminal,
'source_label' => $c->source_label,
'source_pos' => $c->source_pos,
'source_width' => $c->source_width,
'fk_target' => $c->fk_target,
'target_terminal' => $c->target_terminal,
'target_label' => $c->target_label,
'target_pos' => $c->target_pos,
'connection_type' => $c->connection_type,
'color' => $c->getColor(),
'output_label' => $c->output_label,
'medium_type' => $c->medium_type,
'medium_spec' => $c->medium_spec,
'medium_length' => $c->medium_length,
'is_rail' => $c->is_rail,
'rail_start_te' => $c->rail_start_te,
'rail_end_te' => $c->rail_end_te,
'rail_phases' => $c->rail_phases,
'excluded_te' => $c->excluded_te,
'position_y' => $c->position_y,
'display_label' => $c->getDisplayLabel()
);
}
$response['success'] = true;
$response['connections'] = $result;
} else {
$response['error'] = 'Missing carrier_id';
}
break;
case 'list_outputs':
// List outputs for an equipment
if ($equipmentId > 0) {
$outputs = $connection->fetchOutputs($equipmentId);
$result = array();
foreach ($outputs as $c) {
$result[] = array(
'id' => $c->id,
'connection_type' => $c->connection_type,
'color' => $c->getColor(),
'output_label' => $c->output_label,
'medium_type' => $c->medium_type,
'medium_spec' => $c->medium_spec,
'medium_length' => $c->medium_length,
'display_label' => $c->getDisplayLabel()
);
}
$response['success'] = true;
$response['outputs'] = $result;
} else {
$response['error'] = 'Missing equipment_id';
}
break;
case 'create':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
$connection->fk_source = GETPOSTINT('fk_source');
$connection->source_terminal = GETPOST('source_terminal', 'alphanohtml') ?: 'output';
$connection->source_terminal_id = GETPOST('source_terminal_id', 'alphanohtml');
$connection->fk_target = GETPOSTINT('fk_target');
$connection->target_terminal = GETPOST('target_terminal', 'alphanohtml') ?: 'input';
$connection->target_terminal_id = GETPOST('target_terminal_id', 'alphanohtml');
$connection->connection_type = GETPOST('connection_type', 'alphanohtml');
$connection->color = GETPOST('color', 'alphanohtml');
$connection->output_label = GETPOST('output_label', 'alphanohtml');
$connection->medium_type = GETPOST('medium_type', 'alphanohtml');
$connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
$connection->medium_length = GETPOST('medium_length', 'alphanohtml');
$connection->is_rail = GETPOSTINT('is_rail');
$connection->rail_start_te = GETPOSTINT('rail_start_te');
$connection->rail_end_te = GETPOSTINT('rail_end_te');
$connection->fk_carrier = $carrierId;
$connection->position_y = GETPOSTINT('position_y');
$result = $connection->create($user);
if ($result > 0) {
$response['success'] = true;
$response['connection_id'] = $result;
} else {
$response['error'] = $connection->error ?: 'Create failed';
}
break;
case 'update':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($connection->fetch($connectionId) > 0) {
$connection->fk_source = GETPOSTINT('fk_source');
$connection->source_terminal = GETPOST('source_terminal', 'alphanohtml') ?: 'output';
$connection->fk_target = GETPOSTINT('fk_target');
$connection->target_terminal = GETPOST('target_terminal', 'alphanohtml') ?: 'input';
$connection->connection_type = GETPOST('connection_type', 'alphanohtml');
$connection->color = GETPOST('color', 'alphanohtml');
$connection->output_label = GETPOST('output_label', 'alphanohtml');
$connection->medium_type = GETPOST('medium_type', 'alphanohtml');
$connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
$connection->medium_length = GETPOST('medium_length', 'alphanohtml');
$connection->is_rail = GETPOSTINT('is_rail');
$connection->rail_start_te = GETPOSTINT('rail_start_te');
$connection->rail_end_te = GETPOSTINT('rail_end_te');
$connection->rail_phases = GETPOST('rail_phases', 'alphanohtml');
$connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
$connection->position_y = GETPOSTINT('position_y');
$result = $connection->update($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $connection->error ?: 'Update failed';
}
} else {
$response['error'] = 'Connection not found';
}
break;
case 'delete':
if (!$user->hasRight('kundenkarte', 'delete')) {
$response['error'] = 'Permission denied';
break;
}
if ($connection->fetch($connectionId) > 0) {
$result = $connection->delete($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $connection->error ?: 'Delete failed';
}
} else {
$response['error'] = 'Connection not found';
}
break;
case 'create_rail':
// Create a rail/bar connection spanning multiple equipment
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
$connection->is_rail = 1;
$connection->connection_type = GETPOST('connection_type', 'alphanohtml');
$connection->color = GETPOST('color', 'alphanohtml');
$connection->rail_start_te = GETPOSTINT('rail_start_te');
$connection->rail_end_te = GETPOSTINT('rail_end_te');
$connection->rail_phases = GETPOST('rail_phases', 'alphanohtml');
$connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
$connection->fk_carrier = $carrierId;
$connection->position_y = GETPOSTINT('position_y');
$result = $connection->create($user);
if ($result > 0) {
$response['success'] = true;
$response['connection_id'] = $result;
} else {
$response['error'] = $connection->error ?: 'Create failed';
}
break;
case 'create_output':
// Create an output connection
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
$connection->fk_source = $equipmentId;
$connection->source_terminal = 'output';
$connection->fk_target = null;
$connection->connection_type = GETPOST('connection_type', 'alphanohtml');
$connection->color = GETPOST('color', 'alphanohtml');
$connection->output_label = GETPOST('output_label', 'alphanohtml');
$connection->medium_type = GETPOST('medium_type', 'alphanohtml');
$connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
$connection->medium_length = GETPOST('medium_length', 'alphanohtml');
$connection->fk_carrier = $carrierId;
$connection->position_y = 0;
$result = $connection->create($user);
if ($result > 0) {
$response['success'] = true;
$response['connection_id'] = $result;
} else {
$response['error'] = $connection->error ?: 'Create failed';
}
break;
case 'list_all':
// List all connections for an anlage (across all carriers)
$anlageId = GETPOSTINT('anlage_id');
if ($anlageId > 0) {
// Get all carriers for this anlage
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
$carrierObj = new EquipmentCarrier($db);
$carriers = $carrierObj->fetchByAnlage($anlageId);
$allConnections = array();
foreach ($carriers as $carrier) {
$connections = $connection->fetchByCarrier($carrier->id);
foreach ($connections as $c) {
$allConnections[] = array(
'id' => $c->id,
'fk_source' => $c->fk_source,
'source_terminal' => $c->source_terminal,
'source_terminal_id' => $c->source_terminal_id,
'source_label' => $c->source_label,
'fk_target' => $c->fk_target,
'target_terminal' => $c->target_terminal,
'target_terminal_id' => $c->target_terminal_id,
'target_label' => $c->target_label,
'connection_type' => $c->connection_type,
'color' => $c->getColor(),
'output_label' => $c->output_label,
'medium_type' => $c->medium_type,
'medium_spec' => $c->medium_spec,
'medium_length' => $c->medium_length,
'is_rail' => $c->is_rail,
'fk_carrier' => $c->fk_carrier
);
}
}
$response['success'] = true;
$response['connections'] = $allConnections;
} else {
$response['error'] = 'Missing anlage_id';
}
break;
case 'clear_all':
// Delete all connections for an anlage
if (!$user->hasRight('kundenkarte', 'delete')) {
$response['error'] = 'Permission denied';
break;
}
$anlageId = GETPOSTINT('anlage_id');
if ($anlageId > 0) {
// Get all carriers for this anlage
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
$carrierObj = new EquipmentCarrier($db);
$carriers = $carrierObj->fetchByAnlage($anlageId);
$deletedCount = 0;
foreach ($carriers as $carrier) {
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection WHERE fk_carrier = ".((int)$carrier->id);
$resql = $db->query($sql);
if ($resql) {
$deletedCount += $db->affected_rows($resql);
}
}
$response['success'] = true;
$response['deleted_count'] = $deletedCount;
} else {
$response['error'] = 'Missing anlage_id';
}
break;
default:
$response['error'] = 'Unknown action';
}
echo json_encode($response);
$db->close();

201
ajax/equipment_panel.php Normal file
View file

@ -0,0 +1,201 @@
<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* AJAX endpoint for equipment panels (Schaltschrankfelder)
*/
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");
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentpanel.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
header('Content-Type: application/json; charset=UTF-8');
$action = GETPOST('action', 'aZ09');
$panelId = GETPOSTINT('panel_id');
$anlageId = GETPOSTINT('anlage_id');
$panel = new EquipmentPanel($db);
$response = array('success' => false, 'error' => '');
// Security check
if (!$user->hasRight('kundenkarte', 'read')) {
$response['error'] = 'Permission denied';
echo json_encode($response);
exit;
}
switch ($action) {
case 'get':
// Get single panel data
if ($panelId > 0 && $panel->fetch($panelId) > 0) {
$response['success'] = true;
$response['panel'] = array(
'id' => $panel->id,
'fk_anlage' => $panel->fk_anlage,
'label' => $panel->label,
'position' => $panel->position,
'note_private' => $panel->note_private,
'status' => $panel->status
);
} else {
$response['error'] = 'Panel not found';
}
break;
case 'list':
// List all panels for an Anlage
if ($anlageId > 0) {
$panels = $panel->fetchByAnlage($anlageId);
$result = array();
foreach ($panels as $p) {
$result[] = array(
'id' => $p->id,
'fk_anlage' => $p->fk_anlage,
'label' => $p->label,
'position' => $p->position,
'status' => $p->status
);
}
$response['success'] = true;
$response['panels'] = $result;
} else {
$response['error'] = 'Missing anlage_id';
}
break;
case 'create':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
$panel->fk_anlage = $anlageId;
$panel->label = GETPOST('label', 'alphanohtml');
$panel->position = GETPOSTINT('position');
$panel->note_private = GETPOST('note_private', 'restricthtml');
$result = $panel->create($user);
if ($result > 0) {
$response['success'] = true;
$response['panel_id'] = $result;
$response['label'] = $panel->label;
} else {
$response['error'] = $panel->error;
}
break;
case 'update':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($panel->fetch($panelId) > 0) {
$panel->label = GETPOST('label', 'alphanohtml') ?: $panel->label;
$panel->position = GETPOSTINT('position') ?: $panel->position;
$panel->note_private = GETPOST('note_private', 'restricthtml');
$result = $panel->update($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $panel->error;
}
} else {
$response['error'] = 'Panel not found';
}
break;
case 'delete':
if (!$user->hasRight('kundenkarte', 'delete')) {
$response['error'] = 'Permission denied';
break;
}
if ($panel->fetch($panelId) > 0) {
$result = $panel->delete($user);
if ($result > 0) {
$response['success'] = true;
} else {
$response['error'] = $panel->error;
}
} else {
$response['error'] = 'Panel not found';
}
break;
case 'list_with_carriers':
// List all panels with their carriers for an Anlage
if ($anlageId > 0) {
$panels = $panel->fetchByAnlage($anlageId);
$result = array();
foreach ($panels as $p) {
$panelData = array(
'id' => $p->id,
'fk_anlage' => $p->fk_anlage,
'label' => $p->label,
'position' => $p->position,
'status' => $p->status,
'carriers' => array()
);
// Fetch carriers for this panel
$p->fetchCarriers();
foreach ($p->carriers as $c) {
$panelData['carriers'][] = array(
'id' => $c->id,
'label' => $c->label,
'total_te' => $c->total_te,
'position' => $c->position
);
}
$result[] = $panelData;
}
$response['success'] = true;
$response['panels'] = $result;
} else {
$response['error'] = 'Missing anlage_id';
}
break;
case 'duplicate':
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = 'Permission denied';
break;
}
if ($panel->fetch($panelId) > 0) {
// Create a copy of the panel
$newPanel = new EquipmentPanel($db);
$newPanel->fk_anlage = $panel->fk_anlage;
$newPanel->label = $panel->label.' (Kopie)';
$newPanel->note_private = $panel->note_private;
$result = $newPanel->create($user);
if ($result > 0) {
$response['success'] = true;
$response['panel_id'] = $result;
} else {
$response['error'] = $newPanel->error;
}
} else {
$response['error'] = 'Panel not found';
}
break;
default:
$response['error'] = 'Unknown action';
}
echo json_encode($response);
$db->close();

View file

@ -0,0 +1,74 @@
<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* AJAX endpoint to get equipment type fields
*/
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");
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmenttype.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipment.class.php';
header('Content-Type: application/json; charset=UTF-8');
$typeId = GETPOSTINT('type_id');
$equipmentId = GETPOSTINT('equipment_id');
$response = array('success' => false, 'fields' => array());
if (!$user->hasRight('kundenkarte', 'read')) {
$response['error'] = 'Permission denied';
echo json_encode($response);
exit;
}
if ($typeId > 0) {
$type = new EquipmentType($db);
if ($type->fetch($typeId) > 0) {
$fields = $type->fetchFields(1);
// Get existing values if editing
$existingValues = array();
if ($equipmentId > 0) {
$equipment = new Equipment($db);
if ($equipment->fetch($equipmentId) > 0) {
$existingValues = $equipment->getFieldValues();
}
}
$result = array();
foreach ($fields as $field) {
$value = isset($existingValues[$field->field_code]) ? $existingValues[$field->field_code] : $field->field_default;
$result[] = array(
'code' => $field->field_code,
'label' => $field->field_label,
'type' => $field->field_type,
'options' => $field->field_options,
'required' => $field->required,
'show_on_block' => $field->show_on_block,
'value' => $value
);
}
$response['success'] = true;
$response['fields'] = $result;
$response['type'] = array(
'id' => $type->id,
'label' => $type->label,
'label_short' => $type->label_short,
'width_te' => $type->width_te,
'color' => $type->color
);
}
}
echo json_encode($response);
$db->close();

View file

@ -25,6 +25,7 @@ class AnlageType extends CommonObject
public $can_have_children;
public $can_be_nested;
public $allowed_parent_types;
public $can_have_equipment;
public $picto;
public $color;
@ -72,7 +73,7 @@ class AnlageType extends CommonObject
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, ref, label, label_short, description, fk_system,";
$sql .= " can_have_children, can_be_nested, allowed_parent_types,";
$sql .= " can_have_children, can_be_nested, allowed_parent_types, can_have_equipment,";
$sql .= " picto, color, is_system, position, active,";
$sql .= " date_creation, fk_user_creat";
$sql .= ") VALUES (";
@ -85,6 +86,7 @@ class AnlageType extends CommonObject
$sql .= ", ".((int) $this->can_have_children);
$sql .= ", ".((int) $this->can_be_nested);
$sql .= ", ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL");
$sql .= ", ".((int) $this->can_have_equipment);
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
$sql .= ", 0"; // is_system = 0 for user-created
@ -141,6 +143,7 @@ class AnlageType extends CommonObject
$this->can_have_children = $obj->can_have_children;
$this->can_be_nested = $obj->can_be_nested;
$this->allowed_parent_types = $obj->allowed_parent_types;
$this->can_have_equipment = $obj->can_have_equipment ?? 0;
$this->picto = $obj->picto;
$this->color = $obj->color;
$this->is_system = $obj->is_system;
@ -186,6 +189,7 @@ class AnlageType extends CommonObject
$sql .= ", can_have_children = ".((int) $this->can_have_children);
$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 .= ", can_have_equipment = ".((int) $this->can_have_equipment);
$sql .= ", picto = ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
$sql .= ", position = ".((int) $this->position);
@ -295,6 +299,7 @@ class AnlageType extends CommonObject
$type->can_have_children = $obj->can_have_children;
$type->can_be_nested = $obj->can_be_nested;
$type->allowed_parent_types = $obj->allowed_parent_types;
$type->can_have_equipment = $obj->can_have_equipment ?? 0;
$type->picto = $obj->picto;
$type->is_system = $obj->is_system;
$type->position = $obj->position;

494
class/equipment.class.php Normal file
View file

@ -0,0 +1,494 @@
<?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.
*/
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmenttype.class.php';
/**
* Class Equipment
* Manages equipment instances (Sicherungsautomaten, FI-Schalter, etc.)
*/
class Equipment extends CommonObject
{
public $element = 'equipment';
public $table_element = 'kundenkarte_equipment';
public $fk_carrier;
public $fk_equipment_type;
public $label;
public $position_te;
public $width_te;
public $field_values;
public $fk_product;
public $fk_protection; // FK to protection device (FI/RCD)
public $protection_label; // Label shown above equipment when in protection group
public $note_private;
public $status;
public $date_creation;
public $fk_user_creat;
public $fk_user_modif;
// Loaded objects
public $type; // EquipmentType object
public $type_label;
public $type_label_short;
public $type_color;
public $type_picto;
public $product_ref;
public $product_label;
public $protection_device_label; // Label of the protection device
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Create object in database
*
* @param User $user User that creates
* @return int Return integer <0 if KO, Id of created object if OK
*/
public function create($user)
{
global $conf;
$error = 0;
$now = dol_now();
if (empty($this->fk_carrier) || empty($this->fk_equipment_type)) {
$this->error = 'ErrorMissingParameters';
return -1;
}
// Get default width from type if not set
if (empty($this->width_te)) {
$type = new EquipmentType($this->db);
if ($type->fetch($this->fk_equipment_type) > 0) {
$this->width_te = $type->width_te;
} else {
$this->width_te = 1;
}
}
$this->db->begin();
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, fk_carrier, fk_equipment_type, label,";
$sql .= " position_te, width_te, field_values, fk_product,";
$sql .= " fk_protection, protection_label,";
$sql .= " note_private, status,";
$sql .= " date_creation, fk_user_creat";
$sql .= ") VALUES (";
$sql .= ((int) $conf->entity);
$sql .= ", ".((int) $this->fk_carrier);
$sql .= ", ".((int) $this->fk_equipment_type);
$sql .= ", ".($this->label ? "'".$this->db->escape($this->label)."'" : "NULL");
$sql .= ", ".((int) $this->position_te);
$sql .= ", ".((int) $this->width_te);
$sql .= ", ".($this->field_values ? "'".$this->db->escape($this->field_values)."'" : "NULL");
$sql .= ", ".($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL");
$sql .= ", ".($this->fk_protection > 0 ? ((int) $this->fk_protection) : "NULL");
$sql .= ", ".($this->protection_label ? "'".$this->db->escape($this->protection_label)."'" : "NULL");
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", ".((int) ($this->status !== null ? $this->status : 1));
$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;
}
}
/**
* Load object from database
*
* @param int $id ID of record
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
*/
public function fetch($id)
{
$sql = "SELECT e.*, t.label as type_label, t.label_short as type_label_short,";
$sql .= " t.color as type_color, t.picto as type_picto,";
$sql .= " p.ref as product_ref, p.label as product_label,";
$sql .= " prot.label as protection_device_label";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as e";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_type as t ON e.fk_equipment_type = t.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON e.fk_product = p.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as prot ON e.fk_protection = prot.rowid";
$sql .= " WHERE e.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->entity = $obj->entity;
$this->fk_carrier = $obj->fk_carrier;
$this->fk_equipment_type = $obj->fk_equipment_type;
$this->label = $obj->label;
$this->position_te = $obj->position_te;
$this->width_te = $obj->width_te;
$this->field_values = $obj->field_values;
$this->fk_product = $obj->fk_product;
$this->fk_protection = $obj->fk_protection;
$this->protection_label = $obj->protection_label;
$this->note_private = $obj->note_private;
$this->status = $obj->status;
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->fk_user_creat = $obj->fk_user_creat;
$this->fk_user_modif = $obj->fk_user_modif;
$this->type_label = $obj->type_label;
$this->type_label_short = $obj->type_label_short;
$this->type_color = $obj->type_color;
$this->type_picto = $obj->type_picto;
$this->product_ref = $obj->product_ref;
$this->product_label = $obj->product_label;
$this->protection_device_label = $obj->protection_device_label;
$this->db->free($resql);
return 1;
} else {
$this->db->free($resql);
return 0;
}
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Update object in database
*
* @param User $user User that modifies
* @return int Return integer <0 if KO, >0 if OK
*/
public function update($user)
{
$error = 0;
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
$sql .= " fk_equipment_type = ".((int) $this->fk_equipment_type);
$sql .= ", label = ".($this->label ? "'".$this->db->escape($this->label)."'" : "NULL");
$sql .= ", position_te = ".((int) $this->position_te);
$sql .= ", width_te = ".((int) $this->width_te);
$sql .= ", field_values = ".($this->field_values ? "'".$this->db->escape($this->field_values)."'" : "NULL");
$sql .= ", fk_product = ".($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL");
$sql .= ", fk_protection = ".($this->fk_protection > 0 ? ((int) $this->fk_protection) : "NULL");
$sql .= ", protection_label = ".($this->protection_label ? "'".$this->db->escape($this->protection_label)."'" : "NULL");
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", status = ".((int) $this->status);
$sql .= ", fk_user_modif = ".((int) $user->id);
$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;
}
}
/**
* Delete object in database
*
* @param User $user User that deletes
* @return int Return integer <0 if KO, >0 if OK
*/
public function delete($user)
{
$error = 0;
$this->db->begin();
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." 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;
}
}
/**
* Fetch all equipment on a carrier
*
* @param int $carrierId Carrier ID
* @param int $activeOnly Only active equipment
* @return array Array of Equipment objects
*/
public function fetchByCarrier($carrierId, $activeOnly = 1)
{
$results = array();
$sql = "SELECT e.*, t.label as type_label, t.label_short as type_label_short,";
$sql .= " t.ref as type_ref, t.color as type_color, t.picto as type_picto,";
$sql .= " t.terminals_config as terminals_config,";
$sql .= " prot.label as protection_device_label";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as e";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_type as t ON e.fk_equipment_type = t.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as prot ON e.fk_protection = prot.rowid";
$sql .= " WHERE e.fk_carrier = ".((int) $carrierId);
if ($activeOnly) {
$sql .= " AND e.status = 1";
}
$sql .= " ORDER BY e.position_te ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$eq = new Equipment($this->db);
$eq->id = $obj->rowid;
$eq->entity = $obj->entity;
$eq->fk_carrier = $obj->fk_carrier;
$eq->fk_equipment_type = $obj->fk_equipment_type;
$eq->label = $obj->label;
$eq->position_te = $obj->position_te;
$eq->width_te = $obj->width_te;
$eq->field_values = $obj->field_values;
$eq->fk_product = $obj->fk_product;
$eq->fk_protection = $obj->fk_protection;
$eq->protection_label = $obj->protection_label;
$eq->note_private = $obj->note_private;
$eq->status = $obj->status;
$eq->type_label = $obj->type_label;
$eq->type_label_short = $obj->type_label_short;
$eq->type_ref = $obj->type_ref;
$eq->type_color = $obj->type_color;
$eq->type_picto = $obj->type_picto;
$eq->terminals_config = $obj->terminals_config;
$eq->protection_device_label = $obj->protection_device_label;
$results[] = $eq;
}
$this->db->free($resql);
}
return $results;
}
/**
* Fetch protection devices (FI/RCD) for an Anlage
* Protection devices are equipment types that can protect other equipment
*
* @param int $anlageId Anlage ID
* @return array Array of Equipment objects that are protection devices
*/
public function fetchProtectionDevices($anlageId)
{
$results = array();
// Get all equipment for this anlage that have type with is_protection = 1
// Or equipment types that are typically protection devices (FI, RCD, etc.)
$sql = "SELECT e.*, t.label as type_label, t.label_short as type_label_short,";
$sql .= " t.color as type_color, t.ref as type_ref, c.fk_anlage";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as e";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_type as t ON e.fk_equipment_type = t.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_carrier as c ON e.fk_carrier = c.rowid";
$sql .= " WHERE c.fk_anlage = ".((int) $anlageId);
$sql .= " AND e.status = 1";
// Filter for protection device types (FI, RCD, FI_LS, etc.)
$sql .= " AND (t.ref LIKE '%FI%' OR t.ref LIKE '%RCD%' OR t.ref LIKE 'RCBO%')";
$sql .= " ORDER BY c.position ASC, e.position_te ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$eq = new Equipment($this->db);
$eq->id = $obj->rowid;
$eq->fk_carrier = $obj->fk_carrier;
$eq->fk_equipment_type = $obj->fk_equipment_type;
$eq->label = $obj->label;
$eq->position_te = $obj->position_te;
$eq->width_te = $obj->width_te;
$eq->type_label = $obj->type_label;
$eq->type_label_short = $obj->type_label_short;
$eq->type_color = $obj->type_color;
$results[] = $eq;
}
$this->db->free($resql);
}
return $results;
}
/**
* Duplicate this equipment (for "+" button)
*
* @param User $user User that creates
* @param int $carrierId Target carrier (0 = same carrier)
* @param int $position Target position (-1 = auto)
* @return int Return integer <0 if KO, Id of new object if OK
*/
public function duplicate($user, $carrierId = 0, $position = -1)
{
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
$newEquipment = new Equipment($this->db);
$newEquipment->fk_carrier = $carrierId > 0 ? $carrierId : $this->fk_carrier;
$newEquipment->fk_equipment_type = $this->fk_equipment_type;
$newEquipment->label = $this->label;
$newEquipment->width_te = $this->width_te;
$newEquipment->field_values = $this->field_values;
$newEquipment->fk_product = $this->fk_product;
$newEquipment->note_private = $this->note_private;
$newEquipment->status = 1;
// Find position
if ($position >= 0) {
$newEquipment->position_te = $position;
} else {
// Auto-find next position after this equipment
$carrier = new EquipmentCarrier($this->db);
$carrier->fetch($newEquipment->fk_carrier);
$carrier->fetchEquipment();
// Try position right after this element
$tryPos = $this->position_te + $this->width_te;
if ($carrier->isPositionAvailable($tryPos, $this->width_te)) {
$newEquipment->position_te = $tryPos;
} else {
// Find any free position
$newEquipment->position_te = $carrier->getNextFreePosition($this->width_te);
if ($newEquipment->position_te < 0) {
$this->error = 'ErrorNoSpaceOnCarrier';
return -1;
}
}
}
return $newEquipment->create($user);
}
/**
* Get field values as array
*
* @return array
*/
public function getFieldValues()
{
if (empty($this->field_values)) {
return array();
}
$values = json_decode($this->field_values, true);
return is_array($values) ? $values : array();
}
/**
* Set field values from array
*
* @param array $values Key-value pairs
* @return void
*/
public function setFieldValues($values)
{
$this->field_values = json_encode($values);
}
/**
* Get single field value
*
* @param string $fieldCode Field code
* @return mixed|null
*/
public function getFieldValue($fieldCode)
{
$values = $this->getFieldValues();
return isset($values[$fieldCode]) ? $values[$fieldCode] : null;
}
/**
* Get label for SVG block display
* Combines fields with show_on_block = 1
*
* @return string Label to display on block (e.g. "B16")
*/
public function getBlockLabel()
{
$type = new EquipmentType($this->db);
if ($type->fetch($this->fk_equipment_type) <= 0) {
return $this->type_label_short ?: '';
}
$blockFields = $type->getBlockFields();
if (empty($blockFields)) {
return $this->type_label_short ?: '';
}
$values = $this->getFieldValues();
$parts = array();
foreach ($blockFields as $field) {
if (isset($values[$field->field_code]) && $values[$field->field_code] !== '') {
$parts[] = $values[$field->field_code];
}
}
if (empty($parts)) {
return $this->type_label_short ?: '';
}
return implode('', $parts);
}
/**
* Get color for SVG block
*
* @return string Hex color code
*/
public function getBlockColor()
{
if (!empty($this->type_color)) {
return $this->type_color;
}
// Default color
return '#3498db';
}
}

View file

@ -0,0 +1,426 @@
<?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 EquipmentCarrier
* Manages equipment carriers (Hutschienen/Traeger)
*/
class EquipmentCarrier extends CommonObject
{
public $element = 'equipmentcarrier';
public $table_element = 'kundenkarte_equipment_carrier';
public $fk_anlage;
public $fk_panel;
public $label;
public $total_te = 12;
public $position;
public $note_private;
public $status;
public $date_creation;
public $fk_user_creat;
public $fk_user_modif;
// Loaded objects
public $equipment = array();
public $anlage_label;
public $panel_label;
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Create object in database
*
* @param User $user User that creates
* @return int Return integer <0 if KO, Id of created object if OK
*/
public function create($user)
{
global $conf;
$error = 0;
$now = dol_now();
if (empty($this->fk_anlage) || empty($this->label)) {
$this->error = 'ErrorMissingParameters';
return -1;
}
// Get next position
if (empty($this->position)) {
$sql = "SELECT MAX(position) as maxpos FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_anlage = ".((int) $this->fk_anlage);
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
$this->position = ($obj->maxpos !== null) ? $obj->maxpos + 1 : 0;
}
}
$this->db->begin();
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, fk_anlage, fk_panel, label, total_te, position, note_private, status,";
$sql .= " date_creation, fk_user_creat";
$sql .= ") VALUES (";
$sql .= ((int) $conf->entity);
$sql .= ", ".((int) $this->fk_anlage);
$sql .= ", ".($this->fk_panel > 0 ? ((int) $this->fk_panel) : "NULL");
$sql .= ", '".$this->db->escape($this->label)."'";
$sql .= ", ".((int) ($this->total_te > 0 ? $this->total_te : 12));
$sql .= ", ".((int) $this->position);
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", ".((int) ($this->status !== null ? $this->status : 1));
$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;
}
}
/**
* Load object from database
*
* @param int $id ID of record
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
*/
public function fetch($id)
{
$sql = "SELECT c.*, a.label as anlage_label, p.label as panel_label";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage as a ON c.fk_anlage = a.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_panel as p ON c.fk_panel = p.rowid";
$sql .= " WHERE c.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->entity = $obj->entity;
$this->fk_anlage = $obj->fk_anlage;
$this->fk_panel = $obj->fk_panel;
$this->label = $obj->label;
$this->total_te = $obj->total_te;
$this->position = $obj->position;
$this->note_private = $obj->note_private;
$this->status = $obj->status;
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->fk_user_creat = $obj->fk_user_creat;
$this->fk_user_modif = $obj->fk_user_modif;
$this->anlage_label = $obj->anlage_label;
$this->panel_label = $obj->panel_label;
$this->db->free($resql);
return 1;
} else {
$this->db->free($resql);
return 0;
}
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Update object in database
*
* @param User $user User that modifies
* @return int Return integer <0 if KO, >0 if OK
*/
public function update($user)
{
$error = 0;
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
$sql .= " label = '".$this->db->escape($this->label)."'";
$sql .= ", fk_panel = ".($this->fk_panel > 0 ? ((int) $this->fk_panel) : "NULL");
$sql .= ", total_te = ".((int) ($this->total_te > 0 ? $this->total_te : 12));
$sql .= ", position = ".((int) $this->position);
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", status = ".((int) $this->status);
$sql .= ", fk_user_modif = ".((int) $user->id);
$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;
}
}
/**
* Delete object in database
*
* @param User $user User that deletes
* @return int Return integer <0 if KO, >0 if OK
*/
public function delete($user)
{
$error = 0;
$this->db->begin();
// Equipment is deleted via CASCADE
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." 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;
}
}
/**
* Fetch all carriers for a Panel
*
* @param int $panelId Panel ID
* @param int $activeOnly Only active carriers
* @return array Array of EquipmentCarrier objects
*/
public function fetchByPanel($panelId, $activeOnly = 1)
{
$results = array();
$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_panel = ".((int) $panelId);
if ($activeOnly) {
$sql .= " AND status = 1";
}
$sql .= " ORDER BY position ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$carrier = new EquipmentCarrier($this->db);
$carrier->id = $obj->rowid;
$carrier->entity = $obj->entity;
$carrier->fk_anlage = $obj->fk_anlage;
$carrier->fk_panel = $obj->fk_panel;
$carrier->label = $obj->label;
$carrier->total_te = $obj->total_te;
$carrier->position = $obj->position;
$carrier->note_private = $obj->note_private;
$carrier->status = $obj->status;
$results[] = $carrier;
}
$this->db->free($resql);
}
return $results;
}
/**
* Fetch all carriers for an Anlage
*
* @param int $anlageId Anlage ID
* @param int $activeOnly Only active carriers
* @return array Array of EquipmentCarrier objects
*/
public function fetchByAnlage($anlageId, $activeOnly = 1)
{
$results = array();
$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_anlage = ".((int) $anlageId);
if ($activeOnly) {
$sql .= " AND status = 1";
}
$sql .= " ORDER BY position ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$carrier = new EquipmentCarrier($this->db);
$carrier->id = $obj->rowid;
$carrier->entity = $obj->entity;
$carrier->fk_anlage = $obj->fk_anlage;
$carrier->fk_panel = $obj->fk_panel;
$carrier->label = $obj->label;
$carrier->total_te = $obj->total_te;
$carrier->position = $obj->position;
$carrier->note_private = $obj->note_private;
$carrier->status = $obj->status;
$results[] = $carrier;
}
$this->db->free($resql);
}
return $results;
}
/**
* Fetch all equipment on this carrier
*
* @return array Array of Equipment objects
*/
public function fetchEquipment()
{
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipment.class.php';
$equipment = new Equipment($this->db);
$this->equipment = $equipment->fetchByCarrier($this->id);
return $this->equipment;
}
/**
* Get array of occupied TE slots
*
* @return array Array of occupied slot numbers (0-based)
*/
public function getOccupiedSlots()
{
$occupied = array();
if (empty($this->equipment)) {
$this->fetchEquipment();
}
foreach ($this->equipment as $eq) {
for ($i = $eq->position_te; $i < $eq->position_te + $eq->width_te; $i++) {
$occupied[] = $i;
}
}
return $occupied;
}
/**
* Get used TE count
*
* @return int Number of used TE
*/
public function getUsedTE()
{
return count($this->getOccupiedSlots());
}
/**
* Get free TE count
*
* @return int Number of free TE
*/
public function getFreeTE()
{
return $this->total_te - $this->getUsedTE();
}
/**
* Find next free position for given width
*
* @param int $width Width in TE needed
* @return int Position (1-based) or -1 if no space
*/
public function getNextFreePosition($width = 1)
{
$occupied = $this->getOccupiedSlots();
// Positions are 1-based (1 to total_te)
for ($pos = 1; $pos <= $this->total_te - $width + 1; $pos++) {
$fits = true;
for ($i = $pos; $i < $pos + $width; $i++) {
if (in_array($i, $occupied)) {
$fits = false;
break;
}
}
if ($fits) {
return $pos;
}
}
return -1; // No space available
}
/**
* Check if position is available for given width
*
* @param int $position Start position (1-based)
* @param int $width Width in TE
* @param int $excludeEquipmentId Equipment ID to exclude (for updates)
* @return bool True if position is available
*/
public function isPositionAvailable($position, $width, $excludeEquipmentId = 0)
{
// Check bounds (positions are 1-based)
if ($position < 1 || $position + $width - 1 > $this->total_te) {
return false;
}
if (empty($this->equipment)) {
$this->fetchEquipment();
}
foreach ($this->equipment as $eq) {
if ($excludeEquipmentId > 0 && $eq->id == $excludeEquipmentId) {
continue;
}
// Check for overlap
$eqStart = $eq->position_te;
$eqEnd = $eq->position_te + $eq->width_te - 1;
$newStart = $position;
$newEnd = $position + $width - 1;
if ($newStart <= $eqEnd && $newEnd >= $eqStart) {
return false; // Overlap
}
}
return true;
}
}

View file

@ -0,0 +1,419 @@
<?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 EquipmentConnection
* Manages connections between equipment (generic for all system types)
*/
class EquipmentConnection extends CommonObject
{
public $element = 'equipmentconnection';
public $table_element = 'kundenkarte_equipment_connection';
public $fk_source;
public $source_terminal = 'output';
public $source_terminal_id;
public $fk_target;
public $target_terminal = 'input';
public $target_terminal_id;
// Connection properties
public $connection_type;
public $color;
// Output/endpoint info
public $output_label;
// Medium info (cable, wire, etc.)
public $medium_type;
public $medium_spec;
public $medium_length;
// Rail info
public $is_rail = 0;
public $rail_start_te;
public $rail_end_te;
public $rail_phases; // '3P', '3P+N', 'L1', 'L1N', etc.
public $excluded_te; // Comma-separated TE positions to exclude (gaps for FI)
public $fk_carrier;
public $position_y = 0;
public $note_private;
public $status = 1;
public $date_creation;
public $fk_user_creat;
public $fk_user_modif;
// Loaded objects
public $source_label;
public $target_label;
public $carrier_label;
public $source_pos;
public $source_width;
public $target_pos;
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Create object in database
*
* @param User $user User that creates
* @return int Return integer <0 if KO, Id of created object if OK
*/
public function create($user)
{
global $conf;
$error = 0;
$now = dol_now();
$this->db->begin();
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, fk_source, source_terminal, source_terminal_id, fk_target, target_terminal, target_terminal_id,";
$sql .= " connection_type, color, output_label,";
$sql .= " medium_type, medium_spec, medium_length,";
$sql .= " is_rail, rail_start_te, rail_end_te, rail_phases, excluded_te, fk_carrier, position_y,";
$sql .= " note_private, status, date_creation, fk_user_creat";
$sql .= ") VALUES (";
$sql .= ((int) $conf->entity);
$sql .= ", ".($this->fk_source > 0 ? ((int) $this->fk_source) : "NULL");
$sql .= ", '".$this->db->escape($this->source_terminal ?: 'output')."'";
$sql .= ", ".($this->source_terminal_id ? "'".$this->db->escape($this->source_terminal_id)."'" : "NULL");
$sql .= ", ".($this->fk_target > 0 ? ((int) $this->fk_target) : "NULL");
$sql .= ", '".$this->db->escape($this->target_terminal ?: 'input')."'";
$sql .= ", ".($this->target_terminal_id ? "'".$this->db->escape($this->target_terminal_id)."'" : "NULL");
$sql .= ", ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "NULL");
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
$sql .= ", ".($this->output_label ? "'".$this->db->escape($this->output_label)."'" : "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_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
$sql .= ", ".((int) $this->is_rail);
$sql .= ", ".($this->rail_start_te > 0 ? ((int) $this->rail_start_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->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
$sql .= ", ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
$sql .= ", ".((int) $this->position_y);
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", ".((int) $this->status);
$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;
}
}
/**
* Load object from database
*
* @param int $id ID of record
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
*/
public function fetch($id)
{
$sql = "SELECT c.*, ";
$sql .= " src.label as source_label, tgt.label as target_label, car.label as carrier_label";
$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 tgt ON c.fk_target = tgt.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_carrier as car ON c.fk_carrier = car.rowid";
$sql .= " WHERE c.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->entity = $obj->entity;
$this->fk_source = $obj->fk_source;
$this->source_terminal = $obj->source_terminal;
$this->source_terminal_id = $obj->source_terminal_id;
$this->fk_target = $obj->fk_target;
$this->target_terminal = $obj->target_terminal;
$this->target_terminal_id = $obj->target_terminal_id;
$this->connection_type = $obj->connection_type;
$this->color = $obj->color;
$this->output_label = $obj->output_label;
$this->medium_type = $obj->medium_type;
$this->medium_spec = $obj->medium_spec;
$this->medium_length = $obj->medium_length;
$this->is_rail = $obj->is_rail;
$this->rail_start_te = $obj->rail_start_te;
$this->rail_end_te = $obj->rail_end_te;
$this->rail_phases = $obj->rail_phases;
$this->excluded_te = $obj->excluded_te;
$this->fk_carrier = $obj->fk_carrier;
$this->position_y = $obj->position_y;
$this->note_private = $obj->note_private;
$this->status = $obj->status;
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->fk_user_creat = $obj->fk_user_creat;
$this->fk_user_modif = $obj->fk_user_modif;
$this->source_label = $obj->source_label;
$this->target_label = $obj->target_label;
$this->carrier_label = $obj->carrier_label;
$this->db->free($resql);
return 1;
} else {
$this->db->free($resql);
return 0;
}
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Update object in database
*
* @param User $user User that modifies
* @return int Return integer <0 if KO, >0 if OK
*/
public function update($user)
{
$error = 0;
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
$sql .= " fk_source = ".($this->fk_source > 0 ? ((int) $this->fk_source) : "NULL");
$sql .= ", source_terminal = '".$this->db->escape($this->source_terminal ?: 'output')."'";
$sql .= ", fk_target = ".($this->fk_target > 0 ? ((int) $this->fk_target) : "NULL");
$sql .= ", target_terminal = '".$this->db->escape($this->target_terminal ?: 'input')."'";
$sql .= ", connection_type = ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "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 .= ", 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_length = ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
$sql .= ", is_rail = ".((int) $this->is_rail);
$sql .= ", rail_start_te = ".($this->rail_start_te > 0 ? ((int) $this->rail_start_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 .= ", excluded_te = ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
$sql .= ", fk_carrier = ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
$sql .= ", position_y = ".((int) $this->position_y);
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", status = ".((int) $this->status);
$sql .= ", fk_user_modif = ".((int) $user->id);
$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;
}
}
/**
* Delete object in database
*
* @param User $user User that deletes
* @return int Return integer <0 if KO, >0 if OK
*/
public function delete($user)
{
$error = 0;
$this->db->begin();
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." 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;
}
}
/**
* Fetch all connections for a carrier
*
* @param int $carrierId Carrier ID
* @param int $activeOnly Only active connections
* @return array Array of EquipmentConnection objects
*/
public function fetchByCarrier($carrierId, $activeOnly = 1)
{
$results = array();
$sql = "SELECT c.*, ";
$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 .= " 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 tgt ON c.fk_target = tgt.rowid";
$sql .= " WHERE c.fk_carrier = ".((int) $carrierId);
if ($activeOnly) {
$sql .= " AND c.status = 1";
}
$sql .= " ORDER BY c.position_y ASC, c.rowid ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$conn = new EquipmentConnection($this->db);
$conn->id = $obj->rowid;
$conn->entity = $obj->entity;
$conn->fk_source = $obj->fk_source;
$conn->source_terminal = $obj->source_terminal;
$conn->source_terminal_id = $obj->source_terminal_id;
$conn->fk_target = $obj->fk_target;
$conn->target_terminal = $obj->target_terminal;
$conn->target_terminal_id = $obj->target_terminal_id;
$conn->connection_type = $obj->connection_type;
$conn->color = $obj->color;
$conn->output_label = $obj->output_label;
$conn->medium_type = $obj->medium_type;
$conn->medium_spec = $obj->medium_spec;
$conn->medium_length = $obj->medium_length;
$conn->is_rail = $obj->is_rail;
$conn->rail_start_te = $obj->rail_start_te;
$conn->rail_end_te = $obj->rail_end_te;
$conn->rail_phases = $obj->rail_phases;
$conn->excluded_te = $obj->excluded_te;
$conn->fk_carrier = $obj->fk_carrier;
$conn->position_y = $obj->position_y;
$conn->status = $obj->status;
$conn->source_label = $obj->source_label;
$conn->source_pos = $obj->source_pos;
$conn->source_width = $obj->source_width;
$conn->target_label = $obj->target_label;
$conn->target_pos = $obj->target_pos;
$results[] = $conn;
}
$this->db->free($resql);
}
return $results;
}
/**
* Fetch all outputs for an equipment
*
* @param int $equipmentId Equipment ID
* @return array Array of EquipmentConnection objects
*/
public function fetchOutputs($equipmentId)
{
$results = array();
$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_source = ".((int) $equipmentId);
$sql .= " AND fk_target IS NULL";
$sql .= " AND status = 1";
$sql .= " ORDER BY position_y ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$conn = new EquipmentConnection($this->db);
$conn->id = $obj->rowid;
$conn->fk_source = $obj->fk_source;
$conn->connection_type = $obj->connection_type;
$conn->color = $obj->color;
$conn->output_label = $obj->output_label;
$conn->medium_type = $obj->medium_type;
$conn->medium_spec = $obj->medium_spec;
$conn->medium_length = $obj->medium_length;
$conn->fk_carrier = $obj->fk_carrier;
$conn->position_y = $obj->position_y;
$conn->status = $obj->status;
$results[] = $conn;
}
$this->db->free($resql);
}
return $results;
}
/**
* Get display color
*
* @return string Color hex code
*/
public function getColor()
{
if (!empty($this->color)) {
return $this->color;
}
return '#888888'; // Default grey
}
/**
* Get display label for output
*
* @return string Display label
*/
public function getDisplayLabel()
{
$parts = array();
if ($this->output_label) {
$parts[] = $this->output_label;
}
if ($this->medium_type) {
$mediumInfo = $this->medium_type;
if ($this->medium_spec) {
$mediumInfo .= ' '.$this->medium_spec;
}
$parts[] = $mediumInfo;
}
return implode(' - ', $parts);
}
}

View file

@ -0,0 +1,285 @@
<?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 EquipmentPanel
* Manages equipment panels (Schaltschrankfelder)
* Physical sections in distribution boards containing carriers
*/
class EquipmentPanel extends CommonObject
{
public $element = 'equipmentpanel';
public $table_element = 'kundenkarte_equipment_panel';
public $fk_anlage;
public $label;
public $position;
public $note_private;
public $status;
public $date_creation;
public $fk_user_creat;
public $fk_user_modif;
// Loaded objects
public $carriers = array();
public $anlage_label;
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Create object in database
*
* @param User $user User that creates
* @return int Return integer <0 if KO, Id of created object if OK
*/
public function create($user)
{
global $conf;
$error = 0;
$now = dol_now();
if (empty($this->fk_anlage)) {
$this->error = 'ErrorMissingParameters';
return -1;
}
// Get next position
if (empty($this->position)) {
$sql = "SELECT MAX(position) as maxpos FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_anlage = ".((int) $this->fk_anlage);
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
$this->position = ($obj->maxpos !== null) ? $obj->maxpos + 1 : 0;
}
}
// Default label if not set
if (empty($this->label)) {
$this->label = 'Feld '.($this->position + 1);
}
$this->db->begin();
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, fk_anlage, label, position, note_private, status,";
$sql .= " date_creation, fk_user_creat";
$sql .= ") VALUES (";
$sql .= ((int) $conf->entity);
$sql .= ", ".((int) $this->fk_anlage);
$sql .= ", '".$this->db->escape($this->label)."'";
$sql .= ", ".((int) $this->position);
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", ".((int) ($this->status !== null ? $this->status : 1));
$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;
}
}
/**
* Load object from database
*
* @param int $id ID of record
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
*/
public function fetch($id)
{
$sql = "SELECT p.*, a.label as anlage_label";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage as a ON p.fk_anlage = a.rowid";
$sql .= " WHERE p.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->entity = $obj->entity;
$this->fk_anlage = $obj->fk_anlage;
$this->label = $obj->label;
$this->position = $obj->position;
$this->note_private = $obj->note_private;
$this->status = $obj->status;
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->fk_user_creat = $obj->fk_user_creat;
$this->fk_user_modif = $obj->fk_user_modif;
$this->anlage_label = $obj->anlage_label;
$this->db->free($resql);
return 1;
} else {
$this->db->free($resql);
return 0;
}
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Update object in database
*
* @param User $user User that modifies
* @return int Return integer <0 if KO, >0 if OK
*/
public function update($user)
{
$error = 0;
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
$sql .= " label = '".$this->db->escape($this->label)."'";
$sql .= ", position = ".((int) $this->position);
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", status = ".((int) $this->status);
$sql .= ", fk_user_modif = ".((int) $user->id);
$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;
}
}
/**
* Delete object in database
*
* @param User $user User that deletes
* @return int Return integer <0 if KO, >0 if OK
*/
public function delete($user)
{
$error = 0;
$this->db->begin();
// Carriers are deleted via CASCADE or need to be reassigned
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." 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;
}
}
/**
* Fetch all panels for an Anlage
*
* @param int $anlageId Anlage ID
* @param int $activeOnly Only active panels
* @return array Array of EquipmentPanel objects
*/
public function fetchByAnlage($anlageId, $activeOnly = 1)
{
$results = array();
$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_anlage = ".((int) $anlageId);
if ($activeOnly) {
$sql .= " AND status = 1";
}
$sql .= " ORDER BY position ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$panel = new EquipmentPanel($this->db);
$panel->id = $obj->rowid;
$panel->entity = $obj->entity;
$panel->fk_anlage = $obj->fk_anlage;
$panel->label = $obj->label;
$panel->position = $obj->position;
$panel->note_private = $obj->note_private;
$panel->status = $obj->status;
$results[] = $panel;
}
$this->db->free($resql);
}
return $results;
}
/**
* Fetch all carriers in this panel
*
* @return array Array of EquipmentCarrier objects
*/
public function fetchCarriers()
{
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
$carrier = new EquipmentCarrier($this->db);
$this->carriers = $carrier->fetchByPanel($this->id);
return $this->carriers;
}
/**
* Get total carriers count
*
* @return int Number of carriers
*/
public function getCarrierCount()
{
if (empty($this->carriers)) {
$this->fetchCarriers();
}
return count($this->carriers);
}
}

View file

@ -0,0 +1,374 @@
<?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 EquipmentType
* Manages equipment type templates (Hutschienen-Komponenten)
*/
class EquipmentType extends CommonObject
{
public $element = 'equipmenttype';
public $table_element = 'kundenkarte_equipment_type';
public $ref;
public $label;
public $label_short;
public $description;
public $fk_system;
// Equipment-spezifische Felder
public $width_te = 1;
public $color;
public $fk_product;
public $terminals_config; // JSON config for terminals
public $picto;
public $is_system;
public $position;
public $active;
public $date_creation;
public $fk_user_creat;
public $fk_user_modif;
// Loaded objects
public $system_label;
public $system_code;
public $product; // Linked product object
public $fields = array();
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Create object in database
*
* @param User $user User that creates
* @return int Return integer <0 if KO, Id of created object if OK
*/
public function create($user)
{
global $conf;
$error = 0;
$now = dol_now();
if (empty($this->ref) || empty($this->label) || empty($this->fk_system)) {
$this->error = 'ErrorMissingParameters';
return -1;
}
$this->db->begin();
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, ref, label, label_short, description, fk_system,";
$sql .= " width_te, color, fk_product, terminals_config,";
$sql .= " picto, is_system, position, active,";
$sql .= " date_creation, fk_user_creat";
$sql .= ") VALUES (";
$sql .= "0"; // entity 0 = global
$sql .= ", '".$this->db->escape($this->ref)."'";
$sql .= ", '".$this->db->escape($this->label)."'";
$sql .= ", ".($this->label_short ? "'".$this->db->escape($this->label_short)."'" : "NULL");
$sql .= ", ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
$sql .= ", ".((int) $this->fk_system);
$sql .= ", ".((int) ($this->width_te > 0 ? $this->width_te : 1));
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
$sql .= ", ".($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL");
$sql .= ", ".($this->terminals_config ? "'".$this->db->escape($this->terminals_config)."'" : "NULL");
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
$sql .= ", 0"; // is_system = 0 for user-created
$sql .= ", ".((int) $this->position);
$sql .= ", ".((int) ($this->active !== null ? $this->active : 1));
$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;
}
}
/**
* Load object from database
*
* @param int $id ID of record
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
*/
public function fetch($id)
{
$sql = "SELECT t.*, s.label as system_label, s.code as system_code,";
$sql .= " p.ref as product_ref, p.label as product_label";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON t.fk_system = s.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON t.fk_product = p.rowid";
$sql .= " WHERE t.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->entity = $obj->entity;
$this->ref = $obj->ref;
$this->label = $obj->label;
$this->label_short = $obj->label_short;
$this->description = $obj->description;
$this->fk_system = $obj->fk_system;
$this->width_te = $obj->width_te;
$this->color = $obj->color;
$this->fk_product = $obj->fk_product;
$this->terminals_config = $obj->terminals_config;
$this->picto = $obj->picto;
$this->is_system = $obj->is_system;
$this->position = $obj->position;
$this->active = $obj->active;
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->fk_user_creat = $obj->fk_user_creat;
$this->fk_user_modif = $obj->fk_user_modif;
$this->system_label = $obj->system_label;
$this->system_code = $obj->system_code;
$this->product_ref = $obj->product_ref;
$this->product_label = $obj->product_label;
$this->db->free($resql);
return 1;
} else {
$this->db->free($resql);
return 0;
}
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Update object in database
*
* @param User $user User that modifies
* @return int Return integer <0 if KO, >0 if OK
*/
public function update($user)
{
$error = 0;
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
$sql .= " ref = '".$this->db->escape($this->ref)."'";
$sql .= ", label = '".$this->db->escape($this->label)."'";
$sql .= ", label_short = ".($this->label_short ? "'".$this->db->escape($this->label_short)."'" : "NULL");
$sql .= ", description = ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
$sql .= ", fk_system = ".((int) $this->fk_system);
$sql .= ", width_te = ".((int) ($this->width_te > 0 ? $this->width_te : 1));
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
$sql .= ", fk_product = ".($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL");
$sql .= ", terminals_config = ".($this->terminals_config ? "'".$this->db->escape($this->terminals_config)."'" : "NULL");
$sql .= ", picto = ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
$sql .= ", position = ".((int) $this->position);
$sql .= ", active = ".((int) $this->active);
$sql .= ", fk_user_modif = ".((int) $user->id);
$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;
}
}
/**
* Delete object in database
*
* @param User $user User that deletes
* @return int Return integer <0 if KO, >0 if OK
*/
public function delete($user)
{
global $conf;
// Check if type is in use
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_equipment";
$sql .= " WHERE fk_equipment_type = ".((int) $this->id);
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
if ($obj->cnt > 0) {
$this->error = 'ErrorTypeInUse';
return -1;
}
}
// Cannot delete system types
if ($this->is_system) {
$this->error = 'ErrorCannotDeleteSystemType';
return -2;
}
$error = 0;
$this->db->begin();
// Delete fields first
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field WHERE fk_equipment_type = ".((int) $this->id);
$this->db->query($sql);
// Delete type
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." 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;
}
}
/**
* Fetch all equipment types for a system
*
* @param int $systemId System ID (0 = all)
* @param int $activeOnly Only active types
* @return array Array of EquipmentType objects
*/
public function fetchAllBySystem($systemId = 0, $activeOnly = 1)
{
$results = array();
$sql = "SELECT t.*, s.label as system_label, s.code as system_code";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON t.fk_system = s.rowid";
$sql .= " WHERE 1 = 1";
if ($systemId > 0) {
$sql .= " AND t.fk_system = ".((int) $systemId);
}
if ($activeOnly) {
$sql .= " AND t.active = 1";
}
$sql .= " ORDER BY t.fk_system ASC, t.position ASC, t.label ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$type = new EquipmentType($this->db);
$type->id = $obj->rowid;
$type->ref = $obj->ref;
$type->label = $obj->label;
$type->label_short = $obj->label_short;
$type->fk_system = $obj->fk_system;
$type->width_te = $obj->width_te;
$type->color = $obj->color;
$type->fk_product = $obj->fk_product;
$type->picto = $obj->picto;
$type->is_system = $obj->is_system;
$type->position = $obj->position;
$type->active = $obj->active;
$type->system_label = $obj->system_label;
$type->system_code = $obj->system_code;
$results[] = $type;
}
$this->db->free($resql);
}
return $results;
}
/**
* Fetch fields for this equipment type
*
* @param int $activeOnly Only active fields
* @return array Array of field objects
*/
public function fetchFields($activeOnly = 1)
{
$results = array();
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field";
$sql .= " WHERE fk_equipment_type = ".((int) $this->id);
if ($activeOnly) {
$sql .= " AND active = 1";
}
$sql .= " ORDER BY position ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$results[] = $obj;
}
$this->db->free($resql);
}
$this->fields = $results;
return $results;
}
/**
* Get fields that should be shown on the SVG block
*
* @return array Array of field objects with show_on_block = 1
*/
public function getBlockFields()
{
$results = array();
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field";
$sql .= " WHERE fk_equipment_type = ".((int) $this->id);
$sql .= " AND show_on_block = 1";
$sql .= " AND active = 1";
$sql .= " ORDER BY position ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$results[] = $obj;
}
$this->db->free($resql);
}
return $results;
}
}

View file

@ -76,7 +76,7 @@ class modKundenKarte extends DolibarrModules
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte'
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
$this->version = '2.5';
$this->version = '3.0';
// Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
@ -393,6 +393,23 @@ class modKundenKarte extends DolibarrModules
'target' => '',
'user' => 0,
);
// Admin submenu: Manage Equipment Types
$this->menu[$r++] = array(
'fk_menu' => 'fk_mainmenu=kundenkarte',
'type' => 'left',
'titre' => 'EquipmentTypes',
'prefix' => img_picto('', 'fa-microchip', 'class="pictofixedwidth valignmiddle paddingright"'),
'mainmenu' => 'kundenkarte',
'leftmenu' => 'kundenkarte_equipment_types',
'url' => '/kundenkarte/admin/equipment_types.php',
'langs' => 'kundenkarte@kundenkarte',
'position' => 1000 + $r,
'enabled' => 'isModEnabled("kundenkarte")',
'perms' => '$user->hasRight("kundenkarte", "admin")',
'target' => '',
'user' => 0,
);
/* END MODULEBUILDER LEFTMENU */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -178,6 +178,83 @@ ConfigHelpSystems = Systeme verwalten: Gehen Sie zum Tab "Anlagen-Systeme" um ei
ConfigHelpTypes = Element-Typen verwalten: Gehen Sie zum Tab "Element-Typen" um Geraetetypen und Felder zu definieren
SetupSaved = Einstellungen gespeichert
# Equipment (Hutschienen-Komponenten)
EquipmentTypes = Equipment-Typen
AddEquipmentType = Equipment-Typ hinzufuegen
EquipmentTypeFields = Equipment-Felder
CanHaveEquipment = Kann Equipment haben
CanHaveEquipmentHelp = Hutschienen-Komponenten koennen unter diesem Typ platziert werden
WidthTE = Breite (TE)
WidthTEHelp = Breite in Teilungseinheiten (1 TE = 18mm)
ColorForSVG = Farbe fuer SVG-Darstellung
TerminalConfig = Anschlusspunkte
TerminalConfigHelp = JSON-Konfiguration der Anschlusspunkte (Terminals)
LinkedProduct = Verknuepftes Produkt
ShowOnBlock = Auf Block anzeigen
ShowInHover = Im Hover anzeigen
Carrier = Traeger
CarrierLabel = Hutschiene
AddCarrier = Traeger hinzufuegen
TotalTE = Gesamt TE
UsedTE = Belegt
FreeTE = Frei
Equipment = Equipment
AddEquipment = Equipment hinzufuegen
DuplicateEquipment = Equipment kopieren
DuplicatePrevious = Vorherigen Automaten kopieren
DuplicatePreviousCarrier = Vorherige Hutschiene kopieren
DuplicatePreviousPanel = Vorheriges Feld kopieren
NoSpaceOnCarrier = Kein Platz mehr auf dem Traeger
PositionTE = Position (TE)
Panels = Felder
Fields = Felder
AddPanel = Feld hinzufuegen
PanelLabel = Feld
NoCarriers = Keine Hutschienen
DirectCarriers = Direkte Hutschienen
NoPanelsOrCarriers = Keine Felder oder Hutschienen vorhanden
AddPanelOrCarrier = Feld oder Hutschiene hinzufuegen
Protection = Schutzeinrichtung
ProtectionLabel = Schutzbezeichnung
AssignedToProtection = Gehoert zu Schutzeinrichtung
Characteristic = Charakteristik
Ampere = Nennstrom
Pole = Polzahl
Circuit = Stromkreis
# Connections (Verbindungen - generisch fuer alle Systeme)
Connections = Verbindungen
Connection = Verbindung
AddConnection = Verbindung hinzufuegen
AddOutput = Abgang hinzufuegen
AddRail = Sammelschiene hinzufuegen
AddBusbar = Sammelschiene hinzufuegen
Busbar = Sammelschiene
ConnectionEditor = Verbindungseditor
ConnectionType = Verbindungstyp
Color = Farbe
OutputLabel = Ziel/Endpunkt
MediumType = Medientyp
MediumSpec = Spezifikation
MediumLength = Laenge
SourceEquipment = Von Equipment
TargetEquipment = Zu Equipment
SourceTerminal = Ausgang von
TargetTerminal = Eingang zu
InputTerminal = Eingang
OutputTerminal = Ausgang
RailStart = Schiene von TE
RailEnd = Schiene bis TE
NoConnections = Keine Verbindungen
DeleteConnection = Verbindung loeschen
ConfirmDeleteConnection = Moechten Sie diese Verbindung wirklich loeschen?
ExternalInput = Externe Einspeisung
# Beispiel-Verbindungstypen (flexibel, Benutzer kann eigene anlegen)
# Strom: L1, L2, L3, N, PE, L1N, 3P, 3P+N, 3P+N+PE
# Netzwerk: CAT5, CAT6, CAT7, LWL, Koax
# Sicherheit: 2-Draht, 4-Draht, BUS
# PDF Export
PDFExportTemplate = PDF Export Vorlage
PDFFontSettings = PDF Schriftgroessen

View file

@ -54,6 +54,11 @@ function kundenkarteAdminPrepareHead()
$head[$h][2] = 'types';
$h++;
$head[$h][0] = dol_buildpath("/kundenkarte/admin/equipment_types.php", 1);
$head[$h][1] = $langs->trans("EquipmentTypes");
$head[$h][2] = 'equipment_types';
$h++;
/*
$head[$h][0] = dol_buildpath("/kundenkarte/admin/myobject_extrafields.php", 1);
$head[$h][1] = $langs->trans("ExtraFields");

View file

@ -0,0 +1,10 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
-- ============================================================================
ALTER TABLE llx_kundenkarte_equipment ADD INDEX idx_kundenkarte_equipment_carrier (fk_carrier);
ALTER TABLE llx_kundenkarte_equipment ADD INDEX idx_kundenkarte_equipment_type (fk_equipment_type);
ALTER TABLE llx_kundenkarte_equipment ADD INDEX idx_kundenkarte_equipment_status (status);
ALTER TABLE llx_kundenkarte_equipment ADD CONSTRAINT fk_kundenkarte_equipment_carrier FOREIGN KEY (fk_carrier) REFERENCES llx_kundenkarte_equipment_carrier (rowid) ON DELETE CASCADE;
ALTER TABLE llx_kundenkarte_equipment ADD CONSTRAINT fk_kundenkarte_equipment_type FOREIGN KEY (fk_equipment_type) REFERENCES llx_kundenkarte_equipment_type (rowid) ON DELETE RESTRICT;

View file

@ -0,0 +1,36 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
--
-- Table for equipment instances (Sicherungsautomaten, FI-Schalter, etc.)
-- Equipment is placed on a carrier (Hutschiene) at a specific position
-- ============================================================================
CREATE TABLE llx_kundenkarte_equipment
(
rowid integer AUTO_INCREMENT PRIMARY KEY,
entity integer DEFAULT 1 NOT NULL,
fk_carrier integer NOT NULL COMMENT 'Traeger/Hutschiene',
fk_equipment_type integer NOT NULL COMMENT 'Equipment-Typ',
label varchar(255) COMMENT 'Optionale Beschriftung/Stromkreis',
-- Positionierung auf dem Traeger
position_te integer NOT NULL COMMENT 'Startposition in TE (0-basiert)',
width_te integer NOT NULL COMMENT 'Breite in TE (vom Typ oder ueberschrieben)',
-- Flexible Felder (JSON)
field_values text,
-- Optionale Produkt-Verknuepfung (ueberschreibt Typ-Produkt)
fk_product integer DEFAULT NULL COMMENT 'Spezifisches Dolibarr-Produkt',
note_private text,
status tinyint DEFAULT 1 NOT NULL,
date_creation datetime,
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
fk_user_creat integer,
fk_user_modif integer,
import_key varchar(14)
) ENGINE=innodb;

View file

@ -0,0 +1,8 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
-- ============================================================================
ALTER TABLE llx_kundenkarte_equipment_carrier ADD INDEX idx_kundenkarte_carrier_anlage (fk_anlage);
ALTER TABLE llx_kundenkarte_equipment_carrier ADD INDEX idx_kundenkarte_carrier_status (status);
ALTER TABLE llx_kundenkarte_equipment_carrier ADD CONSTRAINT fk_kundenkarte_carrier_anlage FOREIGN KEY (fk_anlage) REFERENCES llx_kundenkarte_anlage (rowid) ON DELETE CASCADE;

View file

@ -0,0 +1,26 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
--
-- Table for equipment carriers (Hutschienen/Traeger)
-- A carrier belongs to a technical place (Anlage) and has a capacity in TE
-- ============================================================================
CREATE TABLE llx_kundenkarte_equipment_carrier
(
rowid integer AUTO_INCREMENT PRIMARY KEY,
entity integer DEFAULT 1 NOT NULL,
fk_anlage integer NOT NULL COMMENT 'Technischer Platz (z.B. Unterverteiler, Zaehlerschrank)',
label varchar(255) NOT NULL COMMENT 'Bezeichnung (z.B. Hutschiene 1, Obere Reihe)',
total_te integer DEFAULT 12 NOT NULL COMMENT 'Gesamtkapazitaet in Teilungseinheiten',
position integer DEFAULT 0 COMMENT 'Reihenfolge der Traeger',
note_private text,
status tinyint DEFAULT 1 NOT NULL,
date_creation datetime,
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
fk_user_creat integer,
fk_user_modif integer
) ENGINE=innodb;

View file

@ -0,0 +1,5 @@
-- Copyright (C) 2026 Alles Watt lauft
-- Update script for equipment_connection table - adds rail_phases and excluded_te
ALTER TABLE llx_kundenkarte_equipment_connection ADD COLUMN IF NOT EXISTS rail_phases VARCHAR(20) DEFAULT NULL;
ALTER TABLE llx_kundenkarte_equipment_connection ADD COLUMN IF NOT EXISTS excluded_te VARCHAR(100) DEFAULT NULL;

View file

@ -0,0 +1,61 @@
-- Copyright (C) 2026 Alles Watt lauft
--
-- Equipment connections (Verbindungen zwischen Equipment)
-- Generic connection system for any installation type (electrical, network, security, etc.)
CREATE TABLE llx_kundenkarte_equipment_connection (
rowid INTEGER AUTO_INCREMENT PRIMARY KEY,
entity INTEGER DEFAULT 1 NOT NULL,
-- Source equipment (where connection comes FROM)
fk_source INTEGER, -- NULL = external input
source_terminal VARCHAR(20) DEFAULT 'output', -- Terminal identifier (flexible)
-- Target equipment (where connection goes TO)
fk_target INTEGER, -- NULL = output/endpoint
target_terminal VARCHAR(20) DEFAULT 'input', -- Terminal identifier (flexible)
-- Connection properties (flexible, user-defined)
connection_type VARCHAR(50), -- User-defined type (e.g., "L1N", "CAT6", "2-Draht", etc.)
color VARCHAR(20), -- Display color (hex code)
-- Output/endpoint info (when fk_target is NULL)
output_label VARCHAR(255), -- e.g., "Küche Steckdosen", "Büro PC1", "Haustür"
-- Medium info (cable, wire, etc.) - all flexible text fields
medium_type VARCHAR(100), -- e.g., "NYM-J", "CAT6 S/FTP", "2x0.8 J-Y(St)Y"
medium_spec VARCHAR(100), -- e.g., "3x2.5", "AWG23", "Ring 1"
medium_length VARCHAR(50), -- Length as text (allows "ca. 15m", "5-10m", etc.)
-- Rail/bar connections (spans multiple equipment)
is_rail TINYINT DEFAULT 0, -- 1 = this is a rail/bar spanning multiple slots
rail_start_te INTEGER, -- Start position on carrier
rail_end_te INTEGER, -- End position on carrier
rail_phases VARCHAR(20), -- '3P', '3P+N', 'L1', 'L1N', etc. for multi-line display
excluded_te VARCHAR(100), -- Comma-separated TE positions to exclude (gaps for FI)
fk_carrier INTEGER, -- Carrier where this connection is rendered
position_y INTEGER DEFAULT 0, -- Y offset for rendering (0=first row, 1=second row, etc.)
note_private TEXT,
status INTEGER DEFAULT 1,
date_creation DATETIME,
tms TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
fk_user_creat INTEGER,
fk_user_modif INTEGER
) ENGINE=InnoDB;
-- Indexes
ALTER TABLE llx_kundenkarte_equipment_connection ADD INDEX idx_connection_source (fk_source);
ALTER TABLE llx_kundenkarte_equipment_connection ADD INDEX idx_connection_target (fk_target);
ALTER TABLE llx_kundenkarte_equipment_connection ADD INDEX idx_connection_carrier (fk_carrier);
ALTER TABLE llx_kundenkarte_equipment_connection ADD INDEX idx_connection_entity (entity);
-- Foreign keys
ALTER TABLE llx_kundenkarte_equipment_connection ADD CONSTRAINT fk_connection_source
FOREIGN KEY (fk_source) REFERENCES llx_kundenkarte_equipment(rowid) ON DELETE CASCADE;
ALTER TABLE llx_kundenkarte_equipment_connection ADD CONSTRAINT fk_connection_target
FOREIGN KEY (fk_target) REFERENCES llx_kundenkarte_equipment(rowid) ON DELETE CASCADE;
ALTER TABLE llx_kundenkarte_equipment_connection ADD CONSTRAINT fk_connection_carrier
FOREIGN KEY (fk_carrier) REFERENCES llx_kundenkarte_equipment_carrier(rowid) ON DELETE CASCADE;

View file

@ -0,0 +1,28 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
--
-- Equipment Panel table (Schaltschrankfelder)
-- Represents physical sections/fields in a distribution board
-- ============================================================================
CREATE TABLE llx_kundenkarte_equipment_panel (
rowid INTEGER AUTO_INCREMENT PRIMARY KEY,
entity INTEGER DEFAULT 1 NOT NULL,
fk_anlage INTEGER NOT NULL, -- Zählerschrank/UV that contains this panel
label VARCHAR(128), -- "Feld 1", "Linkes Feld", etc.
position INTEGER DEFAULT 0, -- Order (left to right)
note_private TEXT,
status SMALLINT DEFAULT 1, -- 1=active, 0=inactive
date_creation DATETIME,
tms TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
fk_user_creat INTEGER,
fk_user_modif INTEGER,
import_key VARCHAR(14),
INDEX idx_panel_anlage (fk_anlage),
CONSTRAINT fk_panel_anlage FOREIGN KEY (fk_anlage)
REFERENCES llx_kundenkarte_anlage(rowid) ON DELETE CASCADE
) ENGINE=InnoDB;

View file

@ -0,0 +1,10 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
-- ============================================================================
ALTER TABLE llx_kundenkarte_equipment_type ADD UNIQUE INDEX uk_kundenkarte_equipment_type_ref (ref, entity);
ALTER TABLE llx_kundenkarte_equipment_type ADD INDEX idx_kundenkarte_equipment_type_fk_system (fk_system);
ALTER TABLE llx_kundenkarte_equipment_type ADD INDEX idx_kundenkarte_equipment_type_active (active);
ALTER TABLE llx_kundenkarte_equipment_type ADD CONSTRAINT fk_kundenkarte_equipment_type_fk_system FOREIGN KEY (fk_system) REFERENCES llx_c_kundenkarte_anlage_system (rowid);

View file

@ -0,0 +1,38 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
--
-- Table for equipment type templates (Hutschienen-Komponenten)
-- Examples: Leitungsschutzschalter, FI-Schalter, Hauptschalter, etc.
-- ============================================================================
CREATE TABLE llx_kundenkarte_equipment_type
(
rowid integer AUTO_INCREMENT PRIMARY KEY,
entity integer DEFAULT 0 NOT NULL,
ref varchar(64) NOT NULL,
label varchar(255) NOT NULL,
label_short varchar(32),
description text,
fk_system integer NOT NULL,
-- Equipment-spezifische Felder
width_te integer DEFAULT 1 NOT NULL COMMENT 'Standardbreite in Teilungseinheiten (TE)',
color varchar(8) COMMENT 'Farbcode fuer SVG-Darstellung (z.B. #3498db)',
-- Optionale Produkt-Verknuepfung
fk_product integer DEFAULT NULL COMMENT 'Optionales Standard-Dolibarr-Produkt',
picto varchar(64),
is_system tinyint DEFAULT 0 NOT NULL,
position integer DEFAULT 0,
active tinyint DEFAULT 1 NOT NULL,
date_creation datetime,
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
fk_user_creat integer,
fk_user_modif integer,
import_key varchar(14)
) ENGINE=innodb;

View file

@ -0,0 +1,8 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
-- ============================================================================
ALTER TABLE llx_kundenkarte_equipment_type_field ADD INDEX idx_kundenkarte_equip_type_field_type (fk_equipment_type);
ALTER TABLE llx_kundenkarte_equipment_type_field ADD INDEX idx_kundenkarte_equip_type_field_active (active);
ALTER TABLE llx_kundenkarte_equipment_type_field ADD CONSTRAINT fk_kundenkarte_equip_type_field_type FOREIGN KEY (fk_equipment_type) REFERENCES llx_kundenkarte_equipment_type (rowid) ON DELETE CASCADE;

View file

@ -0,0 +1,33 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
--
-- Table for defining fields per equipment type (EAV pattern for flexibility)
-- ============================================================================
CREATE TABLE llx_kundenkarte_equipment_type_field
(
rowid integer AUTO_INCREMENT PRIMARY KEY,
entity integer DEFAULT 0 NOT NULL,
fk_equipment_type integer NOT NULL,
field_code varchar(64) NOT NULL,
field_label varchar(255) NOT NULL,
field_type varchar(32) NOT NULL,
field_options text,
field_size integer DEFAULT 255,
field_default varchar(255),
required tinyint DEFAULT 0 NOT NULL,
show_in_hover tinyint DEFAULT 1 NOT NULL,
show_on_block tinyint DEFAULT 0 NOT NULL COMMENT 'Auf der SVG-Darstellung anzeigen',
position integer DEFAULT 0,
active tinyint DEFAULT 1 NOT NULL,
date_creation datetime,
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
fk_user_creat integer,
fk_user_modif integer
) ENGINE=innodb;

12
sql/update_3.0.0.sql Normal file
View file

@ -0,0 +1,12 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
--
-- Update script for version 3.0.0
-- Adds can_have_equipment column to anlage_type table
-- ============================================================================
-- Add can_have_equipment to existing anlage_type table
ALTER TABLE llx_kundenkarte_anlage_type
ADD COLUMN can_have_equipment tinyint DEFAULT 0 NOT NULL
COMMENT 'Ob dieser Typ Equipment (Hutschienen-Komponenten) haben kann'
AFTER allowed_parent_types;

41
sql/update_3.1.0.sql Normal file
View file

@ -0,0 +1,41 @@
-- ============================================================================
-- Copyright (C) 2026 Alles Watt lauft
--
-- Update script for version 3.1.0
-- Adds panels (Schaltschrankfelder) and FI protection assignment
-- ============================================================================
-- 1. Create equipment_panel table
CREATE TABLE IF NOT EXISTS llx_kundenkarte_equipment_panel (
rowid INTEGER AUTO_INCREMENT PRIMARY KEY,
entity INTEGER DEFAULT 1 NOT NULL,
fk_anlage INTEGER NOT NULL,
label VARCHAR(128),
position INTEGER DEFAULT 0,
note_private TEXT,
status SMALLINT DEFAULT 1,
date_creation DATETIME,
tms TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
fk_user_creat INTEGER,
fk_user_modif INTEGER,
import_key VARCHAR(14),
INDEX idx_panel_anlage (fk_anlage)
) ENGINE=InnoDB;
-- 2. Add fk_panel to equipment_carrier (optional - carrier can belong to panel or directly to anlage)
ALTER TABLE llx_kundenkarte_equipment_carrier
ADD COLUMN fk_panel INTEGER DEFAULT NULL AFTER fk_anlage,
ADD INDEX idx_carrier_panel (fk_panel);
-- 3. Add fk_protection to equipment (for FI/RCD assignment)
ALTER TABLE llx_kundenkarte_equipment
ADD COLUMN fk_protection INTEGER DEFAULT NULL
COMMENT 'FK to protection device (FI/RCD) that protects this equipment'
AFTER fk_product,
ADD INDEX idx_equipment_protection (fk_protection);
-- 4. Add protection_label field to show above protected equipment
ALTER TABLE llx_kundenkarte_equipment
ADD COLUMN protection_label VARCHAR(64) DEFAULT NULL
COMMENT 'Label shown above equipment when part of protection group'
AFTER fk_protection;

78
sql/update_3.2.0.sql Normal file
View file

@ -0,0 +1,78 @@
-- ============================================================================
-- KundenKarte Module Update 3.2.0
-- Add terminals configuration for equipment types
-- ============================================================================
-- Add terminals_config field to equipment_type table
-- JSON format for defining input/output terminals
-- Example: {"inputs": [{"id": "in_L", "label": "L"}, {"id": "in_N", "label": "N"}], "outputs": [{"id": "out_L", "label": "L"}, {"id": "out_N", "label": "N"}]}
ALTER TABLE llx_kundenkarte_equipment_type
ADD COLUMN terminals_config TEXT DEFAULT NULL COMMENT 'JSON config for terminals (inputs/outputs)';
-- Add free position fields for equipment in the schematic editor
ALTER TABLE llx_kundenkarte_equipment
ADD COLUMN editor_x INTEGER DEFAULT NULL COMMENT 'X position in schematic editor',
ADD COLUMN editor_y INTEGER DEFAULT NULL COMMENT 'Y position in schematic editor';
-- Update connection table to support terminal-based connections
ALTER TABLE llx_kundenkarte_equipment_connection
ADD COLUMN source_terminal_id VARCHAR(64) DEFAULT NULL COMMENT 'Source terminal ID (e.g., out_L, out_N)',
ADD COLUMN target_terminal_id VARCHAR(64) DEFAULT NULL COMMENT 'Target terminal ID (e.g., in_L, in_N)';
-- Default terminal configurations for common equipment types
-- These can be customized by the user
-- LS (Leitungsschutzschalter) - 1 input, 1 output
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in","label":"●","pos":"top"}],"outputs":[{"id":"out","label":"●","pos":"bottom"}]}'
WHERE ref IN ('LS', 'LSS', 'B16', 'C16') AND terminals_config IS NULL;
-- FI (Fehlerstromschutzschalter) - 2 inputs, 2 outputs (L+N)
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_L","label":"L","pos":"top-left"},{"id":"in_N","label":"N","pos":"top-right"}],"outputs":[{"id":"out_L","label":"L","pos":"bottom-left"},{"id":"out_N","label":"N","pos":"bottom-right"}]}'
WHERE ref IN ('FI', 'RCD', 'RCCB') AND terminals_config IS NULL;
-- FI/LS Kombi - 2 inputs, 2 outputs
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_L","label":"L","pos":"top-left"},{"id":"in_N","label":"N","pos":"top-right"}],"outputs":[{"id":"out_L","label":"L","pos":"bottom-left"},{"id":"out_N","label":"N","pos":"bottom-right"}]}'
WHERE ref IN ('FILS', 'RCBO') AND terminals_config IS NULL;
-- 3-poliger LS - 3 inputs, 3 outputs
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_L1","label":"L1","pos":"top"},{"id":"in_L2","label":"L2","pos":"top"},{"id":"in_L3","label":"L3","pos":"top"}],"outputs":[{"id":"out_L1","label":"L1","pos":"bottom"},{"id":"out_L2","label":"L2","pos":"bottom"},{"id":"out_L3","label":"L3","pos":"bottom"}]}'
WHERE ref IN ('LS3P', 'C3P') AND terminals_config IS NULL;
-- 4-poliger FI - 4 inputs, 4 outputs
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_L1","label":"L1","pos":"top"},{"id":"in_L2","label":"L2","pos":"top"},{"id":"in_L3","label":"L3","pos":"top"},{"id":"in_N","label":"N","pos":"top"}],"outputs":[{"id":"out_L1","label":"L1","pos":"bottom"},{"id":"out_L2","label":"L2","pos":"bottom"},{"id":"out_L3","label":"L3","pos":"bottom"},{"id":"out_N","label":"N","pos":"bottom"}]}'
WHERE ref IN ('FI4P', 'RCD4P') AND terminals_config IS NULL;
-- Hauptschalter - variable (default 3P+N)
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_L1","label":"L1","pos":"top"},{"id":"in_L2","label":"L2","pos":"top"},{"id":"in_L3","label":"L3","pos":"top"},{"id":"in_N","label":"N","pos":"top"}],"outputs":[{"id":"out_L1","label":"L1","pos":"bottom"},{"id":"out_L2","label":"L2","pos":"bottom"},{"id":"out_L3","label":"L3","pos":"bottom"},{"id":"out_N","label":"N","pos":"bottom"}]}'
WHERE ref IN ('HS', 'HAUPTSCHALTER') AND terminals_config IS NULL;
-- Schütz/Relais - 2 inputs, 2 outputs (Steuerstromkreis + Lastkreis)
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_A1","label":"A1","pos":"top-left"},{"id":"in_1","label":"1","pos":"top-right"}],"outputs":[{"id":"out_A2","label":"A2","pos":"bottom-left"},{"id":"out_2","label":"2","pos":"bottom-right"}]}'
WHERE ref IN ('SCHUETZ', 'RELAIS', 'K') AND terminals_config IS NULL;
-- Stromstoßschalter - Steuerung + 2 Lastanschlüsse
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_A1","label":"A1","pos":"top-left"},{"id":"in_1","label":"1","pos":"top-right"}],"outputs":[{"id":"out_A2","label":"A2","pos":"bottom-left"},{"id":"out_2","label":"2","pos":"bottom-right"}]}'
WHERE ref IN ('SSS', 'STROMSTOSS') AND terminals_config IS NULL;
-- Klemme - durchgehend
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in","label":"●","pos":"top"}],"outputs":[{"id":"out","label":"●","pos":"bottom"}]}'
WHERE ref IN ('KLEMME', 'REIHENKLEMME', 'RK') AND terminals_config IS NULL;
-- Überspannungsschutz - 3P+N+PE
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in_L1","label":"L1","pos":"top"},{"id":"in_L2","label":"L2","pos":"top"},{"id":"in_L3","label":"L3","pos":"top"},{"id":"in_N","label":"N","pos":"top"}],"outputs":[{"id":"out_PE","label":"PE","pos":"bottom"}]}'
WHERE ref IN ('SPD', 'UESP') AND terminals_config IS NULL;
-- Default for any equipment without config: 1 input, 1 output
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"inputs":[{"id":"in","label":"●","pos":"top"}],"outputs":[{"id":"out","label":"●","pos":"bottom"}]}'
WHERE terminals_config IS NULL;

54
sql/update_3.3.0.sql Normal file
View file

@ -0,0 +1,54 @@
-- ============================================================================
-- KundenKarte Module Update 3.3.0
-- Correct terminal configurations (bidirectional format)
-- ============================================================================
-- FI (Fehlerstromschutzschalter) - 4 Terminals (2 oben: L+N, 2 unten: L+N)
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"L","pos":"top"},{"id":"t2","label":"N","pos":"top"},{"id":"t3","label":"L","pos":"bottom"},{"id":"t4","label":"N","pos":"bottom"}]}'
WHERE ref IN ('FI', 'RCD', 'RCCB');
-- FI/LS Kombi - 4 Terminals
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"L","pos":"top"},{"id":"t2","label":"N","pos":"top"},{"id":"t3","label":"L","pos":"bottom"},{"id":"t4","label":"N","pos":"bottom"}]}'
WHERE ref IN ('FILS', 'RCBO');
-- HS (Hauptschalter) - 3 Pole (L1, L2, L3) ohne N
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"L1","pos":"top"},{"id":"t2","label":"L2","pos":"top"},{"id":"t3","label":"L3","pos":"top"},{"id":"t4","label":"L1","pos":"bottom"},{"id":"t5","label":"L2","pos":"bottom"},{"id":"t6","label":"L3","pos":"bottom"}]}'
WHERE ref IN ('HS', 'HAUPTSCHALTER');
-- LS (Leitungsschutzschalter) - 2 Terminals (1 oben, 1 unten)
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"●","pos":"top"},{"id":"t2","label":"●","pos":"bottom"}]}'
WHERE ref IN ('LS', 'LSS', 'B16', 'C16') AND (terminals_config IS NULL OR terminals_config LIKE '%inputs%');
-- 3-poliger LS - 6 Terminals
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"L1","pos":"top"},{"id":"t2","label":"L2","pos":"top"},{"id":"t3","label":"L3","pos":"top"},{"id":"t4","label":"L1","pos":"bottom"},{"id":"t5","label":"L2","pos":"bottom"},{"id":"t6","label":"L3","pos":"bottom"}]}'
WHERE ref IN ('LS3P', 'C3P') AND (terminals_config IS NULL OR terminals_config LIKE '%inputs%');
-- 4-poliger FI - 8 Terminals (3P+N)
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"L1","pos":"top"},{"id":"t2","label":"L2","pos":"top"},{"id":"t3","label":"L3","pos":"top"},{"id":"t4","label":"N","pos":"top"},{"id":"t5","label":"L1","pos":"bottom"},{"id":"t6","label":"L2","pos":"bottom"},{"id":"t7","label":"L3","pos":"bottom"},{"id":"t8","label":"N","pos":"bottom"}]}'
WHERE ref IN ('FI4P', 'RCD4P') AND (terminals_config IS NULL OR terminals_config LIKE '%inputs%');
-- Schuetz/Relais - 4 Terminals
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"A1","pos":"top"},{"id":"t2","label":"1","pos":"top"},{"id":"t3","label":"A2","pos":"bottom"},{"id":"t4","label":"2","pos":"bottom"}]}'
WHERE ref IN ('SCHUETZ', 'RELAIS', 'K') AND (terminals_config IS NULL OR terminals_config LIKE '%inputs%');
-- Klemme - 2 Terminals
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"●","pos":"top"},{"id":"t2","label":"●","pos":"bottom"}]}'
WHERE ref IN ('KLEMME', 'REIHENKLEMME', 'RK') AND (terminals_config IS NULL OR terminals_config LIKE '%inputs%');
-- Ueberspannungsschutz - 5 Terminals (3P+N oben, PE unten)
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"L1","pos":"top"},{"id":"t2","label":"L2","pos":"top"},{"id":"t3","label":"L3","pos":"top"},{"id":"t4","label":"N","pos":"top"},{"id":"t5","label":"PE","pos":"bottom"}]}'
WHERE ref IN ('SPD', 'UESP') AND (terminals_config IS NULL OR terminals_config LIKE '%inputs%');
-- Default: 2 Terminals fuer alle ohne Konfiguration
UPDATE llx_kundenkarte_equipment_type
SET terminals_config = '{"terminals":[{"id":"t1","label":"●","pos":"top"},{"id":"t2","label":"●","pos":"bottom"}]}'
WHERE terminals_config IS NULL OR terminals_config = '';

File diff suppressed because it is too large Load diff