Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c855ed9d9 | |||
| b09a6f7720 | |||
| 8c5185bd8c | |||
| 0b30f8463f | |||
| 671a9e8e85 | |||
| 3d9e5c470a | |||
| e636242542 |
+2
-1
@@ -3382,6 +3382,7 @@ def library_loans_admin():
|
||||
'item_code': item_doc.get('Code_4', ''),
|
||||
'item_author': item_doc.get('Author', ''),
|
||||
'item_isbn': item_doc.get('ISBN', ''),
|
||||
'item_cost_raw': item_doc.get('Anschaffungskosten', ''),
|
||||
'user': decrypted_user,
|
||||
'status': record.get('Status', ''),
|
||||
'start': fmt_dt(record.get('Start')),
|
||||
@@ -9134,7 +9135,7 @@ def library_item_invoices(item_id):
|
||||
flash('Bibliotheksmedium nicht gefunden.', 'error')
|
||||
return redirect(url_for('library_loans_admin'))
|
||||
|
||||
borrow_docs = list(ausleihungen.find(
|
||||
borrow_docs = list(items_col.find(
|
||||
{
|
||||
'Item': str(item_doc.get('_id')),
|
||||
'InvoiceData': {'$exists': True, '$ne': {}}
|
||||
|
||||
@@ -1261,10 +1261,10 @@
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_school_settings') }}">Schulstammdaten</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('admin_borrowings', False) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_borrowings') }}">Ausleihen</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_borrowings') }}">Alle Ausleihen</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('admin_damaged_items', False) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Defekte Items</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Alle defekten Items</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.actions.get('can_view_logs', False) and current_permissions.pages.get('admin_audit_dashboard', False) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_audit_dashboard') }}">Audit Dashboard</a></li>
|
||||
@@ -1372,7 +1372,7 @@
|
||||
{% if 'username' in session and current_permissions.actions.get('can_manage_settings', False) %}
|
||||
<li><h6 class="dropdown-header">Bibliotheks-Verwaltung</h6></li>
|
||||
{% if current_permissions.pages.get('library_loans_admin', False) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('library_loans_admin') }}">Ausleihen / Defekte Items</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('library_loans_admin') }}">Alle Ausleihen/Alle Defekten Items</a></li>
|
||||
{% endif %}
|
||||
{% if student_cards_module_enabled %}
|
||||
{% if current_permissions.actions.get('can_manage_users', False) %}
|
||||
|
||||
@@ -407,14 +407,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="damage-invoice-modal" style="display:none; position:fixed; inset:0; background:rgba(15,23,42,0.72); z-index:9999; padding:20px; overflow:auto;">
|
||||
<div id="damage-invoice-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" style="display:none; position:fixed; inset:0; background:rgba(15,23,42,0.72); z-index:9999; padding:20px; overflow:auto;">
|
||||
<div style="max-width:760px; margin:40px auto; background: var(--ui-surface); border-radius:12px; padding:24px; box-shadow:0 20px 60px rgba(0,0,0,0.3);">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:18px;">
|
||||
<div>
|
||||
<h2 style="margin:0;">Rechnung erstellen</h2>
|
||||
<h2 id="modal-title" style="margin:0;">Rechnung erstellen</h2>
|
||||
<p style="margin:6px 0 0; color:#666;">Die Rechnung nutzt das bestehende Rechnungssystem und kann direkt nach der Schadensmeldung erstellt werden.</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" onclick="closeDamageInvoiceModal()">Schließen</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="closeDamageInvoiceModal()" aria-label="Modal schließen">Schließen</button>
|
||||
</div>
|
||||
|
||||
<form id="damage-invoice-form" method="post" action="">
|
||||
@@ -498,42 +498,48 @@
|
||||
|
||||
const description = noteInput.trim() || 'Schaden erneut gemeldet';
|
||||
|
||||
// Visuelles Feedback: Button deaktivieren und Text ändern
|
||||
const originalText = button.textContent;
|
||||
button.disabled = true;
|
||||
button.textContent = 'Speichere...';
|
||||
|
||||
fetch(`/report_damage/${itemId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ description })
|
||||
})
|
||||
.then(response => response.json().then(data => ({ ok: response.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (!ok || !data.success) {
|
||||
.then(async response => {
|
||||
// Robustes JSON-Parsing (verhindert Absturz, falls der Server kein JSON zurückgibt)
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !data.success) {
|
||||
throw new Error(data.message || 'Fehler beim Speichern der Schadensmeldung.');
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.then(data => {
|
||||
if (confirm('Schaden gespeichert. Soll direkt eine Rechnung erstellt werden?')) {
|
||||
openDamageInvoiceModal(row, description);
|
||||
// Button wieder zurücksetzen, da die Seite nicht neu geladen wird
|
||||
button.disabled = false;
|
||||
button.textContent = originalText;
|
||||
return;
|
||||
}
|
||||
|
||||
// Bei "Abbrechen" im Confirm -> Neuladen der Tabelle
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
alert(error.message || 'Fehler beim Speichern der Schadensmeldung.');
|
||||
alert(error.message || 'Ein unbekannter Fehler ist aufgetreten.');
|
||||
// Fehlerbehandlung: Button wieder aktiv schalten
|
||||
button.disabled = false;
|
||||
button.textContent = originalText;
|
||||
});
|
||||
}
|
||||
|
||||
window.openDamageReportPrompt = openDamageReportPrompt;
|
||||
|
||||
function openDamageInvoiceModal(row, description) {
|
||||
const modal = document.getElementById('damage-invoice-modal');
|
||||
const form = document.getElementById('damage-invoice-form');
|
||||
const inputItem = document.getElementById('damage-invoice-item');
|
||||
const inputBorrower = document.getElementById('damage-invoice-borrower');
|
||||
const inputCode = document.getElementById('damage-invoice-code');
|
||||
const inputAmount = document.getElementById('damage-invoice-amount');
|
||||
const inputReason = document.getElementById('damage-invoice-reason');
|
||||
const replaceBtn = document.getElementById('damage-invoice-replace-btn');
|
||||
|
||||
if (!modal || !form) {
|
||||
if (!damageInvoiceModal || !damageInvoiceForm) {
|
||||
console.error("Modal oder Formular nicht gefunden.");
|
||||
return;
|
||||
}
|
||||
@@ -542,40 +548,43 @@
|
||||
const itemName = row.dataset.itemName || '';
|
||||
const borrower = row.dataset.userName || '';
|
||||
const itemCode = row.dataset.itemCode || '';
|
||||
|
||||
// KORREKTUR: Jetzt greifen wir auf das richtige dataset-Attribut zu
|
||||
const itemCost = row.dataset.itemCost || '';
|
||||
|
||||
form.action = "{{ url_for('admin_create_invoice', borrow_id='__BORROW_ID__') }}".replace('__BORROW_ID__', borrowId);
|
||||
damageInvoiceForm.action = "{{ url_for('admin_create_invoice', borrow_id='__BORROW_ID__') }}".replace('__BORROW_ID__', borrowId);
|
||||
|
||||
inputItem.value = itemName;
|
||||
inputBorrower.value = borrower;
|
||||
inputCode.value = itemCode;
|
||||
damageInvoiceItem.value = itemName;
|
||||
damageInvoiceBorrower.value = borrower;
|
||||
damageInvoiceCode.value = itemCode;
|
||||
|
||||
// Feld zunächst leeren, damit der Ersetzen-Button genutzt werden kann
|
||||
inputAmount.value = '';
|
||||
damageInvoiceAmount.value = '';
|
||||
|
||||
// Original-Preis im Button als data-Attribut hinterlegen
|
||||
if (replaceBtn) {
|
||||
replaceBtn.dataset.price = String(itemCost).replace(' EUR', '').trim();
|
||||
if (damageInvoiceReplaceBtn) {
|
||||
damageInvoiceReplaceBtn.dataset.acquisition_costs = String(itemCost).replace(' EUR', '').trim();
|
||||
}
|
||||
|
||||
inputReason.value = description || `Schaden gemeldet für ${itemName}`;
|
||||
damageInvoiceReason.value = description || `Schaden gemeldet für ${itemName}`;
|
||||
|
||||
modal.style.display = 'block';
|
||||
inputAmount.focus();
|
||||
damageInvoiceModal.style.display = 'block';
|
||||
|
||||
// Accessibility: Fokus ins erste aktivierbare Feld setzen
|
||||
damageInvoiceAmount.focus();
|
||||
}
|
||||
|
||||
function closeDamageInvoiceModal() {
|
||||
const modal = document.getElementById('damage-invoice-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
if (damageInvoiceModal) {
|
||||
damageInvoiceModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Event-Listener für den Ersetzen-Button
|
||||
if (damageInvoiceReplaceBtn) {
|
||||
damageInvoiceReplaceBtn.addEventListener('click', function() {
|
||||
if (this.dataset.price) {
|
||||
damageInvoiceAmount.value = this.dataset.price;
|
||||
if (this.dataset.acquisition_costs) {
|
||||
damageInvoiceAmount.value = this.dataset.acquisition_costs;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@
|
||||
</select>
|
||||
<input type="text" id="activeStudentCard" placeholder="Aktiver Ausweis (gescannt)">
|
||||
<input type="text" id="manualItemCode" placeholder="Manueller Mediencode (optional)" style="min-width:180px;">
|
||||
<button id="resetCardBtn" class="button" type="button">Ausweis löschen</button>
|
||||
<button id="resetCardBtn" class="button" type="button">Feld zurücksetzen</button>
|
||||
<button id="toggleScannerBtn" class="button" type="button">Scanner starten</button>
|
||||
<label style="display:flex; align-items:center; gap:8px; margin-left:6px;">
|
||||
<input type="checkbox" id="keyboardScannerToggle">
|
||||
|
||||
Reference in New Issue
Block a user