|
|
|
@@ -3312,13 +3312,17 @@ def library_view():
|
|
|
|
|
def library_loans_admin():
|
|
|
|
|
"""Admin overview for library borrowings and damaged library items."""
|
|
|
|
|
if 'username' not in session:
|
|
|
|
|
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!', 'error')
|
|
|
|
|
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!',
|
|
|
|
|
'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
current_permissions = us.get_effective_permissions(session['username'])
|
|
|
|
|
|
|
|
|
|
if not current_permissions['pages'].get('library_loans_admin', False):
|
|
|
|
|
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!', 'error')
|
|
|
|
|
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!',
|
|
|
|
|
'error')
|
|
|
|
|
return redirect(url_for('library_view'))
|
|
|
|
|
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
@@ -3336,18 +3340,56 @@ def library_loans_admin():
|
|
|
|
|
def fmt_money(value):
|
|
|
|
|
return _format_money_value(value)
|
|
|
|
|
|
|
|
|
|
def safe_decrypt(val):
|
|
|
|
|
if not val or not isinstance(val, str):
|
|
|
|
|
return val or ''
|
|
|
|
|
try:
|
|
|
|
|
return decrypt_text(val)
|
|
|
|
|
except Exception:
|
|
|
|
|
return val
|
|
|
|
|
|
|
|
|
|
# Hilfsfunktion zur Formatierung der Schadenshistorie
|
|
|
|
|
def format_damage_history(reports, repairs):
|
|
|
|
|
fmt_reports = []
|
|
|
|
|
for r in (reports or []):
|
|
|
|
|
fmt_reports.append({
|
|
|
|
|
'description': r.get('description', ''),
|
|
|
|
|
'reported_by': r.get('reported_by', ''),
|
|
|
|
|
'reported_at': fmt_dt(r.get('reported_at'))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
fmt_repairs = []
|
|
|
|
|
for rep in (repairs or []):
|
|
|
|
|
res_reports = []
|
|
|
|
|
for res in rep.get('resolved_reports', []):
|
|
|
|
|
res_reports.append({
|
|
|
|
|
'description': res.get('description', ''),
|
|
|
|
|
'reported_by': res.get('reported_by', ''),
|
|
|
|
|
'reported_at': fmt_dt(res.get('reported_at'))
|
|
|
|
|
})
|
|
|
|
|
fmt_repairs.append({
|
|
|
|
|
'repaired_by': rep.get('repaired_by', ''),
|
|
|
|
|
'repaired_at': fmt_dt(rep.get('repaired_at')),
|
|
|
|
|
'resolved_reports': res_reports
|
|
|
|
|
})
|
|
|
|
|
return fmt_reports, fmt_repairs
|
|
|
|
|
|
|
|
|
|
client = None
|
|
|
|
|
try:
|
|
|
|
|
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
|
|
|
|
db = client[MONGODB_DB]
|
|
|
|
|
items_col = db['items']
|
|
|
|
|
ausleihungen_col = db['ausleihungen']
|
|
|
|
|
users_col = db['users']
|
|
|
|
|
student_cards_col = db['student_cards']
|
|
|
|
|
|
|
|
|
|
# WICHTIG: 'DamageRepairs': 1 zur Projektion hinzugefügt!
|
|
|
|
|
library_items = list(items_col.find(
|
|
|
|
|
{'ItemType': {'$in': LIBRARY_ITEM_TYPES}, 'Deleted': {'$ne': True}},
|
|
|
|
|
{'Name': 1, 'Code_4': 1, 'Anschaffungskosten': 1, 'Condition': 1, 'HasDamage': 1, 'DamageReports': 1,
|
|
|
|
|
'Verfuegbar': 1, 'User': 1, 'ItemType': 1, 'Author': 1, 'ISBN': 1}
|
|
|
|
|
{
|
|
|
|
|
'Name': 1, 'Code_4': 1, 'Anschaffungskosten': 1, 'Condition': 1,
|
|
|
|
|
'HasDamage': 1, 'DamageReports': 1, 'DamageRepairs': 1,
|
|
|
|
|
'Verfuegbar': 1, 'User': 1, 'ItemType': 1, 'Author': 1, 'ISBN': 1
|
|
|
|
|
}
|
|
|
|
|
))
|
|
|
|
|
item_map = {str(item['_id']): item for item in library_items if item.get('_id')}
|
|
|
|
|
item_ids = list(item_map.keys())
|
|
|
|
@@ -3357,40 +3399,32 @@ def library_loans_admin():
|
|
|
|
|
active_records = list(ausleihungen_col.find(
|
|
|
|
|
{'Item': {'$in': item_ids}, 'Status': {'$in': ['active', 'planned', 'completed']}},
|
|
|
|
|
{'User': 1, 'Item': 1, 'Status': 1, 'Start': 1, 'End': 1, 'Period': 1, 'Notes': 1, 'InvoiceData': 1,
|
|
|
|
|
'Klasse': 1, 'Class': 1, 'school_class': 1}
|
|
|
|
|
'Klasse': 1, 'klasse': 1, 'Class': 1, 'school_class': 1}
|
|
|
|
|
).sort('Start', -1))
|
|
|
|
|
|
|
|
|
|
user_map = {}
|
|
|
|
|
if active_records:
|
|
|
|
|
raw_users = list({r.get('User') for r in active_records if r.get('User')})
|
|
|
|
|
decrypted_users = []
|
|
|
|
|
for ru in raw_users:
|
|
|
|
|
try:
|
|
|
|
|
du = decrypt_text(ru) if ru else ''
|
|
|
|
|
if du:
|
|
|
|
|
decrypted_users.append(du)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
# class_map aufbauen (wie zuvor besprochen)
|
|
|
|
|
class_map = {}
|
|
|
|
|
all_cards = list(student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'AusweisId': 1}))
|
|
|
|
|
for card in all_cards:
|
|
|
|
|
raw_cls = card.get('Klasse')
|
|
|
|
|
dec_cls = safe_decrypt(raw_cls)
|
|
|
|
|
if not dec_cls:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
query_identifiers = list(set(raw_users + decrypted_users))
|
|
|
|
|
if query_identifiers:
|
|
|
|
|
user_docs = list(users_col.find(
|
|
|
|
|
{'$or': [
|
|
|
|
|
{'Username': {'$in': query_identifiers}},
|
|
|
|
|
{'Name': {'$in': query_identifiers}},
|
|
|
|
|
{'User': {'$in': query_identifiers}}
|
|
|
|
|
]},
|
|
|
|
|
{'Username': 1, 'Name': 1, 'User': 1, 'Klasse': 1, 'Class': 1, 'school_class': 1}
|
|
|
|
|
))
|
|
|
|
|
for u in user_docs:
|
|
|
|
|
cls = u.get('Klasse') or u.get('Class') or u.get('school_class') or ''
|
|
|
|
|
if cls:
|
|
|
|
|
if u.get('Username'): user_map[u['Username']] = cls
|
|
|
|
|
if u.get('Name'): user_map[u['Name']] = cls
|
|
|
|
|
if u.get('User'): user_map[u['User']] = cls
|
|
|
|
|
raw_name = card.get('SchülerName')
|
|
|
|
|
dec_name = safe_decrypt(raw_name)
|
|
|
|
|
ausweis_id = card.get('AusweisId')
|
|
|
|
|
|
|
|
|
|
if dec_name:
|
|
|
|
|
class_map[dec_name.strip().lower()] = dec_cls
|
|
|
|
|
if raw_name:
|
|
|
|
|
class_map[raw_name] = dec_cls
|
|
|
|
|
if ausweis_id:
|
|
|
|
|
class_map[str(ausweis_id).strip().lower()] = dec_cls
|
|
|
|
|
|
|
|
|
|
active_item_ids = set()
|
|
|
|
|
loan_entries = []
|
|
|
|
|
|
|
|
|
|
for record in active_records:
|
|
|
|
|
item_id = str(record.get('Item') or '')
|
|
|
|
|
if item_id and record.get('Status') == 'active':
|
|
|
|
@@ -3402,20 +3436,24 @@ def library_loans_admin():
|
|
|
|
|
|
|
|
|
|
invoice_data = record.get('InvoiceData') or {}
|
|
|
|
|
condition_value = str(item_doc.get('Condition', '')).strip().lower()
|
|
|
|
|
item_has_damage = bool(item_doc.get('HasDamage')) or condition_value == 'destroyed' or bool(
|
|
|
|
|
item_doc.get('DamageReports'))
|
|
|
|
|
damage_reports = item_doc.get('DamageReports', []) or []
|
|
|
|
|
|
|
|
|
|
raw_reports = item_doc.get('DamageReports', [])
|
|
|
|
|
raw_repairs = item_doc.get('DamageRepairs', [])
|
|
|
|
|
item_has_damage = bool(item_doc.get('HasDamage')) or condition_value == 'destroyed' or bool(raw_reports)
|
|
|
|
|
|
|
|
|
|
fmt_reports, fmt_repairs = format_damage_history(raw_reports, raw_repairs)
|
|
|
|
|
|
|
|
|
|
raw_user = record.get('User', '')
|
|
|
|
|
decrypted_user = decrypt_text(raw_user) if raw_user else ''
|
|
|
|
|
decrypted_user = safe_decrypt(raw_user)
|
|
|
|
|
|
|
|
|
|
user_class = (
|
|
|
|
|
record.get('Klasse')
|
|
|
|
|
or record.get('Class')
|
|
|
|
|
or record.get('school_class')
|
|
|
|
|
or user_map.get(raw_user, '')
|
|
|
|
|
or user_map.get(decrypted_user, '')
|
|
|
|
|
or ''
|
|
|
|
|
safe_decrypt(record.get('Klasse'))
|
|
|
|
|
or safe_decrypt(record.get('Class'))
|
|
|
|
|
or safe_decrypt(record.get('school_class'))
|
|
|
|
|
or class_map.get(raw_user, '')
|
|
|
|
|
or class_map.get(decrypted_user, '')
|
|
|
|
|
or class_map.get(decrypted_user.strip().lower(), '')
|
|
|
|
|
or '—'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
loan_entries.append({
|
|
|
|
@@ -3426,7 +3464,7 @@ def library_loans_admin():
|
|
|
|
|
'item_author': item_doc.get('Author', ''),
|
|
|
|
|
'item_isbn': item_doc.get('ISBN', ''),
|
|
|
|
|
'item_cost_raw': item_doc.get('Anschaffungskosten', ''),
|
|
|
|
|
'user': decrypted_user,
|
|
|
|
|
'user': decrypted_user if decrypted_user else raw_user,
|
|
|
|
|
'klasse': user_class,
|
|
|
|
|
'status': record.get('Status', ''),
|
|
|
|
|
'start': fmt_dt(record.get('Start')),
|
|
|
|
@@ -3434,24 +3472,32 @@ def library_loans_admin():
|
|
|
|
|
'period': record.get('Period', ''),
|
|
|
|
|
'notes': record.get('Notes', ''),
|
|
|
|
|
'invoice_number': invoice_data.get('invoice_number', ''),
|
|
|
|
|
'invoice_amount': fmt_money(invoice_data.get('amount')) if invoice_data.get('amount') is not None else fmt_money(item_doc.get('Anschaffungskosten')),
|
|
|
|
|
'invoice_amount': fmt_money(invoice_data.get('amount')) if invoice_data.get(
|
|
|
|
|
'amount') is not None else fmt_money(item_doc.get('Anschaffungskosten')),
|
|
|
|
|
'invoice_paid': bool(invoice_data.get('paid', False)),
|
|
|
|
|
'invoice_paid_at': fmt_dt(invoice_data.get('paid_at')) if isinstance(invoice_data.get('paid_at'), datetime.datetime) else '',
|
|
|
|
|
'invoice_corrections_count': len(record.get('InvoiceCorrections', []) or []),
|
|
|
|
|
'invoice_paid_at': fmt_dt(invoice_data.get('paid_at')) if isinstance(invoice_data.get('paid_at'),
|
|
|
|
|
datetime.datetime) else '',
|
|
|
|
|
'has_damage': item_has_damage,
|
|
|
|
|
'damage_count': len(damage_reports),
|
|
|
|
|
'damage_text': (damage_reports[0].get('description', '') if damage_reports else ''),
|
|
|
|
|
'damage_count': len(raw_reports),
|
|
|
|
|
'damage_text': fmt_reports[0]['description'] if fmt_reports else '',
|
|
|
|
|
'damage_reports': fmt_reports,
|
|
|
|
|
'damage_repairs': fmt_repairs
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
damaged_items = []
|
|
|
|
|
for item_doc in library_items:
|
|
|
|
|
item_id = str(item_doc.get('_id') or '')
|
|
|
|
|
condition_value = str(item_doc.get('Condition', '')).strip().lower()
|
|
|
|
|
damage_reports = item_doc.get('DamageReports', []) or []
|
|
|
|
|
item_has_damage = bool(item_doc.get('HasDamage')) or condition_value == 'destroyed' or bool(damage_reports)
|
|
|
|
|
|
|
|
|
|
raw_reports = item_doc.get('DamageReports', [])
|
|
|
|
|
raw_repairs = item_doc.get('DamageRepairs', [])
|
|
|
|
|
item_has_damage = bool(item_doc.get('HasDamage')) or condition_value == 'destroyed' or bool(raw_reports)
|
|
|
|
|
|
|
|
|
|
if not item_has_damage or item_id in active_item_ids:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
fmt_reports, fmt_repairs = format_damage_history(raw_reports, raw_repairs)
|
|
|
|
|
|
|
|
|
|
damaged_items.append({
|
|
|
|
|
'id': item_id,
|
|
|
|
|
'name': item_doc.get('Name', ''),
|
|
|
|
@@ -3459,11 +3505,13 @@ def library_loans_admin():
|
|
|
|
|
'author': item_doc.get('Author', ''),
|
|
|
|
|
'isbn': item_doc.get('ISBN', ''),
|
|
|
|
|
'condition': item_doc.get('Condition', ''),
|
|
|
|
|
'damage_count': len(damage_reports),
|
|
|
|
|
'damage_text': (damage_reports[0].get('description', '') if damage_reports else ''),
|
|
|
|
|
'available': bool(item_doc.get('Verfuegbar', False)),
|
|
|
|
|
'last_updated': fmt_dt(item_doc.get('LastUpdated')),
|
|
|
|
|
'acquisition_costs': item_doc.get('Anschaffungskosten', "")
|
|
|
|
|
'acquisition_costs': item_doc.get('Anschaffungskosten', ""),
|
|
|
|
|
'damage_count': len(raw_reports),
|
|
|
|
|
'damage_text': fmt_reports[0]['description'] if fmt_reports else '',
|
|
|
|
|
'damage_reports': fmt_reports,
|
|
|
|
|
'damage_repairs': fmt_repairs
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
@@ -9193,7 +9241,7 @@ def resolve_repaired_item_funct(item_id, action, new_code_4="", current_user="ad
|
|
|
|
|
"""
|
|
|
|
|
Verarbeitet Items aus der Reparatur und aktualisiert die Serien-Counts
|
|
|
|
|
sowie Parent/Child-Abhängigkeiten beim Löschen korrekt.
|
|
|
|
|
Entfernt Schadens-Flags komplett aus Items und zugehörigen Ausleihen.
|
|
|
|
|
Verschiebt gemeldete Schäden in die Historie (DamageRepairs) bei Reparatur/Austausch.
|
|
|
|
|
"""
|
|
|
|
|
if not ObjectId.is_valid(item_id):
|
|
|
|
|
return False, "Ungültige Item-ID."
|
|
|
|
@@ -9210,19 +9258,24 @@ def resolve_repaired_item_funct(item_id, action, new_code_4="", current_user="ad
|
|
|
|
|
return False, "Item nicht in der Datenbank gefunden."
|
|
|
|
|
|
|
|
|
|
series_group_id = item.get('SeriesGroupId')
|
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
|
|
|
|
|
|
damage_unset = {
|
|
|
|
|
'HasDamage': "",
|
|
|
|
|
legacy_damage_unset = {
|
|
|
|
|
'has_damage': "",
|
|
|
|
|
'DamageCount': "",
|
|
|
|
|
'damage_count': "",
|
|
|
|
|
'DamageText': "",
|
|
|
|
|
'damage_text': "",
|
|
|
|
|
'is_damaged': "",
|
|
|
|
|
'IsDamaged': "",
|
|
|
|
|
'IsDamaged': ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
borrowings_unset = legacy_damage_unset.copy()
|
|
|
|
|
borrowings_unset.update({
|
|
|
|
|
'HasDamage': "",
|
|
|
|
|
'Condition': "",
|
|
|
|
|
'DamageReports': ""
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# --- OPTION 1: DELETE ---
|
|
|
|
|
if action == 'delete':
|
|
|
|
@@ -9264,24 +9317,36 @@ def resolve_repaired_item_funct(item_id, action, new_code_4="", current_user="ad
|
|
|
|
|
|
|
|
|
|
return True, "Item wurde gelöscht und der Bestand aller Serien-Items angepasst."
|
|
|
|
|
|
|
|
|
|
current_reports = item.get('DamageReports', [])
|
|
|
|
|
repair_entry = {
|
|
|
|
|
'repaired_by': current_user,
|
|
|
|
|
'repaired_at': now,
|
|
|
|
|
'action_taken': action,
|
|
|
|
|
'resolved_reports': current_reports
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- OPTION 2: REPLACE ---
|
|
|
|
|
elif action == 'replace':
|
|
|
|
|
if action == 'replace':
|
|
|
|
|
items.update_one(
|
|
|
|
|
{'_id': ObjectId(item_id)},
|
|
|
|
|
{
|
|
|
|
|
'$push': {'DamageRepairs': repair_entry},
|
|
|
|
|
'$set': {
|
|
|
|
|
'Code_4': str(new_code_4).strip(),
|
|
|
|
|
'Verfuegbar': True,
|
|
|
|
|
'LastUpdated': datetime.datetime.now()
|
|
|
|
|
'HasDamage': False,
|
|
|
|
|
'Condition': 'gut',
|
|
|
|
|
'DamageReports': [],
|
|
|
|
|
'LastUpdated': now
|
|
|
|
|
},
|
|
|
|
|
'$unset': damage_unset
|
|
|
|
|
'$unset': legacy_damage_unset
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if borrowings is not None:
|
|
|
|
|
borrowings.update_many(
|
|
|
|
|
{'$or': [{'item_id': str(item_id)}, {'item_id': ObjectId(item_id)}]},
|
|
|
|
|
{'$unset': damage_unset}
|
|
|
|
|
{'$unset': borrowings_unset}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return True, f"Item wurde ersetzt. Neuer Code 4 zugewiesen: {new_code_4}."
|
|
|
|
@@ -9291,18 +9356,22 @@ def resolve_repaired_item_funct(item_id, action, new_code_4="", current_user="ad
|
|
|
|
|
items.update_one(
|
|
|
|
|
{'_id': ObjectId(item_id)},
|
|
|
|
|
{
|
|
|
|
|
'$push': {'DamageRepairs': repair_entry},
|
|
|
|
|
'$set': {
|
|
|
|
|
'Verfuegbar': True,
|
|
|
|
|
'LastUpdated': datetime.datetime.now()
|
|
|
|
|
'HasDamage': False,
|
|
|
|
|
'Condition': 'gut',
|
|
|
|
|
'DamageReports': [],
|
|
|
|
|
'LastUpdated': now
|
|
|
|
|
},
|
|
|
|
|
'$unset': damage_unset
|
|
|
|
|
'$unset': legacy_damage_unset
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if borrowings is not None:
|
|
|
|
|
borrowings.update_many(
|
|
|
|
|
{'$or': [{'item_id': str(item_id)}, {'item_id': ObjectId(item_id)}]},
|
|
|
|
|
{'$unset': damage_unset}
|
|
|
|
|
{'$unset': borrowings_unset}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return True, "Item wurde repariert und ist wieder regulär verfügbar."
|
|
|
|
@@ -9314,9 +9383,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}")
|
|
|
|
|
return False, "Ein Datenbankfehler ist aufgetreten."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/admin/library/items/<item_id>/invoices', methods=['GET'])
|
|
|
|
|
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:
|
|
|
|
|
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!',
|
|
|
|
@@ -9352,6 +9422,9 @@ def library_item_invoices(item_id):
|
|
|
|
|
item_id_obj = item_doc.get('_id')
|
|
|
|
|
item_id_str = str(item_id_obj)
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
# 1. RECHNUNGSHISTORIE (Aus Ausleihen)
|
|
|
|
|
# ==========================================
|
|
|
|
|
borrow_docs = list(ausleihungen.find(
|
|
|
|
|
{
|
|
|
|
|
'Item': {'$in': [item_id_str, item_id_obj]},
|
|
|
|
@@ -9382,7 +9455,9 @@ def library_item_invoices(item_id):
|
|
|
|
|
entries.append({
|
|
|
|
|
'borrow_id': str(borrow_doc.get('_id')),
|
|
|
|
|
'borrow_status': borrow_doc.get('Status', ''),
|
|
|
|
|
'borrow_user': decrypt_text(borrow_doc.get('User', '')),
|
|
|
|
|
'borrow_user': decrypt_text(borrow_doc.get('User', '')) if hasattr(borrow_doc,
|
|
|
|
|
'get') and borrow_doc.get(
|
|
|
|
|
'User') else '',
|
|
|
|
|
'borrow_start': borrow_doc.get('Start').strftime('%d.%m.%Y %H:%M') if isinstance(
|
|
|
|
|
borrow_doc.get('Start'), datetime.datetime) else '',
|
|
|
|
|
'borrow_end': borrow_doc.get('End').strftime('%d.%m.%Y %H:%M') if isinstance(borrow_doc.get('End'),
|
|
|
|
@@ -9398,6 +9473,121 @@ def library_item_invoices(item_id):
|
|
|
|
|
'invoice_paid_by': invoice_data.get('paid_by', ''),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
# 2. SCHADENSHISTORIE (Neues Format + Fallback)
|
|
|
|
|
# ==========================================
|
|
|
|
|
raw_damages = []
|
|
|
|
|
|
|
|
|
|
# A: Aktive / gemeldete Schäden aus 'DamageReports'
|
|
|
|
|
damage_reports = item_doc.get('DamageReports', [])
|
|
|
|
|
if not isinstance(damage_reports, list):
|
|
|
|
|
damage_reports = []
|
|
|
|
|
|
|
|
|
|
for d in damage_reports:
|
|
|
|
|
if not isinstance(d, dict): continue
|
|
|
|
|
|
|
|
|
|
# Rechnungs-Metadaten anhängen, falls vorhanden
|
|
|
|
|
extra_info = ""
|
|
|
|
|
if d.get('invoice_number'):
|
|
|
|
|
amount = d.get('invoice_amount', '')
|
|
|
|
|
extra_info = f"\n(Mit Rechnung: {d['invoice_number']} - {amount}€)"
|
|
|
|
|
|
|
|
|
|
raw_damages.append({
|
|
|
|
|
'raw_date': d.get('reported_at'),
|
|
|
|
|
'source': str(d.get('source', 'Meldung')).capitalize(),
|
|
|
|
|
'user': d.get('reported_by', 'System/Admin'),
|
|
|
|
|
'description': d.get('description', 'Keine Beschreibung') + extra_info,
|
|
|
|
|
'status': 'Offen',
|
|
|
|
|
'borrow_id': None
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# B: Behobene Schäden aus 'DamageRepairs'
|
|
|
|
|
damage_repairs = item_doc.get('DamageRepairs', [])
|
|
|
|
|
if not isinstance(damage_repairs, list):
|
|
|
|
|
damage_repairs = []
|
|
|
|
|
|
|
|
|
|
for r in damage_repairs:
|
|
|
|
|
if not isinstance(r, dict): continue
|
|
|
|
|
resolved_list = r.get('resolved_reports', [])
|
|
|
|
|
repaired_at = r.get('repaired_at')
|
|
|
|
|
repaired_by = r.get('repaired_by', 'Admin')
|
|
|
|
|
action_taken = r.get('action_taken', 'repair')
|
|
|
|
|
|
|
|
|
|
# Status anhand der Aktion bestimmen
|
|
|
|
|
status_text = 'Repariert' if action_taken == 'repair' else 'Erledigt'
|
|
|
|
|
|
|
|
|
|
if resolved_list and isinstance(resolved_list, list):
|
|
|
|
|
for res in resolved_list:
|
|
|
|
|
# Rechnungs-Metadaten aus dem ehemals offenen Report
|
|
|
|
|
extra_info = ""
|
|
|
|
|
if res.get('invoice_number'):
|
|
|
|
|
amount = res.get('invoice_amount', '')
|
|
|
|
|
extra_info = f"\n(Mit Rechnung: {res['invoice_number']} - {amount}€)"
|
|
|
|
|
|
|
|
|
|
raw_damages.append({
|
|
|
|
|
'raw_date': res.get('reported_at') or repaired_at,
|
|
|
|
|
'source': f"{status_text} von {repaired_by}",
|
|
|
|
|
'user': res.get('reported_by', 'System/Admin'),
|
|
|
|
|
'description': res.get('description', 'Keine Beschreibung') + extra_info,
|
|
|
|
|
'status': status_text,
|
|
|
|
|
'borrow_id': None
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
# Fallback, falls keine genauen Reports hinterlegt sind
|
|
|
|
|
raw_damages.append({
|
|
|
|
|
'raw_date': repaired_at,
|
|
|
|
|
'source': 'Reparatur',
|
|
|
|
|
'user': repaired_by,
|
|
|
|
|
'description': 'Schaden behoben',
|
|
|
|
|
'status': status_text,
|
|
|
|
|
'borrow_id': None
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# C: Legacy-Fallback: Schäden, die evtl. nur in alten 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': ''}}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
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': "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(
|
|
|
|
|
'library_item_invoices.html',
|
|
|
|
|
item={
|
|
|
|
@@ -9408,14 +9598,15 @@ def library_item_invoices(item_id):
|
|
|
|
|
'isbn': item_doc.get('ISBN', ''),
|
|
|
|
|
},
|
|
|
|
|
invoices=entries,
|
|
|
|
|
damages=damage_entries,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
mail_module_enabled=cfg.MODULES.is_enabled('mail')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
app.logger.error(f"Error loading invoice history for item {item_id}: {e}")
|
|
|
|
|
flash('Fehler beim Laden der Rechnungshistorie.', 'error')
|
|
|
|
|
app.logger.error(f"Error loading invoice/damage history for item {item_id}: {e}")
|
|
|
|
|
flash('Fehler beim Laden der Rechnungs- oder Schadenshistorie.', 'error')
|
|
|
|
|
return redirect(url_for('library_loans_admin'))
|
|
|
|
|
finally:
|
|
|
|
|
if client:
|
|
|
|
|