Ablaufdatum-Berechnung
Dieser Commit ist enthalten in:
33
JOURNAL.md
33
JOURNAL.md
@@ -1063,4 +1063,35 @@ Die Session-Daten werden erst gefüllt, wenn der License Server API implementier
|
||||
- ✅ Select2 Dropdown mit Suchfunktion
|
||||
- ✅ Neue/bestehende Kunden können ausgewählt werden
|
||||
- ✅ E-Mail-Duplikate werden verhindert
|
||||
- ✅ Sowohl Einzellizenz als auch Batch unterstützt
|
||||
- ✅ Sowohl Einzellizenz als auch Batch unterstützt
|
||||
|
||||
## 2025-01-06: Automatische Ablaufdatum-Berechnung
|
||||
|
||||
**Problem:**
|
||||
- Manuelles Eingeben von Start- und Enddatum war umständlich
|
||||
- Fehleranfällig bei der Datumseingabe
|
||||
- Nicht intuitiv für Standard-Laufzeiten
|
||||
|
||||
**Lösung:**
|
||||
1. **Frontend-Änderungen:**
|
||||
- Startdatum + Laufzeit (Zahl) + Einheit (Tage/Monate/Jahre)
|
||||
- Ablaufdatum wird automatisch berechnet und angezeigt (read-only)
|
||||
- Standard: 1 Jahr Laufzeit voreingestellt
|
||||
2. **Backend-Validierung:**
|
||||
- Server-seitige Berechnung zur Sicherheit
|
||||
- Verwendung von `python-dateutil` für korrekte Monats-/Jahresberechnungen
|
||||
3. **Benutzerfreundlichkeit:**
|
||||
- Sofortige Neuberechnung bei Änderungen
|
||||
- Visuelle Hervorhebung des berechneten Datums
|
||||
|
||||
**Änderungen:**
|
||||
- `index.html`: Laufzeit-Eingabe statt Ablaufdatum
|
||||
- `batch_form.html`: Laufzeit-Eingabe statt Ablaufdatum
|
||||
- `app.py`: Datum-Berechnung in `/create` und `/batch` Routes
|
||||
- `requirements.txt`: `python-dateutil` hinzugefügt
|
||||
|
||||
**Status:**
|
||||
- ✅ Automatische Berechnung funktioniert
|
||||
- ✅ Frontend zeigt berechnetes Datum sofort an
|
||||
- ✅ Backend validiert die Berechnung
|
||||
- ✅ Standardwert (1 Jahr) voreingestellt
|
||||
@@ -1015,7 +1015,26 @@ def create_license():
|
||||
license_key = request.form["license_key"].upper() # Immer Großbuchstaben
|
||||
license_type = request.form["license_type"]
|
||||
valid_from = request.form["valid_from"]
|
||||
valid_until = request.form["valid_until"]
|
||||
|
||||
# Berechne valid_until basierend auf Laufzeit
|
||||
duration = int(request.form.get("duration", 1))
|
||||
duration_type = request.form.get("duration_type", "years")
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
start_date = datetime.strptime(valid_from, "%Y-%m-%d")
|
||||
|
||||
if duration_type == "days":
|
||||
end_date = start_date + timedelta(days=duration)
|
||||
elif duration_type == "months":
|
||||
end_date = start_date + relativedelta(months=duration)
|
||||
else: # years
|
||||
end_date = start_date + relativedelta(years=duration)
|
||||
|
||||
# Ein Tag abziehen, da der Starttag mitgezählt wird
|
||||
end_date = end_date - timedelta(days=1)
|
||||
valid_until = end_date.strftime("%Y-%m-%d")
|
||||
|
||||
# Validiere License Key Format
|
||||
if not validate_license_key(license_key):
|
||||
@@ -1110,7 +1129,26 @@ def batch_licenses():
|
||||
license_type = request.form["license_type"]
|
||||
quantity = int(request.form["quantity"])
|
||||
valid_from = request.form["valid_from"]
|
||||
valid_until = request.form["valid_until"]
|
||||
|
||||
# Berechne valid_until basierend auf Laufzeit
|
||||
duration = int(request.form.get("duration", 1))
|
||||
duration_type = request.form.get("duration_type", "years")
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
start_date = datetime.strptime(valid_from, "%Y-%m-%d")
|
||||
|
||||
if duration_type == "days":
|
||||
end_date = start_date + timedelta(days=duration)
|
||||
elif duration_type == "months":
|
||||
end_date = start_date + relativedelta(months=duration)
|
||||
else: # years
|
||||
end_date = start_date + relativedelta(years=duration)
|
||||
|
||||
# Ein Tag abziehen, da der Starttag mitgezählt wird
|
||||
end_date = end_date - timedelta(days=1)
|
||||
valid_until = end_date.strftime("%Y-%m-%d")
|
||||
|
||||
# Sicherheitslimit
|
||||
if quantity < 1 or quantity > 100:
|
||||
|
||||
@@ -8,3 +8,4 @@ openpyxl
|
||||
cryptography
|
||||
apscheduler
|
||||
requests
|
||||
python-dateutil
|
||||
|
||||
@@ -66,14 +66,28 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-2">
|
||||
<label for="validFrom" class="form-label">Gültig ab</label>
|
||||
<input type="date" class="form-control" id="validFrom" name="valid_from" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-1">
|
||||
<label for="duration" class="form-label">Laufzeit</label>
|
||||
<input type="number" class="form-control" id="duration" name="duration" value="1" min="1" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="durationType" class="form-label">Einheit</label>
|
||||
<select class="form-select" id="durationType" name="duration_type" required>
|
||||
<option value="days">Tage</option>
|
||||
<option value="months">Monate</option>
|
||||
<option value="years" selected>Jahre</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="validUntil" class="form-label">Gültig bis</label>
|
||||
<input type="date" class="form-control" id="validUntil" name="valid_until" required>
|
||||
<input type="date" class="form-control" id="validUntil" name="valid_until" readonly style="background-color: #e9ecef;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -111,15 +125,47 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Funktion zur Berechnung des Ablaufdatums
|
||||
function calculateValidUntil() {
|
||||
const validFrom = document.getElementById('validFrom').value;
|
||||
const duration = parseInt(document.getElementById('duration').value) || 1;
|
||||
const durationType = document.getElementById('durationType').value;
|
||||
|
||||
if (!validFrom) return;
|
||||
|
||||
const startDate = new Date(validFrom);
|
||||
let endDate = new Date(startDate);
|
||||
|
||||
switch(durationType) {
|
||||
case 'days':
|
||||
endDate.setDate(endDate.getDate() + duration);
|
||||
break;
|
||||
case 'months':
|
||||
endDate.setMonth(endDate.getMonth() + duration);
|
||||
break;
|
||||
case 'years':
|
||||
endDate.setFullYear(endDate.getFullYear() + duration);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ein Tag abziehen, da der Starttag mitgezählt wird
|
||||
endDate.setDate(endDate.getDate() - 1);
|
||||
|
||||
document.getElementById('validUntil').value = endDate.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// Event Listener für Änderungen
|
||||
document.getElementById('validFrom').addEventListener('change', calculateValidUntil);
|
||||
document.getElementById('duration').addEventListener('input', calculateValidUntil);
|
||||
document.getElementById('durationType').addEventListener('change', calculateValidUntil);
|
||||
|
||||
// Setze heutiges Datum als Standard
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('validFrom').value = today;
|
||||
|
||||
// Setze valid_until auf 1 Jahr später
|
||||
const oneYearLater = new Date();
|
||||
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
||||
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
||||
// Berechne initiales Ablaufdatum
|
||||
calculateValidUntil();
|
||||
|
||||
// Initialisiere Select2 für Kundenauswahl
|
||||
$('#customerSelect').select2({
|
||||
|
||||
@@ -58,9 +58,21 @@
|
||||
<label for="validFrom" class="form-label">Kaufdatum</label>
|
||||
<input type="date" class="form-control" id="validFrom" name="valid_from" required>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<label for="duration" class="form-label">Laufzeit</label>
|
||||
<input type="number" class="form-control" id="duration" name="duration" value="1" min="1" required>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<label for="durationType" class="form-label">Einheit</label>
|
||||
<select class="form-select" id="durationType" name="duration_type" required>
|
||||
<option value="days">Tage</option>
|
||||
<option value="months">Monate</option>
|
||||
<option value="years" selected>Jahre</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="validUntil" class="form-label">Ablaufdatum</label>
|
||||
<input type="date" class="form-control" id="validUntil" name="valid_until" required>
|
||||
<input type="date" class="form-control" id="validUntil" name="valid_until" readonly style="background-color: #e9ecef;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -153,15 +165,47 @@ document.getElementById('licenseKey').addEventListener('input', function(e) {
|
||||
e.target.value = e.target.value.toUpperCase();
|
||||
});
|
||||
|
||||
// Funktion zur Berechnung des Ablaufdatums
|
||||
function calculateValidUntil() {
|
||||
const validFrom = document.getElementById('validFrom').value;
|
||||
const duration = parseInt(document.getElementById('duration').value) || 1;
|
||||
const durationType = document.getElementById('durationType').value;
|
||||
|
||||
if (!validFrom) return;
|
||||
|
||||
const startDate = new Date(validFrom);
|
||||
let endDate = new Date(startDate);
|
||||
|
||||
switch(durationType) {
|
||||
case 'days':
|
||||
endDate.setDate(endDate.getDate() + duration);
|
||||
break;
|
||||
case 'months':
|
||||
endDate.setMonth(endDate.getMonth() + duration);
|
||||
break;
|
||||
case 'years':
|
||||
endDate.setFullYear(endDate.getFullYear() + duration);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ein Tag abziehen, da der Starttag mitgezählt wird
|
||||
endDate.setDate(endDate.getDate() - 1);
|
||||
|
||||
document.getElementById('validUntil').value = endDate.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// Event Listener für Änderungen
|
||||
document.getElementById('validFrom').addEventListener('change', calculateValidUntil);
|
||||
document.getElementById('duration').addEventListener('input', calculateValidUntil);
|
||||
document.getElementById('durationType').addEventListener('change', calculateValidUntil);
|
||||
|
||||
// Setze heutiges Datum als Standard für valid_from
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('validFrom').value = today;
|
||||
|
||||
// Setze valid_until auf 1 Jahr später als Standard
|
||||
const oneYearLater = new Date();
|
||||
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
||||
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
||||
// Berechne initiales Ablaufdatum
|
||||
calculateValidUntil();
|
||||
|
||||
// Initialisiere Select2 für Kundenauswahl
|
||||
$('#customerSelect').select2({
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren