Files
Inventarsystem/Web/templates/admin_borrowings.html
T
2026-04-10 14:48:52 +02:00

316 lines
14 KiB
HTML

<!--
Copyright 2025-2026 AIIrondev
Licensed under the Inventarsystem EULA (Endbenutzer-Lizenzvertrag).
See Legal/LICENSE for the full license text.
Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited.
For commercial licensing inquiries: https://github.com/AIIrondev
-->
{% extends 'base.html' %}
{% block content %}
<div class="container">
<h1>Ausleihen verwalten</h1>
<p>Liste aller aktiven und geplanten Ausleihen. Sie können einzelne Einträge zurücksetzen oder bei Schäden eine Rechnung als PDF erstellen.</p>
{% if entries and entries|length > 0 %}
<!-- Filter Section -->
<div style="margin-bottom: 20px; padding: 15px; background-color: #f5f5f5; border-radius: 5px;">
<h3>Filter</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">
<div>
<label for="user-filter" style="display: block; margin-bottom: 5px; font-weight: bold;">Benutzer:</label>
<input
type="text"
id="user-filter"
placeholder="Nach Benutzer filtern..."
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px;"
/>
</div>
<div>
<label for="item-filter" style="display: block; margin-bottom: 5px; font-weight: bold;">Element:</label>
<input
type="text"
id="item-filter"
placeholder="Nach Element filtern..."
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px;"
/>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div>
<label for="status-filter" style="display: block; margin-bottom: 5px; font-weight: bold;">Status:</label>
<select
id="status-filter"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px;"
>
<option value="">Alle</option>
<option value="active">Aktiv</option>
<option value="planned">Geplant</option>
</select>
</div>
<div style="display: flex; align-items: flex-end;">
<button
id="reset-filters"
class="btn btn-secondary"
style="width: 100%;"
>
Filter zurücksetzen
</button>
</div>
</div>
</div>
<table class="table" style="width:100%; border-collapse: collapse;">
<thead>
<tr>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Status</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Element</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Element-ID</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Benutzer</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Start</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Ende</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Stunde</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Preis</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Rechnung</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Notizen</th>
<th style="text-align:left; padding:8px; border-bottom:1px solid #ddd;">Aktion</th>
</tr>
</thead>
<tbody id="borrowing-table-body">
{% for e in entries %}
<tr class="borrowing-row"
data-user="{{ e.user }}"
data-item="{{ e.item_name }}"
data-status="{{ e.status }}"
data-borrow-id="{{ e.id }}"
data-item-id="{{ e.item_id }}"
data-item-name="{{ e.item_name }}"
data-item-code="{{ e.item_code }}"
data-item-cost="{{ e.item_cost_raw }}"
data-user-name="{{ e.user }}">
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.status }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.item_name }}</td>
<td style="padding:8px; border-bottom:1px solid #eee; font-family:monospace;">{{ e.item_id }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.user }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.start }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.end }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.period }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.item_cost }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">
{% if e.invoice_number %}
<div style="font-weight:600;">{{ e.invoice_number }}</div>
<div style="font-size:0.85rem; color:#666;">{{ e.invoice_amount }}</div>
{% if e.invoice_paid %}
<div style="display:inline-block; margin-top:6px; padding:2px 8px; border-radius:999px; background:#dcfce7; color:#166534; font-size:0.75rem; font-weight:700;">Bezahlt</div>
{% if e.invoice_paid_at %}
<div style="font-size:0.75rem; color:#666; margin-top:4px;">am {{ e.invoice_paid_at }}</div>
{% endif %}
{% else %}
<div style="display:inline-block; margin-top:6px; padding:2px 8px; border-radius:999px; background:#fee2e2; color:#991b1b; font-size:0.75rem; font-weight:700;">Offen</div>
{% endif %}
{% else %}
<div style="font-size:0.9rem; color:#666;">Noch keine Rechnung</div>
{% endif %}
</td>
<td style="padding:8px; border-bottom:1px solid #eee;">{{ e.notes }}</td>
<td style="padding:8px; border-bottom:1px solid #eee;">
<div style="display:flex; flex-wrap:wrap; gap:8px;">
<button type="button" class="btn btn-danger" onclick="openInvoiceModal(this)" {% if e.status != 'active' %}disabled{% endif %}>Rechnung erstellen</button>
{% if e.invoice_number and ((not e.invoice_paid) or e.has_damage) %}
<form method="post" action="{{ url_for('admin_finalize_invoice_and_repair', borrow_id=e.id) }}" onsubmit="return confirm('Rechnung als bezahlt und Element als repariert markieren?');">
<button type="submit" class="btn btn-success">
{% if not e.invoice_paid and e.has_damage %}
Bezahlt + repariert
{% elif not e.invoice_paid %}
Als bezahlt markieren
{% else %}
Als repariert markieren
{% endif %}
</button>
</form>
{% endif %}
<form method="post" action="{{ url_for('admin_reset_borrowing', borrow_id=e.id) }}" onsubmit="return confirm('Ausleihe zurücksetzen?');">
<button type="submit" class="btn btn-warning">Zurücksetzen</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div id="no-results" style="display: none; text-align: center; padding: 20px; color: #666;">
<p>Keine Einträge mit den aktuellen Filtern gefunden.</p>
</div>
{% else %}
<div class="empty-state">
<p>Keine aktiven oder geplanten Ausleihen gefunden.</p>
</div>
{% endif %}
</div>
<div id="invoice-modal" 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:#fff; 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>
<p style="margin:6px 0 0; color:#666;">Preis wird aus den Anschaffungskosten vorbelegt und kann bei Bedarf angepasst werden.</p>
</div>
<button type="button" class="btn btn-secondary" onclick="closeInvoiceModal()">Schließen</button>
</div>
<form id="invoice-form" method="post" action="">
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(240px, 1fr)); gap:16px; margin-bottom:16px;">
<div>
<label for="invoice-item" style="display:block; font-weight:700; margin-bottom:6px;">Element</label>
<input id="invoice-item" type="text" readonly style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px; background:#f8fafc;">
</div>
<div>
<label for="invoice-borrower" style="display:block; font-weight:700; margin-bottom:6px;">Schüler</label>
<input id="invoice-borrower" type="text" readonly style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px; background:#f8fafc;">
</div>
<div>
<label for="invoice-code" style="display:block; font-weight:700; margin-bottom:6px;">Element-ID / Code</label>
<input id="invoice-code" type="text" readonly style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px; background:#f8fafc;">
</div>
<div>
<label for="invoice-amount" style="display:block; font-weight:700; margin-bottom:6px;">Preis</label>
<input id="invoice-amount" name="invoice_amount" type="text" required style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px;" placeholder="z.B. 12,50">
</div>
</div>
<div style="margin-bottom:16px;">
<label for="damage-reason" style="display:block; font-weight:700; margin-bottom:6px;">Schadensbeschreibung</label>
<textarea id="damage-reason" name="damage_reason" rows="5" required style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px; resize:vertical;" placeholder="Beschreiben Sie kurz den Schaden oder die Zerstörung."></textarea>
</div>
<div style="display:flex; flex-wrap:wrap; gap:16px; align-items:center; margin-bottom:18px;">
<label style="display:flex; align-items:center; gap:8px;">
<input type="checkbox" name="mark_destroyed" checked>
Element als zerstört markieren
</label>
<label style="display:flex; align-items:center; gap:8px;">
<input type="checkbox" name="close_borrowing" checked>
Ausleihe abschließen
</label>
</div>
<div style="display:flex; justify-content:flex-end; gap:10px;">
<button type="button" class="btn btn-secondary" onclick="closeInvoiceModal()">Abbrechen</button>
<button type="submit" class="btn btn-danger">PDF-Rechnung erstellen</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const userFilter = document.getElementById('user-filter');
const itemFilter = document.getElementById('item-filter');
const statusFilter = document.getElementById('status-filter');
const resetButton = document.getElementById('reset-filters');
const tableBody = document.getElementById('borrowing-table-body');
const noResults = document.getElementById('no-results');
function applyFilters() {
const userValue = userFilter.value.toLowerCase();
const itemValue = itemFilter.value.toLowerCase();
const statusValue = statusFilter.value;
let visibleCount = 0;
document.querySelectorAll('.borrowing-row').forEach(row => {
const user = row.getAttribute('data-user').toLowerCase();
const item = row.getAttribute('data-item').toLowerCase();
const status = row.getAttribute('data-status');
const userMatch = user.includes(userValue);
const itemMatch = item.includes(itemValue);
const statusMatch = statusValue === '' || status === statusValue;
const shouldShow = userMatch && itemMatch && statusMatch;
if (shouldShow) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
// Show/hide no results message
if (visibleCount === 0) {
noResults.style.display = 'block';
} else {
noResults.style.display = 'none';
}
}
function resetFilters() {
userFilter.value = '';
itemFilter.value = '';
statusFilter.value = '';
applyFilters();
}
function normalizeMoneyInput(value) {
if (!value) {
return '';
}
return String(value).replace(' EUR', '').trim();
}
window.openInvoiceModal = function(button) {
const row = button.closest('.borrowing-row');
if (!row) {
return;
}
const borrowId = row.getAttribute('data-borrow-id') || '';
const itemName = row.getAttribute('data-item-name') || '';
const borrower = row.getAttribute('data-user-name') || '';
const itemCode = row.getAttribute('data-item-code') || '';
const itemCost = row.getAttribute('data-item-cost') || '';
const modal = document.getElementById('invoice-modal');
const form = document.getElementById('invoice-form');
const amountField = document.getElementById('invoice-amount');
const reasonField = document.getElementById('damage-reason');
form.action = "{{ url_for('admin_create_invoice', borrow_id='__BORROW_ID__') }}".replace('__BORROW_ID__', borrowId);
document.getElementById('invoice-item').value = itemName;
document.getElementById('invoice-borrower').value = borrower;
document.getElementById('invoice-code').value = itemCode;
amountField.value = normalizeMoneyInput(itemCost);
reasonField.value = 'Das Element ' + itemName + ' wurde während der Ausleihe beschädigt oder zerstört.';
modal.style.display = 'block';
amountField.focus();
};
window.closeInvoiceModal = function() {
const modal = document.getElementById('invoice-modal');
if (modal) {
modal.style.display = 'none';
}
};
const modal = document.getElementById('invoice-modal');
if (modal) {
modal.addEventListener('click', function(event) {
if (event.target === modal) {
closeInvoiceModal();
}
});
}
// Add event listeners
userFilter.addEventListener('input', applyFilters);
itemFilter.addEventListener('input', applyFilters);
statusFilter.addEventListener('change', applyFilters);
resetButton.addEventListener('click', resetFilters);
});
</script>
{% endblock %}