feat(library): add damage history view alongside invoices for items
Release Inventarsystem / release-docker (push) Successful in 2m14s
Release Inventarsystem / release-docker (push) Successful in 2m14s
This commit is contained in:
+76
-3
@@ -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:
|
||||||
|
|||||||
@@ -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 %}
|
||||||
Reference in New Issue
Block a user