|
|
|
@@ -388,7 +388,7 @@ ALLOWED_COVER_DOMAINS = {
|
|
|
|
|
"www.googleapis.com"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SENSITIVE_AUDIT_FIELDS = ["email", "username", "full_name", "phone"]
|
|
|
|
|
SENSITIVE_AUDIT_FIELDS = ["email", "username", "full_name", "phone", "borrower", "ip"]
|
|
|
|
|
|
|
|
|
|
# Apply the configuration for general use throughout the app
|
|
|
|
|
APP_VERSION = __version__
|
|
|
|
@@ -3604,8 +3604,11 @@ def api_item_detail(item_id):
|
|
|
|
|
# Basic detail HTML
|
|
|
|
|
detail_html = f"""
|
|
|
|
|
<h2>{html.escape(item.get('Name', 'Untitled'))}</h2>
|
|
|
|
|
<p><strong>Autor/Künstler:</strong> {html.escape(item.get('Autor', item.get('Author', '-')))}</p>
|
|
|
|
|
<p><strong>ISBN:</strong> {html.escape(item.get('ISBN', item.get('Code4', '-')))}</p>
|
|
|
|
|
<p><strong>Anzahl:</strong> {html.escape(item.get('SeriesCount', '-'))}</p>
|
|
|
|
|
<p><strong>Ort:</strong> {html.escape(item.get('Ort', '-'))}</p>
|
|
|
|
|
<p><strong>Typ:</strong> {html.escape(item.get('ItemType', '-'))}</p>
|
|
|
|
|
<p><strong>Kategorie:</strong> {html.escape(item.get('library_category', '-'))}</p>
|
|
|
|
|
<p><strong>Beschreibung:</strong> {html.escape(item.get('Beschreibung', '-'))}</p>
|
|
|
|
|
<p><strong>Status:</strong> {html.escape(status_label)}</p>
|
|
|
|
|
{f'<p><strong>Ausgeliehen von:</strong> {html.escape(str(borrower_value))}</p>' if borrower_value and status_label == 'Ausgeliehen' else ''}
|
|
|
|
@@ -5082,7 +5085,8 @@ def debug_favorites():
|
|
|
|
|
try:
|
|
|
|
|
db_favs = us.get_favorites(username)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return jsonify({'ok': False, 'error': f'db_error: {e}', 'session': session_favs})
|
|
|
|
|
app.logger.error(f"Error fetching DB favorites: {e}")
|
|
|
|
|
return jsonify({'ok': False, 'error': 'Failed to fetch DB favorites', 'session': session_favs})
|
|
|
|
|
merged = sorted(set(session_favs) | set(db_favs))
|
|
|
|
|
return jsonify({'ok': True, 'user': username, 'session': session_favs, 'db': db_favs, 'merged': merged})
|
|
|
|
|
|
|
|
|
@@ -5143,7 +5147,9 @@ def upload_item():
|
|
|
|
|
individual_codes_raw = sanitize_form_value(request.form.get('individual_codes', ''))
|
|
|
|
|
item_count_raw = sanitize_form_value(request.form.get('item_count', '1'))
|
|
|
|
|
item_type_input = sanitize_form_value(request.form.get('item_type_input', ''))
|
|
|
|
|
|
|
|
|
|
library_category = sanitize_form_value(request.form.get('library_category', ''))
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"Upload attempt by {encrypt_text(username)}: name={name!r}, ort={ort!r}, beschreibung length={len(beschreibung)}, images count={len(images)}, filters={filter_upload}, filters2={filter_upload2}, filters3={filter_upload3}, anschaffungs_jahr={anschaffungs_jahr}, anschaffungs_kosten={anschaffungs_kosten}, code_4={code_4}, isbn={isbn_raw!r}, upload_mode={upload_mode!r}, item_count={item_count_raw!r}, item_type_input={item_type_input!r}, individual_codes={individual_codes_raw!r}")
|
|
|
|
|
try:
|
|
|
|
|
item_count = int(item_count_raw) if item_count_raw else 1
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
@@ -5155,6 +5161,8 @@ def upload_item():
|
|
|
|
|
if individual_codes_raw:
|
|
|
|
|
individual_codes = [c.strip() for c in str(individual_codes_raw).replace(',', '\n').splitlines() if c.strip()]
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"DEBUG: Parsed individual codes: {individual_codes}, count: {len(individual_codes)}")
|
|
|
|
|
|
|
|
|
|
# Check if this is a duplication
|
|
|
|
|
is_duplicating = request.form.get('is_duplicating') == 'true'
|
|
|
|
|
|
|
|
|
@@ -5270,6 +5278,8 @@ def upload_item():
|
|
|
|
|
for c in extra_codes:
|
|
|
|
|
if c not in all_item_codes:
|
|
|
|
|
all_item_codes.append(c)
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"DEBUG: Unified item codes: {all_item_codes}, total count: {len(all_item_codes)}")
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
|
|
|
# 2. OVERRIDE ITEM COUNT
|
|
|
|
@@ -5301,7 +5311,7 @@ def upload_item():
|
|
|
|
|
# Validate every code in our master list against the database
|
|
|
|
|
for code in all_item_codes:
|
|
|
|
|
if not it.is_code_unique(code):
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"DEBUG: Code '{code}' is not unique.")
|
|
|
|
|
# Special case: If they only uploaded ONE item, redirect them to that existing item
|
|
|
|
|
if item_count == 1:
|
|
|
|
|
existing_item = it._get_items_collection().find_one({"code_4": code})
|
|
|
|
@@ -5334,13 +5344,13 @@ def upload_item():
|
|
|
|
|
if not base_code:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
candidate = base_code if position == 1 else f"{base_code}-{position}"
|
|
|
|
|
candidate = base_code if position == 1 else f"{base_code}"
|
|
|
|
|
if it.is_code_unique(candidate):
|
|
|
|
|
return candidate
|
|
|
|
|
|
|
|
|
|
suffix = 1
|
|
|
|
|
while suffix <= 1000:
|
|
|
|
|
alternative = f"{candidate}-{suffix}"
|
|
|
|
|
alternative = f"{candidate}"
|
|
|
|
|
if it.is_code_unique(alternative):
|
|
|
|
|
return alternative
|
|
|
|
|
suffix += 1
|
|
|
|
@@ -6034,7 +6044,8 @@ def upload_item():
|
|
|
|
|
is_grouped_sub_item=(position > 1),
|
|
|
|
|
parent_item_id=parent_item_id,
|
|
|
|
|
isbn=item_isbn,
|
|
|
|
|
item_type=item_type
|
|
|
|
|
item_type=item_type,
|
|
|
|
|
library_category=library_category
|
|
|
|
|
)
|
|
|
|
|
if not item_id:
|
|
|
|
|
break
|
|
|
|
@@ -7507,8 +7518,9 @@ def plan_booking():
|
|
|
|
|
if booking_type == 'single':
|
|
|
|
|
end_date = start_date
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return {"success": False, "error": f"Invalid date format: {e}"}, 400
|
|
|
|
|
|
|
|
|
|
app.logger.error(f"Invalid date format: {e}")
|
|
|
|
|
return {"success": False, "error": "Invalid date format"}, 400
|
|
|
|
|
|
|
|
|
|
# Check if item exists
|
|
|
|
|
item = it.get_item(item_id)
|
|
|
|
|
if not item:
|
|
|
|
@@ -7734,9 +7746,7 @@ def terminplan():
|
|
|
|
|
|
|
|
|
|
return render_template('terminplan.html', school_periods=SCHOOL_PERIODS)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
import traceback
|
|
|
|
|
print(f"Error rendering terminplan: {e}")
|
|
|
|
|
|
|
|
|
|
app.logger.error(f"Error rendering terminplan: {e}")
|
|
|
|
|
flash('Ein Fehler ist beim Anzeigen des Kalenders aufgetreten.', 'error')
|
|
|
|
|
return redirect(url_for('home'))
|
|
|
|
|
|
|
|
|
@@ -8030,7 +8040,7 @@ def admin_verify_audit_chain():
|
|
|
|
|
status_code = 200 if result.get('ok') else 409
|
|
|
|
|
return jsonify(result), status_code
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
return jsonify({'ok': False, 'error': str(exc)}), 500
|
|
|
|
|
return jsonify({'ok': False, 'error': 'Internal server error'}), 500
|
|
|
|
|
finally:
|
|
|
|
|
if client:
|
|
|
|
|
client.close()
|
|
|
|
@@ -8049,7 +8059,7 @@ def admin_audit_dashboard():
|
|
|
|
|
al.ensure_audit_indexes(db)
|
|
|
|
|
verify_result = al.verify_audit_chain(db)
|
|
|
|
|
|
|
|
|
|
audit_rows = list(db['audit_log'].find(...).sort('chain_index', -1).limit(200))
|
|
|
|
|
audit_rows = list(db['audit_log'].find({}).sort('chain_index', -1).limit(200))
|
|
|
|
|
|
|
|
|
|
# DEC_START: Decrypt the sensitive fields for display
|
|
|
|
|
for row in audit_rows:
|
|
|
|
@@ -8101,7 +8111,7 @@ def admin_audit_export_pdf_official():
|
|
|
|
|
])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
audit_rows = list(db['audit_log'].find(...).sort('chain_index', -1).limit(limit))
|
|
|
|
|
audit_rows = list(db['audit_log'].find({}).sort('chain_index', -1).limit(limit))
|
|
|
|
|
|
|
|
|
|
# DEC_START: Decrypt sensitive fields for the PDF report
|
|
|
|
|
for row in audit_rows:
|
|
|
|
@@ -8128,7 +8138,7 @@ def admin_audit_export_pdf_official():
|
|
|
|
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
app.logger.error(f"PDF Official Report export error: {str(exc)}\n{traceback.format_exc()}")
|
|
|
|
|
return jsonify({'ok': False, 'error': str(exc)}), 500
|
|
|
|
|
return jsonify({'ok': False, 'error': 'Internal server error'}), 500
|
|
|
|
|
finally:
|
|
|
|
|
if client:
|
|
|
|
|
client.close()
|
|
|
|
@@ -10914,7 +10924,6 @@ def cancel_ausleihung_route(id):
|
|
|
|
|
next_appt = item_doc.get('NextAppointment', {})
|
|
|
|
|
if next_appt and str(next_appt.get('appointment_id')) == str(id):
|
|
|
|
|
cleared = it.clear_item_next_appointment(item_id)
|
|
|
|
|
app.logger.info(f"Cleared NextAppointment for item {item_id}: {cleared}")
|
|
|
|
|
except Exception as clear_err:
|
|
|
|
|
app.logger.warning(f"Warning: could not clear NextAppointment for cancelled ausleihung {id}: {clear_err}")
|
|
|
|
|
else:
|
|
|
|
@@ -10954,13 +10963,12 @@ def reset_item(id):
|
|
|
|
|
if result['success']:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': result['message'],
|
|
|
|
|
'details': result.get('details', {})
|
|
|
|
|
'message': 'Item reset successfully'
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': result['message']
|
|
|
|
|
'message': 'Failed to reset item'
|
|
|
|
|
}), 400
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|