diff --git a/Web/app.py b/Web/app.py index b4c06c0..6385afd 100755 --- a/Web/app.py +++ b/Web/app.py @@ -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."