Quelltext des aktuellen Standes
<?php
$this->layout('layouts/main', [
'page_title' => $page_title,
'current_route' => $current_route,
'full_width' => true,
'extra_css' => [
'/assets/css/catalog.css?v=' . filemtime(dirname(__DIR__, 2) . '/public/assets/css/catalog.css'),
'/assets/css/company-search.css',
],
'extra_js' => ['/assets/js/company-search.js'],
]);
// Der Vorschlag, eine unbekannte Firma anzulegen, setzt das Adressbuch und
// das Recht voraus, es zu pflegen.
$kannFirmaAnlegen = !empty($supplier_options) && ($modules['contacts'] ?? false)
&& $this->hasCapability('manage_contacts');
$item = $item ?? null;
$id = $item ? (int)$item['id'] : 0;
$action = $id > 0 ? '/catalog/items/' . $id : '/catalog/items';
$val = fn($k, $d = '') => $this->e($item[$k] ?? $d);
$accIds = $accessory_ids ?? [];
$reqs = $requirements ?? [];
$billing = $billing ?? ['once' => 'einmalig', 'monthly' => 'pro Monat', 'yearly' => 'pro Jahr'];
$reqOptions = '<option value="">– Artikel wählen –</option>';
foreach (($all_items ?? []) as $ai) {
if ((int)$ai['id'] === $id) { continue; }
$reqOptions .= '<option value="' . (int)$ai['id'] . '">' . htmlspecialchars((string)$ai['name'], ENT_QUOTES) . '</option>';
}
?>
<div class="cat-wrap">
<div class="content-header">
<div>
<h1><?= $id > 0 ? 'Artikel bearbeiten' : 'Neuer Artikel' ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/catalog"><?= $this->te('Ausstattung & Preise') ?></a></li>
<li class="breadcrumb-item active"><?= $id > 0 ? $this->e($item['name']) : 'Neu' ?></li>
</ol>
</div>
<div class="content-header-actions">
<a href="<?= $id > 0 ? '/catalog/items/' . $id : '/catalog' ?>" class="btn btn-default"><i class="fas fa-arrow-left"></i> <?= $this->te('Abbrechen') ?></a>
</div>
</div>
<div class="page-body">
<form method="post" action="<?= $action ?>" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
<div class="detail-card">
<div class="detail-card-body">
<div class="form-row-2">
<div class="form-group">
<label for="f-name"><?= $this->te('Name') ?> <span class="text-danger">*</span></label>
<input type="text" id="f-name" name="name" class="form-control" maxlength="255" required value="<?= $val('name') ?>" placeholder="<?= $this->te('z. B. Standard-Laptop 14" Business') ?>">
</div>
<div class="form-group">
<label for="f-cat"><?= $this->te('Kategorie') ?></label>
<select id="f-cat" name="category_id" class="form-control" data-searchable>
<option value=""><?= $this->te('– keine –') ?></option>
<?php foreach (($categories ?? []) as $c): ?>
<option value="<?= (int)$c['id'] ?>" <?= (int)($item['category_id'] ?? 0) === (int)$c['id'] ? 'selected' : '' ?>><?= $this->e($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-row-2">
<div class="form-group">
<label for="f-man"><?= $this->te('Hersteller') ?></label>
<input type="text" id="f-man" name="manufacturer" class="form-control" maxlength="150" value="<?= $val('manufacturer') ?>">
</div>
<div class="form-group">
<label for="f-model"><?= $this->te('Modell') ?></label>
<input type="text" id="f-model" name="model" class="form-control" maxlength="150" value="<?= $val('model') ?>">
</div>
</div>
<div class="form-row-2">
<div class="form-group">
<label for="f-assettype"><?= $this->te('Inventar-Modell verknüpfen') ?></label>
<select id="f-assettype" name="asset_type_id" class="form-control" data-searchable>
<option value=""><?= $this->te('– kein Modell –') ?></option>
<?php foreach (($asset_types ?? []) as $t): ?>
<option value="<?= (int)$t['id'] ?>"
data-name="<?= $this->e($t['name']) ?>"
data-man="<?= $this->e($t['manufacturer'] ?? '') ?>"
data-spec="<?= $this->e($t['specs'] ?? '') ?>"
data-image="<?= $this->e($t['image'] ?? '') ?>"
<?= (int)($item['asset_type_id'] ?? 0) === (int)$t['id'] ? 'selected' : '' ?>>
<?= $this->e(trim(($t['manufacturer'] ?? '') . ' ' . $t['name'])) ?>
</option>
<?php endforeach; ?>
</select>
<small class="form-hint"><?= $this->te('Übernimmt beim Auswählen automatisch Bild sowie Name, Hersteller, Modell und Spezifikation als Vorbelegung (überschreibbar).') ?>
<?php if (empty($asset_types)): ?><br><?= $this->te('Noch keine Inventar-Modelle vorhanden.') ?><?php endif; ?>
</small>
</div>
<div class="form-group">
<label><?= $this->te('Bild') ?></label>
<div class="cat-img-row">
<div class="cat-img-preview" id="f-img-preview">
<?php if (!empty($item['image_effective'])): ?><img src="<?= $this->e($item['image_effective']) ?>" alt=""><?php else: ?><span class="text-muted"><?= $this->te('Kein Bild') ?></span><?php endif; ?>
</div>
<div class="cat-img-fields">
<input type="file" name="item_image" id="f-img" class="form-control form-control-sm" accept="image/jpeg,image/png,image/gif,image/webp">
<small class="form-hint"><?= $this->te('Eigenes Bild (JPG/PNG/GIF/WebP) – hat Vorrang vor dem Modellbild.') ?></small>
<?php if (!empty($item['image_path'])): ?>
<label class="cat-img-remove"><input type="checkbox" name="remove_image" value="1"> <?= $this->te('Eigenes Bild entfernen') ?></label>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="form-row-2">
<div class="form-group">
<label for="f-sku"><?= $this->te('Artikel-Nr. (SKU)') ?></label>
<input type="text" id="f-sku" name="sku" class="form-control" maxlength="100" value="<?= $val('sku') ?>">
</div>
<div class="form-group">
<label for="f-unit"><?= $this->te('Einheit') ?></label>
<input type="text" id="f-unit" name="unit" class="form-control" maxlength="30" value="<?= $val('unit', 'Stück') ?>">
</div>
</div>
<div class="form-row-2">
<div class="form-group">
<label for="f-cycle"><?= $this->te('Abrechnung') ?></label>
<select id="f-cycle" name="billing_cycle" class="form-control" data-searchable>
<?php foreach ($billing as $bk => $bl): ?>
<option value="<?= $this->e($bk) ?>" <?= ($item['billing_cycle'] ?? 'once') === $bk ? 'selected' : '' ?>><?= $this->e($bl) ?></option>
<?php endforeach; ?>
</select>
<small class="form-hint"><?= $this->te('„pro Monat/Jahr" z. B. für Abo-Lizenzen (M365, Adobe …).') ?></small>
</div>
<div class="form-group">
<label for="f-sup"><?= $this->te('Standard-Lieferant') ?></label>
<?php if (!empty($supplier_options)): ?>
<select id="f-sup" name="default_supplier_company_id" class="form-control" data-searchable <?= $kannFirmaAnlegen ? 'data-create-company' : '' ?>>
<option value=""><?= $this->te('– keiner –') ?></option>
<?php foreach ($supplier_options as $so): ?>
<option value="<?= (int)$so['id'] ?>" <?= (int)($item['default_supplier_company_id'] ?? 0) === (int)$so['id'] ? 'selected' : '' ?>><?= $this->e($so['name']) ?></option>
<?php endforeach; ?>
</select>
<small class="form-hint"><?= $this->te('Bestimmt den in der Übersicht gezeigten Preis.') ?></small>
<?php else: ?>
<input type="text" class="form-control" value="" placeholder="<?= $this->te('Kontakte-Modul für Firmenauswahl aktivieren') ?>" disabled>
<?php endif; ?>
</div>
</div>
<div class="form-group">
<label for="f-syn"><?= $this->te('Synonyme / Suchbegriffe') ?></label>
<input type="text" id="f-syn" name="synonyms" class="form-control" maxlength="255" value="<?= $val('synonyms') ?>" placeholder="<?= $this->te('z. B. Notebook, Ultrabook') ?>">
<small class="form-hint"><?= $this->te('Kommagetrennt – damit die Suche den Artikel auch unter anderen Begriffen findet.') ?></small>
</div>
<div class="form-group">
<label for="f-spec"><?= $this->te('Spezifikation') ?></label>
<textarea id="f-spec" name="spec" class="form-control" rows="3" placeholder="<?= $this->te('CPU, RAM, Displaygröße …') ?>"><?= $val('spec') ?></textarea>
</div>
<div class="form-group">
<label for="f-notes"><?= $this->te('Interne Notiz') ?></label>
<textarea id="f-notes" name="notes" class="form-control" rows="2"><?= $val('notes') ?></textarea>
</div>
<div class="form-group">
<label for="f-acc"><?= $this->te('Typisches Zubehör') ?></label>
<select id="f-acc" name="accessories[]" class="form-control" multiple size="6">
<?php foreach (($all_items ?? []) as $ai): if ((int)$ai['id'] === $id) { continue; } ?>
<option value="<?= (int)$ai['id'] ?>" <?= in_array((int)$ai['id'], $accIds, true) ? 'selected' : '' ?>><?= $this->e($ai['name']) ?></option>
<?php endforeach; ?>
</select>
<small class="form-hint"><?= $this->te('Mehrfachauswahl (Strg/Cmd) – erscheint auf der Detailseite als „Typisches Zubehör".') ?></small>
</div>
<div class="form-group">
<label><?= $this->te('Pflicht-Abhängigkeiten') ?></label>
<div id="req-rows"></div>
<button type="button" class="btn btn-default btn-sm" id="req-add"><i class="fas fa-plus"></i> <?= $this->te('Pflicht-Position hinzufügen') ?></button>
<small class="form-hint"><?= $this->te('Zwingend benötigte Artikel/Lizenzen (z. B. Smartphone → 1× MDM-Lizenz). Werden auf der Detailseite, in der Preisliste, Kalkulation und in Profilen automatisch mitgeführt und mitgerechnet.') ?></small>
</div>
<div class="settings-toggles">
<label class="settings-toggle">
<input type="checkbox" name="is_standard" value="1" <?= (int)($item['is_standard'] ?? 0) === 1 ? 'checked' : '' ?>>
<span class="toggle-text"><strong><?= $this->te('Standard-Artikel der Kategorie') ?></strong> <small><?= $this->te('Wird hervorgehoben; je Kategorie nur einer.') ?></small></span>
</label>
<label class="settings-toggle">
<input type="checkbox" name="is_active" value="1" <?= (int)($item['is_active'] ?? 1) === 1 ? 'checked' : '' ?>>
<span class="toggle-text"><strong><?= $this->te('Aktiv') ?></strong> <small><?= $this->te('Nur aktive Artikel erscheinen in Katalog und Kalkulation.') ?></small></span>
</label>
</div>
</div>
</div>
<div class="cat-form-actions">
<button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> <?= $this->te('Speichern') ?></button>
<a href="<?= $id > 0 ? '/catalog/items/' . $id : '/catalog' ?>" class="btn btn-default"><?= $this->te('Abbrechen') ?></a>
</div>
</form>
</div>
</div>
<template id="req-row-tpl">
<div class="cat-brow">
<select name="req_item_id[]" class="form-control" data-searchable><?= $reqOptions ?></select>
<input type="number" name="req_qty[]" class="form-control cat-brow-qty" value="1" min="1" title="<?= $this->te('Menge') ?>">
<input type="text" name="req_note[]" class="form-control cat-brow-note" maxlength="255" placeholder="<?= $this->te('Notiz (optional)') ?>">
<button type="button" class="btn btn-outline-danger btn-sm cat-brow-rm" title="<?= $this->te('Entfernen') ?>">×</button>
</div>
</template>
<script>
(function () {
var host = document.getElementById('req-rows');
var tpl = document.getElementById('req-row-tpl');
if (!host || !tpl) { return; }
function addRow(itemId, qty, note) {
var node = tpl.content.cloneNode(true);
var sel = node.querySelector('select');
var q = node.querySelector('input[name="req_qty[]"]');
var n = node.querySelector('input[name="req_note[]"]');
if (itemId) { sel.value = String(itemId); }
if (qty) { q.value = qty; }
if (note) { n.value = note; }
node.querySelector('.cat-brow-rm').addEventListener('click', function () { this.closest('.cat-brow').remove(); });
host.appendChild(node);
// Nachträglich eingefügte Zeile: durchsuchbares Dropdown aufwerten
// (beim Erst-Rendern übernimmt das die globale Initialisierung).
if (window.ssInit) { window.ssInit(host); }
}
document.getElementById('req-add').addEventListener('click', function () { addRow(); });
var existing = <?= json_encode(array_map(fn($r) => ['id' => (int)$r['id'], 'qty' => (int)$r['quantity'], 'note' => (string)($r['note'] ?? '')], $reqs), JSON_UNESCAPED_UNICODE) ?>;
existing.forEach(function (r) { addRow(r.id, r.qty, r.note); });
})();
// Inventar-Modell verknüpfen: automatische Vorbelegung (Bild + Stammdaten).
(function () {
var sel = document.getElementById('f-assettype');
var preview = document.getElementById('f-img-preview');
var fileInput = document.getElementById('f-img');
var hasOwn = <?= !empty($item['image_path']) ? 'true' : 'false' ?>;
function setPreviewImg(src) {
preview.innerHTML = src ? '<img src="' + src + '" alt="">' : '<span class="text-muted">Kein Bild</span>';
}
if (sel) {
sel.addEventListener('change', function () {
var opt = this.options[this.selectedIndex];
if (!opt || !opt.value) {
if (!hasOwn) { setPreviewImg(''); }
return;
}
// Stammdaten als Vorbelegung übernehmen (überschreibbar).
var man = opt.getAttribute('data-man') || '';
var model = opt.getAttribute('data-name') || ''; // asset_type.name = Modellbezeichnung
var spec = opt.getAttribute('data-spec') || '';
var nameEl = document.getElementById('f-name');
var manEl = document.getElementById('f-man');
var modelEl = document.getElementById('f-model');
var specEl = document.getElementById('f-spec');
if (manEl) { manEl.value = man; }
if (modelEl) { modelEl.value = model; }
if (specEl) { specEl.value = spec; }
if (nameEl) { nameEl.value = (man + ' ' + model).trim(); }
// Bildvorschau nur, wenn kein eigenes Bild Vorrang hat.
if (!hasOwn) { setPreviewImg(opt.getAttribute('data-image') || ''); }
});
}
if (fileInput) {
fileInput.addEventListener('change', function () {
var f = this.files && this.files[0];
if (f) { setPreviewImg(URL.createObjectURL(f)); hasOwn = true; }
});
}
})();
</script>
<?php if ($kannFirmaAnlegen): ?>
<?= $this->render('partials/_company_quick_create') ?>
<?php endif; ?>