Suchfunktion bei Key anlegen
Dieser Commit ist enthalten in:
@@ -44,7 +44,8 @@
|
|||||||
"Bash(docker-compose restart:*)",
|
"Bash(docker-compose restart:*)",
|
||||||
"Bash(find:*)",
|
"Bash(find:*)",
|
||||||
"Bash(docker network:*)",
|
"Bash(docker network:*)",
|
||||||
"Bash(curl:*)"
|
"Bash(curl:*)",
|
||||||
|
"Bash(find:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
37
JOURNAL.md
37
JOURNAL.md
@@ -1028,4 +1028,39 @@ Die Session-Daten werden erst gefüllt, wenn der License Server API implementier
|
|||||||
- ✅ Export als CSV
|
- ✅ Export als CSV
|
||||||
- ✅ Copy-to-Clipboard Funktionalität
|
- ✅ Copy-to-Clipboard Funktionalität
|
||||||
- ✅ Audit-Log-Integration
|
- ✅ Audit-Log-Integration
|
||||||
- ✅ Navigation aktualisiert
|
- ✅ Navigation aktualisiert
|
||||||
|
|
||||||
|
## 2025-01-06: Implementierung Searchable Dropdown für Kundenauswahl
|
||||||
|
|
||||||
|
**Problem:**
|
||||||
|
- Bei der Lizenzerstellung wurde immer ein neuer Kunde angelegt
|
||||||
|
- Keine Möglichkeit, Lizenzen für bestehende Kunden zu erstellen
|
||||||
|
- Bei vielen Kunden wäre ein normales Dropdown unübersichtlich
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
1. **Select2 Library** für searchable Dropdown integriert
|
||||||
|
2. **API-Endpoint `/api/customers`** für die Kundensuche erstellt
|
||||||
|
3. **Frontend angepasst:**
|
||||||
|
- Searchable Dropdown mit Live-Suche
|
||||||
|
- Option "Neuer Kunde" im Dropdown
|
||||||
|
- Eingabefelder erscheinen nur bei "Neuer Kunde"
|
||||||
|
4. **Backend-Logik verbessert:**
|
||||||
|
- Prüfung ob neuer oder bestehender Kunde
|
||||||
|
- E-Mail-Duplikatsprüfung vor Kundenerstellung
|
||||||
|
- Separate Audit-Logs für Kunde und Lizenz
|
||||||
|
5. **Datenbank:**
|
||||||
|
- UNIQUE Constraint auf E-Mail-Spalte hinzugefügt
|
||||||
|
|
||||||
|
**Änderungen:**
|
||||||
|
- `app.py`: Neuer API-Endpoint `/api/customers`, angepasste Routes `/create` und `/batch`
|
||||||
|
- `base.html`: Select2 CSS und JS eingebunden
|
||||||
|
- `index.html`: Kundenauswahl mit Select2 implementiert
|
||||||
|
- `batch_form.html`: Kundenauswahl mit Select2 implementiert
|
||||||
|
- `init.sql`: UNIQUE Constraint für E-Mail
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
- ✅ API-Endpoint funktioniert mit Pagination
|
||||||
|
- ✅ Select2 Dropdown mit Suchfunktion
|
||||||
|
- ✅ Neue/bestehende Kunden können ausgewählt werden
|
||||||
|
- ✅ E-Mail-Duplikate werden verhindert
|
||||||
|
- ✅ Sowohl Einzellizenz als auch Batch unterstützt
|
||||||
@@ -2,4 +2,3 @@
|
|||||||
# https://curl.se/docs/http-cookies.html
|
# https://curl.se/docs/http-cookies.html
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
#HttpOnly_localhost FALSE / FALSE 1749330711 admin_session kL1GAl-qrgTLBwfFpeQLngAfFne2ehrnKbWE3M3awqE
|
|
||||||
|
|||||||
@@ -764,6 +764,86 @@ def api_generate_key():
|
|||||||
'error': 'Fehler bei der Key-Generierung'
|
'error': 'Fehler bei der Key-Generierung'
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
|
@app.route("/api/customers", methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def api_customers():
|
||||||
|
"""API Endpoint für die Kundensuche mit Select2"""
|
||||||
|
try:
|
||||||
|
# Suchparameter
|
||||||
|
search = request.args.get('q', '').strip()
|
||||||
|
page = request.args.get('page', 1, type=int)
|
||||||
|
per_page = 20
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# SQL Query mit optionaler Suche
|
||||||
|
if search:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT c.id, c.name, c.email,
|
||||||
|
COUNT(l.id) as license_count
|
||||||
|
FROM customers c
|
||||||
|
LEFT JOIN licenses l ON c.id = l.customer_id
|
||||||
|
WHERE LOWER(c.name) LIKE LOWER(%s)
|
||||||
|
OR LOWER(c.email) LIKE LOWER(%s)
|
||||||
|
GROUP BY c.id, c.name, c.email
|
||||||
|
ORDER BY c.name
|
||||||
|
LIMIT %s OFFSET %s
|
||||||
|
""", (f'%{search}%', f'%{search}%', per_page, (page - 1) * per_page))
|
||||||
|
else:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT c.id, c.name, c.email,
|
||||||
|
COUNT(l.id) as license_count
|
||||||
|
FROM customers c
|
||||||
|
LEFT JOIN licenses l ON c.id = l.customer_id
|
||||||
|
GROUP BY c.id, c.name, c.email
|
||||||
|
ORDER BY c.name
|
||||||
|
LIMIT %s OFFSET %s
|
||||||
|
""", (per_page, (page - 1) * per_page))
|
||||||
|
|
||||||
|
customers = cur.fetchall()
|
||||||
|
|
||||||
|
# Format für Select2
|
||||||
|
results = []
|
||||||
|
for customer in customers:
|
||||||
|
results.append({
|
||||||
|
'id': customer[0],
|
||||||
|
'text': f"{customer[1]} - {customer[2]} ({customer[3]} Lizenzen)",
|
||||||
|
'name': customer[1],
|
||||||
|
'email': customer[2],
|
||||||
|
'license_count': customer[3]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Gesamtanzahl für Pagination
|
||||||
|
if search:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT COUNT(*) FROM customers
|
||||||
|
WHERE LOWER(name) LIKE LOWER(%s)
|
||||||
|
OR LOWER(email) LIKE LOWER(%s)
|
||||||
|
""", (f'%{search}%', f'%{search}%'))
|
||||||
|
else:
|
||||||
|
cur.execute("SELECT COUNT(*) FROM customers")
|
||||||
|
|
||||||
|
total_count = cur.fetchone()[0]
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Select2 Response Format
|
||||||
|
return jsonify({
|
||||||
|
'results': results,
|
||||||
|
'pagination': {
|
||||||
|
'more': (page * per_page) < total_count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Fehler bei Kundensuche: {str(e)}")
|
||||||
|
return jsonify({
|
||||||
|
'results': [],
|
||||||
|
'error': 'Fehler bei der Kundensuche'
|
||||||
|
}), 500
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
@@ -931,8 +1011,7 @@ def dashboard():
|
|||||||
@login_required
|
@login_required
|
||||||
def create_license():
|
def create_license():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
name = request.form["customer_name"]
|
customer_id = request.form.get("customer_id")
|
||||||
email = request.form["email"]
|
|
||||||
license_key = request.form["license_key"].upper() # Immer Großbuchstaben
|
license_key = request.form["license_key"].upper() # Immer Großbuchstaben
|
||||||
license_type = request.form["license_type"]
|
license_type = request.form["license_type"]
|
||||||
valid_from = request.form["valid_from"]
|
valid_from = request.form["valid_from"]
|
||||||
@@ -946,36 +1025,76 @@ def create_license():
|
|||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
# Kunde einfügen (falls nicht vorhanden)
|
try:
|
||||||
cur.execute("""
|
# Prüfe ob neuer Kunde oder bestehender
|
||||||
INSERT INTO customers (name, email, created_at)
|
if customer_id == "new":
|
||||||
VALUES (%s, %s, NOW())
|
# Neuer Kunde
|
||||||
RETURNING id
|
name = request.form.get("customer_name")
|
||||||
""", (name, email))
|
email = request.form.get("email")
|
||||||
customer_id = cur.fetchone()[0]
|
|
||||||
|
if not name:
|
||||||
|
flash('Kundenname ist erforderlich!', 'error')
|
||||||
|
return redirect(url_for('create_license'))
|
||||||
|
|
||||||
|
# Prüfe ob E-Mail bereits existiert
|
||||||
|
if email:
|
||||||
|
cur.execute("SELECT id, name FROM customers WHERE LOWER(email) = LOWER(%s)", (email,))
|
||||||
|
existing = cur.fetchone()
|
||||||
|
if existing:
|
||||||
|
flash(f'E-Mail bereits vergeben für Kunde: {existing[1]}', 'error')
|
||||||
|
return redirect(url_for('create_license'))
|
||||||
|
|
||||||
|
# Kunde einfügen
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO customers (name, email, created_at)
|
||||||
|
VALUES (%s, %s, NOW())
|
||||||
|
RETURNING id
|
||||||
|
""", (name, email))
|
||||||
|
customer_id = cur.fetchone()[0]
|
||||||
|
customer_info = {'name': name, 'email': email}
|
||||||
|
|
||||||
|
# Audit-Log für neuen Kunden
|
||||||
|
log_audit('CREATE', 'customer', customer_id,
|
||||||
|
new_values={'name': name, 'email': email})
|
||||||
|
else:
|
||||||
|
# Bestehender Kunde - hole Infos für Audit-Log
|
||||||
|
cur.execute("SELECT name, email FROM customers WHERE id = %s", (customer_id,))
|
||||||
|
customer_data = cur.fetchone()
|
||||||
|
if not customer_data:
|
||||||
|
flash('Kunde nicht gefunden!', 'error')
|
||||||
|
return redirect(url_for('create_license'))
|
||||||
|
customer_info = {'name': customer_data[0], 'email': customer_data[1]}
|
||||||
|
|
||||||
# Lizenz hinzufügen
|
# Lizenz hinzufügen
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active)
|
INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active)
|
||||||
VALUES (%s, %s, %s, %s, %s, TRUE)
|
VALUES (%s, %s, %s, %s, %s, TRUE)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
""", (license_key, customer_id, license_type, valid_from, valid_until))
|
""", (license_key, customer_id, license_type, valid_from, valid_until))
|
||||||
license_id = cur.fetchone()[0]
|
license_id = cur.fetchone()[0]
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Audit-Log
|
# Audit-Log
|
||||||
log_audit('CREATE', 'license', license_id,
|
log_audit('CREATE', 'license', license_id,
|
||||||
new_values={
|
new_values={
|
||||||
'license_key': license_key,
|
'license_key': license_key,
|
||||||
'customer_name': name,
|
'customer_name': customer_info['name'],
|
||||||
'customer_email': email,
|
'customer_email': customer_info['email'],
|
||||||
'license_type': license_type,
|
'license_type': license_type,
|
||||||
'valid_from': valid_from,
|
'valid_from': valid_from,
|
||||||
'valid_until': valid_until
|
'valid_until': valid_until
|
||||||
})
|
})
|
||||||
cur.close()
|
|
||||||
conn.close()
|
flash(f'Lizenz {license_key} erfolgreich erstellt!', 'success')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
logging.error(f"Fehler beim Erstellen der Lizenz: {str(e)}")
|
||||||
|
flash('Fehler beim Erstellen der Lizenz!', 'error')
|
||||||
|
finally:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
return redirect("/create")
|
return redirect("/create")
|
||||||
|
|
||||||
@@ -987,8 +1106,7 @@ def batch_licenses():
|
|||||||
"""Batch-Generierung mehrerer Lizenzen für einen Kunden"""
|
"""Batch-Generierung mehrerer Lizenzen für einen Kunden"""
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
# Formulardaten
|
# Formulardaten
|
||||||
name = request.form["customer_name"]
|
customer_id = request.form.get("customer_id")
|
||||||
email = request.form["email"]
|
|
||||||
license_type = request.form["license_type"]
|
license_type = request.form["license_type"]
|
||||||
quantity = int(request.form["quantity"])
|
quantity = int(request.form["quantity"])
|
||||||
valid_from = request.form["valid_from"]
|
valid_from = request.form["valid_from"]
|
||||||
@@ -1003,14 +1121,44 @@ def batch_licenses():
|
|||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Kunde einfügen (falls nicht vorhanden)
|
# Prüfe ob neuer Kunde oder bestehender
|
||||||
cur.execute("""
|
if customer_id == "new":
|
||||||
INSERT INTO customers (name, email, created_at)
|
# Neuer Kunde
|
||||||
VALUES (%s, %s, NOW())
|
name = request.form.get("customer_name")
|
||||||
ON CONFLICT (name, email) DO UPDATE SET name=EXCLUDED.name
|
email = request.form.get("email")
|
||||||
RETURNING id
|
|
||||||
""", (name, email))
|
if not name:
|
||||||
customer_id = cur.fetchone()[0]
|
flash('Kundenname ist erforderlich!', 'error')
|
||||||
|
return redirect(url_for('batch_licenses'))
|
||||||
|
|
||||||
|
# Prüfe ob E-Mail bereits existiert
|
||||||
|
if email:
|
||||||
|
cur.execute("SELECT id, name FROM customers WHERE LOWER(email) = LOWER(%s)", (email,))
|
||||||
|
existing = cur.fetchone()
|
||||||
|
if existing:
|
||||||
|
flash(f'E-Mail bereits vergeben für Kunde: {existing[1]}', 'error')
|
||||||
|
return redirect(url_for('batch_licenses'))
|
||||||
|
|
||||||
|
# Kunde einfügen
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO customers (name, email, created_at)
|
||||||
|
VALUES (%s, %s, NOW())
|
||||||
|
RETURNING id
|
||||||
|
""", (name, email))
|
||||||
|
customer_id = cur.fetchone()[0]
|
||||||
|
|
||||||
|
# Audit-Log für neuen Kunden
|
||||||
|
log_audit('CREATE', 'customer', customer_id,
|
||||||
|
new_values={'name': name, 'email': email})
|
||||||
|
else:
|
||||||
|
# Bestehender Kunde - hole Infos
|
||||||
|
cur.execute("SELECT name, email FROM customers WHERE id = %s", (customer_id,))
|
||||||
|
customer_data = cur.fetchone()
|
||||||
|
if not customer_data:
|
||||||
|
flash('Kunde nicht gefunden!', 'error')
|
||||||
|
return redirect(url_for('batch_licenses'))
|
||||||
|
name = customer_data[0]
|
||||||
|
email = customer_data[1]
|
||||||
|
|
||||||
# Lizenzen generieren und speichern
|
# Lizenzen generieren und speichern
|
||||||
generated_licenses = []
|
generated_licenses = []
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ CREATE TABLE IF NOT EXISTS customers (
|
|||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
email TEXT,
|
email TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_email UNIQUE (email)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS licenses (
|
CREATE TABLE IF NOT EXISTS licenses (
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}Admin Panel{% endblock %} - Lizenzverwaltung</title>
|
<title>{% block title %}Admin Panel{% endblock %} - Lizenzverwaltung</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
|
||||||
{% block extra_css %}{% endblock %}
|
{% block extra_css %}{% endblock %}
|
||||||
<style>
|
<style>
|
||||||
#session-timer {
|
#session-timer {
|
||||||
@@ -87,6 +89,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Session-Timer Konfiguration
|
// Session-Timer Konfiguration
|
||||||
|
|||||||
@@ -33,12 +33,19 @@
|
|||||||
|
|
||||||
<form method="post" action="/batch" accept-charset="UTF-8">
|
<form method="post" action="/batch" accept-charset="UTF-8">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-12">
|
||||||
|
<label for="customerSelect" class="form-label">Kunde auswählen</label>
|
||||||
|
<select class="form-select" id="customerSelect" name="customer_id" required>
|
||||||
|
<option value="">🔍 Kunde suchen oder neuen Kunden anlegen...</option>
|
||||||
|
<option value="new">➕ Neuer Kunde</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6" id="customerNameDiv" style="display: none;">
|
||||||
<label for="customerName" class="form-label">Kundenname</label>
|
<label for="customerName" class="form-label">Kundenname</label>
|
||||||
<input type="text" class="form-control" id="customerName" name="customer_name"
|
<input type="text" class="form-control" id="customerName" name="customer_name"
|
||||||
placeholder="Firma GmbH" accept-charset="UTF-8" required>
|
placeholder="Firma GmbH" accept-charset="UTF-8">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6" id="emailDiv" style="display: none;">
|
||||||
<label for="email" class="form-label">E-Mail</label>
|
<label for="email" class="form-label">E-Mail</label>
|
||||||
<input type="email" class="form-control" id="email" name="email"
|
<input type="email" class="form-control" id="email" name="email"
|
||||||
placeholder="kontakt@firma.de" accept-charset="UTF-8">
|
placeholder="kontakt@firma.de" accept-charset="UTF-8">
|
||||||
@@ -113,6 +120,83 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const oneYearLater = new Date();
|
const oneYearLater = new Date();
|
||||||
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
||||||
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Initialisiere Select2 für Kundenauswahl
|
||||||
|
$('#customerSelect').select2({
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
placeholder: '🔍 Kunde suchen oder neuen Kunden anlegen...',
|
||||||
|
allowClear: true,
|
||||||
|
ajax: {
|
||||||
|
url: '/api/customers',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 250,
|
||||||
|
data: function (params) {
|
||||||
|
return {
|
||||||
|
q: params.term,
|
||||||
|
page: params.page || 1
|
||||||
|
};
|
||||||
|
},
|
||||||
|
processResults: function (data, params) {
|
||||||
|
params.page = params.page || 1;
|
||||||
|
|
||||||
|
// "Neuer Kunde" Option immer oben anzeigen
|
||||||
|
const results = data.results || [];
|
||||||
|
if (params.page === 1) {
|
||||||
|
results.unshift({
|
||||||
|
id: 'new',
|
||||||
|
text: '➕ Neuer Kunde',
|
||||||
|
isNew: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: results,
|
||||||
|
pagination: data.pagination
|
||||||
|
};
|
||||||
|
},
|
||||||
|
cache: true
|
||||||
|
},
|
||||||
|
minimumInputLength: 0,
|
||||||
|
language: {
|
||||||
|
inputTooShort: function() { return ''; },
|
||||||
|
noResults: function() { return 'Keine Kunden gefunden'; },
|
||||||
|
searching: function() { return 'Suche...'; },
|
||||||
|
loadingMore: function() { return 'Lade weitere Ergebnisse...'; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event Handler für Kundenauswahl
|
||||||
|
$('#customerSelect').on('select2:select', function (e) {
|
||||||
|
const selectedValue = e.params.data.id;
|
||||||
|
const nameDiv = document.getElementById('customerNameDiv');
|
||||||
|
const emailDiv = document.getElementById('emailDiv');
|
||||||
|
const nameInput = document.getElementById('customerName');
|
||||||
|
const emailInput = document.getElementById('email');
|
||||||
|
|
||||||
|
if (selectedValue === 'new') {
|
||||||
|
// Zeige Eingabefelder für neuen Kunden
|
||||||
|
nameDiv.style.display = 'block';
|
||||||
|
emailDiv.style.display = 'block';
|
||||||
|
nameInput.required = true;
|
||||||
|
emailInput.required = true;
|
||||||
|
} else {
|
||||||
|
// Verstecke Eingabefelder bei bestehendem Kunden
|
||||||
|
nameDiv.style.display = 'none';
|
||||||
|
emailDiv.style.display = 'none';
|
||||||
|
nameInput.required = false;
|
||||||
|
emailInput.required = false;
|
||||||
|
nameInput.value = '';
|
||||||
|
emailInput.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear handler
|
||||||
|
$('#customerSelect').on('select2:clear', function (e) {
|
||||||
|
document.getElementById('customerNameDiv').style.display = 'none';
|
||||||
|
document.getElementById('emailDiv').style.display = 'none';
|
||||||
|
document.getElementById('customerName').required = false;
|
||||||
|
document.getElementById('email').required = false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Vorschau-Funktion
|
// Vorschau-Funktion
|
||||||
|
|||||||
@@ -19,11 +19,18 @@
|
|||||||
|
|
||||||
<form method="post" action="/create" accept-charset="UTF-8">
|
<form method="post" action="/create" accept-charset="UTF-8">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-12">
|
||||||
<label for="customerName" class="form-label">Kundenname</label>
|
<label for="customerSelect" class="form-label">Kunde auswählen</label>
|
||||||
<input type="text" class="form-control" id="customerName" name="customer_name" accept-charset="UTF-8" required>
|
<select class="form-select" id="customerSelect" name="customer_id" required>
|
||||||
|
<option value="">🔍 Kunde suchen oder neuen Kunden anlegen...</option>
|
||||||
|
<option value="new">➕ Neuer Kunde</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6" id="customerNameDiv" style="display: none;">
|
||||||
|
<label for="customerName" class="form-label">Kundenname</label>
|
||||||
|
<input type="text" class="form-control" id="customerName" name="customer_name" accept-charset="UTF-8">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6" id="emailDiv" style="display: none;">
|
||||||
<label for="email" class="form-label">E-Mail</label>
|
<label for="email" class="form-label">E-Mail</label>
|
||||||
<input type="email" class="form-control" id="email" name="email" accept-charset="UTF-8">
|
<input type="email" class="form-control" id="email" name="email" accept-charset="UTF-8">
|
||||||
</div>
|
</div>
|
||||||
@@ -155,6 +162,83 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const oneYearLater = new Date();
|
const oneYearLater = new Date();
|
||||||
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
||||||
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Initialisiere Select2 für Kundenauswahl
|
||||||
|
$('#customerSelect').select2({
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
placeholder: '🔍 Kunde suchen oder neuen Kunden anlegen...',
|
||||||
|
allowClear: true,
|
||||||
|
ajax: {
|
||||||
|
url: '/api/customers',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 250,
|
||||||
|
data: function (params) {
|
||||||
|
return {
|
||||||
|
q: params.term,
|
||||||
|
page: params.page || 1
|
||||||
|
};
|
||||||
|
},
|
||||||
|
processResults: function (data, params) {
|
||||||
|
params.page = params.page || 1;
|
||||||
|
|
||||||
|
// "Neuer Kunde" Option immer oben anzeigen
|
||||||
|
const results = data.results || [];
|
||||||
|
if (params.page === 1) {
|
||||||
|
results.unshift({
|
||||||
|
id: 'new',
|
||||||
|
text: '➕ Neuer Kunde',
|
||||||
|
isNew: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: results,
|
||||||
|
pagination: data.pagination
|
||||||
|
};
|
||||||
|
},
|
||||||
|
cache: true
|
||||||
|
},
|
||||||
|
minimumInputLength: 0,
|
||||||
|
language: {
|
||||||
|
inputTooShort: function() { return ''; },
|
||||||
|
noResults: function() { return 'Keine Kunden gefunden'; },
|
||||||
|
searching: function() { return 'Suche...'; },
|
||||||
|
loadingMore: function() { return 'Lade weitere Ergebnisse...'; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event Handler für Kundenauswahl
|
||||||
|
$('#customerSelect').on('select2:select', function (e) {
|
||||||
|
const selectedValue = e.params.data.id;
|
||||||
|
const nameDiv = document.getElementById('customerNameDiv');
|
||||||
|
const emailDiv = document.getElementById('emailDiv');
|
||||||
|
const nameInput = document.getElementById('customerName');
|
||||||
|
const emailInput = document.getElementById('email');
|
||||||
|
|
||||||
|
if (selectedValue === 'new') {
|
||||||
|
// Zeige Eingabefelder für neuen Kunden
|
||||||
|
nameDiv.style.display = 'block';
|
||||||
|
emailDiv.style.display = 'block';
|
||||||
|
nameInput.required = true;
|
||||||
|
emailInput.required = true;
|
||||||
|
} else {
|
||||||
|
// Verstecke Eingabefelder bei bestehendem Kunden
|
||||||
|
nameDiv.style.display = 'none';
|
||||||
|
emailDiv.style.display = 'none';
|
||||||
|
nameInput.required = false;
|
||||||
|
emailInput.required = false;
|
||||||
|
nameInput.value = '';
|
||||||
|
emailInput.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear handler
|
||||||
|
$('#customerSelect').on('select2:clear', function (e) {
|
||||||
|
document.getElementById('customerNameDiv').style.display = 'none';
|
||||||
|
document.getElementById('emailDiv').style.display = 'none';
|
||||||
|
document.getElementById('customerName').required = false;
|
||||||
|
document.getElementById('email').required = false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren