Implementation for the Damage history for the library items
Release Inventarsystem / release-docker (push) Successful in 2m22s

This commit is contained in:
2026-08-21 17:45:55 +02:00
parent e46ee80cf2
commit 58ea4e5716
+112 -64
View File
@@ -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'):
@@ -3337,7 +3341,6 @@ def library_loans_admin():
return _format_money_value(value)
def safe_decrypt(val):
"""Helper to decrypt strings that start with enc:: or standard encrypted strings."""
if not val or not isinstance(val, str):
return val or ''
try:
@@ -3345,19 +3348,48 @@ def library_loans_admin():
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())
@@ -3370,42 +3402,29 @@ def library_loans_admin():
'Klasse': 1, 'klasse': 1, 'Class': 1, 'school_class': 1}
).sort('Start', -1))
# class_map aufbauen (wie zuvor besprochen)
class_map = {}
student_cards = list(student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'AusweisId': 1}))
for sc in student_cards:
dec_cls = safe_decrypt(sc.get('Klasse'))
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
dec_name = safe_decrypt(sc.get('SchülerName'))
raw_name = sc.get('SchülerName')
ausweis_id = sc.get('AusweisId')
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
class_map[dec_name] = dec_cls
if raw_name:
class_map[raw_name] = dec_cls
if ausweis_id:
class_map[str(ausweis_id).strip().lower()] = dec_cls
users = list(users_col.find({}, {'Username': 1, 'Name': 1, 'User': 1, 'Klasse': 1, 'klasse': 1, 'Class': 1,
'school_class': 1}))
for u in users:
raw_cls = u.get('Klasse') or u.get('klasse') or u.get('Class') or u.get('school_class') or ''
dec_cls = safe_decrypt(raw_cls)
if not dec_cls:
continue
for key in [u.get('Username'), u.get('Name'), u.get('User')]:
if key:
dec_key = safe_decrypt(key)
class_map[key] = dec_cls
class_map[dec_key] = dec_cls
class_map[dec_key.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':
@@ -3417,26 +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 = safe_decrypt(raw_user)
direct_class = (
user_class = (
safe_decrypt(record.get('Klasse'))
or safe_decrypt(record.get('klasse'))
or safe_decrypt(record.get('Class'))
or safe_decrypt(record.get('school_class'))
)
user_class = (
direct_class
or class_map.get(decrypted_user)
or class_map.get(decrypted_user.strip().lower())
or class_map.get(raw_user)
or ''
or class_map.get(raw_user, '')
or class_map.get(decrypted_user, '')
or class_map.get(decrypted_user.strip().lower(), '')
or ''
)
loan_entries.append({
@@ -3447,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')),
@@ -3455,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', ''),
@@ -3480,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(
@@ -9214,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."
@@ -9231,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':
@@ -9285,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}."
@@ -9312,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."