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(): def library_loans_admin():
"""Admin overview for library borrowings and damaged library items.""" """Admin overview for library borrowings and damaged library items."""
if 'username' not in session: 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')) return redirect(url_for('login'))
current_permissions = us.get_effective_permissions(session['username']) current_permissions = us.get_effective_permissions(session['username'])
if not current_permissions['pages'].get('library_loans_admin', False): 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')) return redirect(url_for('library_view'))
if not cfg.MODULES.is_enabled('library'): if not cfg.MODULES.is_enabled('library'):
@@ -3337,7 +3341,6 @@ def library_loans_admin():
return _format_money_value(value) return _format_money_value(value)
def safe_decrypt(val): def safe_decrypt(val):
"""Helper to decrypt strings that start with enc:: or standard encrypted strings."""
if not val or not isinstance(val, str): if not val or not isinstance(val, str):
return val or '' return val or ''
try: try:
@@ -3345,19 +3348,48 @@ def library_loans_admin():
except Exception: except Exception:
return val 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 client = None
try: try:
client = MongoClient(MONGODB_HOST, MONGODB_PORT) client = MongoClient(MONGODB_HOST, MONGODB_PORT)
db = client[MONGODB_DB] db = client[MONGODB_DB]
items_col = db['items'] items_col = db['items']
ausleihungen_col = db['ausleihungen'] ausleihungen_col = db['ausleihungen']
users_col = db['users']
student_cards_col = db['student_cards'] student_cards_col = db['student_cards']
# WICHTIG: 'DamageRepairs': 1 zur Projektion hinzugefügt!
library_items = list(items_col.find( library_items = list(items_col.find(
{'ItemType': {'$in': LIBRARY_ITEM_TYPES}, 'Deleted': {'$ne': True}}, {'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_map = {str(item['_id']): item for item in library_items if item.get('_id')}
item_ids = list(item_map.keys()) item_ids = list(item_map.keys())
@@ -3370,42 +3402,29 @@ def library_loans_admin():
'Klasse': 1, 'klasse': 1, 'Class': 1, 'school_class': 1} 'Klasse': 1, 'klasse': 1, 'Class': 1, 'school_class': 1}
).sort('Start', -1)) ).sort('Start', -1))
# class_map aufbauen (wie zuvor besprochen)
class_map = {} class_map = {}
student_cards = list(student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'AusweisId': 1})) all_cards = list(student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'AusweisId': 1}))
for sc in student_cards: for card in all_cards:
dec_cls = safe_decrypt(sc.get('Klasse')) raw_cls = card.get('Klasse')
dec_cls = safe_decrypt(raw_cls)
if not dec_cls: if not dec_cls:
continue continue
dec_name = safe_decrypt(sc.get('SchülerName')) raw_name = card.get('SchülerName')
raw_name = sc.get('SchülerName') dec_name = safe_decrypt(raw_name)
ausweis_id = sc.get('AusweisId') ausweis_id = card.get('AusweisId')
if dec_name: if dec_name:
class_map[dec_name.strip().lower()] = dec_cls class_map[dec_name.strip().lower()] = dec_cls
class_map[dec_name] = dec_cls
if raw_name: if raw_name:
class_map[raw_name] = dec_cls class_map[raw_name] = dec_cls
if ausweis_id: if ausweis_id:
class_map[str(ausweis_id).strip().lower()] = dec_cls 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() active_item_ids = set()
loan_entries = [] loan_entries = []
for record in active_records: for record in active_records:
item_id = str(record.get('Item') or '') item_id = str(record.get('Item') or '')
if item_id and record.get('Status') == 'active': if item_id and record.get('Status') == 'active':
@@ -3417,26 +3436,24 @@ def library_loans_admin():
invoice_data = record.get('InvoiceData') or {} invoice_data = record.get('InvoiceData') or {}
condition_value = str(item_doc.get('Condition', '')).strip().lower() 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')) raw_reports = item_doc.get('DamageReports', [])
damage_reports = item_doc.get('DamageReports', []) or [] 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', '') raw_user = record.get('User', '')
decrypted_user = safe_decrypt(raw_user) decrypted_user = safe_decrypt(raw_user)
direct_class = ( user_class = (
safe_decrypt(record.get('Klasse')) safe_decrypt(record.get('Klasse'))
or safe_decrypt(record.get('klasse'))
or safe_decrypt(record.get('Class')) or safe_decrypt(record.get('Class'))
or safe_decrypt(record.get('school_class')) or safe_decrypt(record.get('school_class'))
) or class_map.get(raw_user, '')
or class_map.get(decrypted_user, '')
user_class = ( or class_map.get(decrypted_user.strip().lower(), '')
direct_class or '—'
or class_map.get(decrypted_user)
or class_map.get(decrypted_user.strip().lower())
or class_map.get(raw_user)
or ''
) )
loan_entries.append({ loan_entries.append({
@@ -3447,7 +3464,7 @@ def library_loans_admin():
'item_author': item_doc.get('Author', ''), 'item_author': item_doc.get('Author', ''),
'item_isbn': item_doc.get('ISBN', ''), 'item_isbn': item_doc.get('ISBN', ''),
'item_cost_raw': item_doc.get('Anschaffungskosten', ''), 'item_cost_raw': item_doc.get('Anschaffungskosten', ''),
'user': decrypted_user, 'user': decrypted_user if decrypted_user else raw_user,
'klasse': user_class, 'klasse': user_class,
'status': record.get('Status', ''), 'status': record.get('Status', ''),
'start': fmt_dt(record.get('Start')), 'start': fmt_dt(record.get('Start')),
@@ -3455,24 +3472,32 @@ def library_loans_admin():
'period': record.get('Period', ''), 'period': record.get('Period', ''),
'notes': record.get('Notes', ''), 'notes': record.get('Notes', ''),
'invoice_number': invoice_data.get('invoice_number', ''), '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': 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_paid_at': fmt_dt(invoice_data.get('paid_at')) if isinstance(invoice_data.get('paid_at'),
'invoice_corrections_count': len(record.get('InvoiceCorrections', []) or []), datetime.datetime) else '',
'has_damage': item_has_damage, 'has_damage': item_has_damage,
'damage_count': len(damage_reports), 'damage_count': len(raw_reports),
'damage_text': (damage_reports[0].get('description', '') if damage_reports else ''), 'damage_text': fmt_reports[0]['description'] if fmt_reports else '',
'damage_reports': fmt_reports,
'damage_repairs': fmt_repairs
}) })
damaged_items = [] damaged_items = []
for item_doc in library_items: for item_doc in library_items:
item_id = str(item_doc.get('_id') or '') item_id = str(item_doc.get('_id') or '')
condition_value = str(item_doc.get('Condition', '')).strip().lower() 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: if not item_has_damage or item_id in active_item_ids:
continue continue
fmt_reports, fmt_repairs = format_damage_history(raw_reports, raw_repairs)
damaged_items.append({ damaged_items.append({
'id': item_id, 'id': item_id,
'name': item_doc.get('Name', ''), 'name': item_doc.get('Name', ''),
@@ -3480,11 +3505,13 @@ def library_loans_admin():
'author': item_doc.get('Author', ''), 'author': item_doc.get('Author', ''),
'isbn': item_doc.get('ISBN', ''), 'isbn': item_doc.get('ISBN', ''),
'condition': item_doc.get('Condition', ''), '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)), 'available': bool(item_doc.get('Verfuegbar', False)),
'last_updated': fmt_dt(item_doc.get('LastUpdated')), '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( 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 Verarbeitet Items aus der Reparatur und aktualisiert die Serien-Counts
sowie Parent/Child-Abhängigkeiten beim Löschen korrekt. 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): if not ObjectId.is_valid(item_id):
return False, "Ungültige 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." return False, "Item nicht in der Datenbank gefunden."
series_group_id = item.get('SeriesGroupId') series_group_id = item.get('SeriesGroupId')
now = datetime.datetime.now()
damage_unset = { legacy_damage_unset = {
'HasDamage': "",
'has_damage': "", 'has_damage': "",
'DamageCount': "", 'DamageCount': "",
'damage_count': "", 'damage_count': "",
'DamageText': "", 'DamageText': "",
'damage_text': "", 'damage_text': "",
'is_damaged': "", 'is_damaged': "",
'IsDamaged': "", 'IsDamaged': ""
}
borrowings_unset = legacy_damage_unset.copy()
borrowings_unset.update({
'HasDamage': "",
'Condition': "", 'Condition': "",
'DamageReports': "" 'DamageReports': ""
} })
# --- OPTION 1: DELETE --- # --- OPTION 1: DELETE ---
if action == '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." 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 --- # --- OPTION 2: REPLACE ---
elif action == 'replace': if action == 'replace':
items.update_one( items.update_one(
{'_id': ObjectId(item_id)}, {'_id': ObjectId(item_id)},
{ {
'$push': {'DamageRepairs': repair_entry},
'$set': { '$set': {
'Code_4': str(new_code_4).strip(), 'Code_4': str(new_code_4).strip(),
'Verfuegbar': True, '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: if borrowings is not None:
borrowings.update_many( borrowings.update_many(
{'$or': [{'item_id': str(item_id)}, {'item_id': ObjectId(item_id)}]}, {'$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}." 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( items.update_one(
{'_id': ObjectId(item_id)}, {'_id': ObjectId(item_id)},
{ {
'$push': {'DamageRepairs': repair_entry},
'$set': { '$set': {
'Verfuegbar': True, '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: if borrowings is not None:
borrowings.update_many( borrowings.update_many(
{'$or': [{'item_id': str(item_id)}, {'item_id': ObjectId(item_id)}]}, {'$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." return True, "Item wurde repariert und ist wieder regulär verfügbar."