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
|
- ✅ Select2 Dropdown mit Suchfunktion
|
||||||
- ✅ Neue/bestehende Kunden können ausgewählt werden
|
- ✅ Neue/bestehende Kunden können ausgewählt werden
|
||||||
- ✅ E-Mail-Duplikate werden verhindert
|
- ✅ 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_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"]
|
||||||
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
|
# Validiere License Key Format
|
||||||
if not validate_license_key(license_key):
|
if not validate_license_key(license_key):
|
||||||
@@ -1110,7 +1129,26 @@ def batch_licenses():
|
|||||||
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"]
|
||||||
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
|
# Sicherheitslimit
|
||||||
if quantity < 1 or quantity > 100:
|
if quantity < 1 or quantity > 100:
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ openpyxl
|
|||||||
cryptography
|
cryptography
|
||||||
apscheduler
|
apscheduler
|
||||||
requests
|
requests
|
||||||
|
python-dateutil
|
||||||
|
|||||||
@@ -66,14 +66,28 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-2">
|
||||||
<label for="validFrom" class="form-label">Gültig ab</label>
|
<label for="validFrom" class="form-label">Gültig ab</label>
|
||||||
<input type="date" class="form-control" id="validFrom" name="valid_from" required>
|
<input type="date" class="form-control" id="validFrom" name="valid_from" required>
|
||||||
</div>
|
</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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -111,15 +125,47 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<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
|
// Setze heutiges Datum als Standard
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
document.getElementById('validFrom').value = today;
|
document.getElementById('validFrom').value = today;
|
||||||
|
|
||||||
// Setze valid_until auf 1 Jahr später
|
// Berechne initiales Ablaufdatum
|
||||||
const oneYearLater = new Date();
|
calculateValidUntil();
|
||||||
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
|
||||||
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
|
||||||
|
|
||||||
// Initialisiere Select2 für Kundenauswahl
|
// Initialisiere Select2 für Kundenauswahl
|
||||||
$('#customerSelect').select2({
|
$('#customerSelect').select2({
|
||||||
|
|||||||
@@ -58,9 +58,21 @@
|
|||||||
<label for="validFrom" class="form-label">Kaufdatum</label>
|
<label for="validFrom" class="form-label">Kaufdatum</label>
|
||||||
<input type="date" class="form-control" id="validFrom" name="valid_from" required>
|
<input type="date" class="form-control" id="validFrom" name="valid_from" required>
|
||||||
</div>
|
</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">
|
<div class="col-md-2">
|
||||||
<label for="validUntil" class="form-label">Ablaufdatum</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -153,15 +165,47 @@ document.getElementById('licenseKey').addEventListener('input', function(e) {
|
|||||||
e.target.value = e.target.value.toUpperCase();
|
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
|
// Setze heutiges Datum als Standard für valid_from
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
document.getElementById('validFrom').value = today;
|
document.getElementById('validFrom').value = today;
|
||||||
|
|
||||||
// Setze valid_until auf 1 Jahr später als Standard
|
// Berechne initiales Ablaufdatum
|
||||||
const oneYearLater = new Date();
|
calculateValidUntil();
|
||||||
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
|
|
||||||
document.getElementById('validUntil').value = oneYearLater.toISOString().split('T')[0];
|
|
||||||
|
|
||||||
// Initialisiere Select2 für Kundenauswahl
|
// Initialisiere Select2 für Kundenauswahl
|
||||||
$('#customerSelect').select2({
|
$('#customerSelect').select2({
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren