feat(library): add damage history view alongside invoices for items
Release Inventarsystem / release-docker (push) Successful in 2m14s

This commit is contained in:
2026-08-21 13:51:16 +02:00
parent f0b112b34c
commit e46ee80cf2
2 changed files with 135 additions and 7 deletions
+76 -3
View File
@@ -9335,9 +9335,10 @@ def resolve_repaired_item_funct(item_id, action, new_code_4="", current_user="ad
print(f"Error resolving repair for item {item_id}: {e}") print(f"Error resolving repair for item {item_id}: {e}")
return False, "Ein Datenbankfehler ist aufgetreten." return False, "Ein Datenbankfehler ist aufgetreten."
@app.route('/admin/library/items/<item_id>/invoices', methods=['GET']) @app.route('/admin/library/items/<item_id>/invoices', methods=['GET'])
def library_item_invoices(item_id): def library_item_invoices(item_id):
"""Show all stored invoices for one specific library item.""" """Show all stored invoices and damages for one specific library item."""
if 'username' not in session: if 'username' not in session:
flash( flash(
'Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!',
@@ -9373,6 +9374,9 @@ def library_item_invoices(item_id):
item_id_obj = item_doc.get('_id') item_id_obj = item_doc.get('_id')
item_id_str = str(item_id_obj) item_id_str = str(item_id_obj)
# ==========================================
# 1. RECHNUNGSHISTORIE (Bestehender Code)
# ==========================================
borrow_docs = list(ausleihungen.find( borrow_docs = list(ausleihungen.find(
{ {
'Item': {'$in': [item_id_str, item_id_obj]}, 'Item': {'$in': [item_id_str, item_id_obj]},
@@ -9419,6 +9423,74 @@ def library_item_invoices(item_id):
'invoice_paid_by': invoice_data.get('paid_by', ''), 'invoice_paid_by': invoice_data.get('paid_by', ''),
}) })
# ==========================================
# 2. SCHADENSHISTORIE (Neuer Code)
# ==========================================
raw_damages = []
# A: Schäden, die manuell am Element (Item-Dokument) hinterlegt wurden
item_damages = item_doc.get('Damages', [])
if not isinstance(item_damages, list): item_damages = []
# Fallback falls sie klein geschrieben wurden
if isinstance(item_doc.get('damages', []), list):
item_damages.extend(item_doc.get('damages', []))
for d in item_damages:
if not isinstance(d, dict): continue
raw_damages.append({
'raw_date': d.get('date') or d.get('created_at'),
'source': 'Manuell / Element',
'user': d.get('reported_by') or d.get('user') or 'System/Admin',
'description': d.get('description') or d.get('reason') or 'Keine Beschreibung',
'status': d.get('status', 'Unbekannt'),
'borrow_id': None
})
# B: Schäden, die in Ausleih-Dokumenten vermerkt sind
damage_borrow_docs = list(ausleihungen.find(
{
'Item': {'$in': [item_id_str, item_id_obj]},
'$or': [
{'Damage': {'$exists': True, '$ne': ''}},
{'DamageData': {'$exists': True, '$ne': {}}},
{'Condition': {'$exists': True, '$ne': ''}} # Falls Zustand gemeldet wurde
]
}
))
for b in damage_borrow_docs:
borrow_user = decrypt_text(b.get('User', '')) if b.get('User') else '—'
d_data = b.get('DamageData') or {}
if not isinstance(d_data, dict): d_data = {}
desc = d_data.get('description') or b.get('Damage') or b.get('Condition')
if desc and str(desc).strip():
raw_damages.append({
'raw_date': d_data.get('reported_at') or b.get('End') or b.get('Start'),
'source': f"Ausleihe",
'user': borrow_user,
'description': desc,
'status': d_data.get('status', 'Gemeldet'),
'borrow_id': str(b.get('_id'))
})
# Daten chronologisch (neueste zuerst) sortieren
def get_sort_key(entry):
d = entry['raw_date']
return d if isinstance(d, datetime.datetime) else datetime.datetime.min
raw_damages.sort(key=get_sort_key, reverse=True)
# Datums-Objekte für das Template formatieren
damage_entries = []
for entry in raw_damages:
d_date = entry.pop('raw_date')
entry['date'] = d_date.strftime('%d.%m.%Y %H:%M') if isinstance(d_date, datetime.datetime) else str(
d_date or 'Unbekannt')
damage_entries.append(entry)
return render_template( return render_template(
'library_item_invoices.html', 'library_item_invoices.html',
item={ item={
@@ -9429,14 +9501,15 @@ def library_item_invoices(item_id):
'isbn': item_doc.get('ISBN', ''), 'isbn': item_doc.get('ISBN', ''),
}, },
invoices=entries, invoices=entries,
damages=damage_entries, # << NEU
library_module_enabled=cfg.MODULES.is_enabled('library'), library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'), student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
mail_module_enabled=cfg.MODULES.is_enabled('mail') mail_module_enabled=cfg.MODULES.is_enabled('mail')
) )
except Exception as e: except Exception as e:
app.logger.error(f"Error loading invoice history for item {item_id}: {e}") app.logger.error(f"Error loading invoice/damage history for item {item_id}: {e}")
flash('Fehler beim Laden der Rechnungshistorie.', 'error') flash('Fehler beim Laden der Rechnungs- oder Schadenshistorie.', 'error')
return redirect(url_for('library_loans_admin')) return redirect(url_for('library_loans_admin'))
finally: finally:
if client: if client:
+58 -3
View File
@@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Rechnungen Element - {{ APP_VERSION }}{% endblock %} {% block title %}Rechnungs- & Schadenshistorie - {{ APP_VERSION }}{% endblock %}
{% block content %} {% block content %}
<style> <style>
@@ -39,6 +39,14 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.section-header {
font-size: 1.25rem;
font-weight: 700;
color: #1e293b;
margin: 36px 0 16px 0;
padding-left: 4px;
}
.invoice-card { .invoice-card {
background: var(--ui-surface); background: var(--ui-surface);
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
@@ -115,7 +123,7 @@
<div class="invoice-history-shell"> <div class="invoice-history-shell">
<div class="invoice-history-head"> <div class="invoice-history-head">
<h1>Rechnungshistorie pro Element</h1> <h1>Rechnungs- & Schadenshistorie pro Element</h1>
<div class="head-meta"> <div class="head-meta">
<span><strong>Element:</strong> {{ item.name or '—' }}</span> <span><strong>Element:</strong> {{ item.name or '—' }}</span>
<span><strong>Code:</strong> {{ item.code or '—' }}</span> <span><strong>Code:</strong> {{ item.code or '—' }}</span>
@@ -128,6 +136,8 @@
</div> </div>
</div> </div>
<!-- BEREICH: RECHNUNGEN -->
<h2 class="section-header">Ausgestellte Rechnungen</h2>
<div class="invoice-card"> <div class="invoice-card">
{% if invoices %} {% if invoices %}
<table class="invoice-table"> <table class="invoice-table">
@@ -137,7 +147,7 @@
<th>Betrag</th> <th>Betrag</th>
<th>Ausleihe</th> <th>Ausleihe</th>
<th>Status</th> <th>Status</th>
<th>Schaden</th> <th>Schaden / Grund</th>
<th>Aktion</th> <th>Aktion</th>
</tr> </tr>
</thead> </thead>
@@ -184,5 +194,50 @@
<div class="empty-state">Für dieses Element wurden noch keine Rechnungen gespeichert.</div> <div class="empty-state">Für dieses Element wurden noch keine Rechnungen gespeichert.</div>
{% endif %} {% endif %}
</div> </div>
<!-- BEREICH: SCHÄDEN -->
<h2 class="section-header">Gemeldete Schäden</h2>
<div class="invoice-card">
{% if damages %}
<table class="invoice-table">
<thead>
<tr>
<th>Datum</th>
<th>Quelle</th>
<th>Nutzer / Verursacher</th>
<th>Beschreibung</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for row in damages %}
<tr>
<td>
<div class="muted">{{ row.date }}</div>
</td>
<td>
<div class="mono">{{ row.source }}</div>
{% if row.borrow_id %}
<div class="muted" style="font-size: 0.8rem;">ID: <span title="{{ row.borrow_id }}">{{ row.borrow_id[:6] }}...</span></div>
{% endif %}
</td>
<td>{{ row.user }}</td>
<td>{{ row.description }}</td>
<td>
<!-- Einfache Logik, um bei reparierten Objekten einen grünen Badge zu zeigen -->
{% if row.status|lower in ['repariert', 'erledigt', 'geschlossen', 'bezahlt'] %}
<span class="badge-pill badge-completed">{{ row.status }}</span>
{% else %}
<span class="badge-pill badge-open">{{ row.status }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="empty-state">Für dieses Element wurden bislang keine Schäden erfasst.</div>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}