implementation of the right time zones and blocked user by mahnung as a blocker for the library ausleih process
Release Inventarsystem / release-docker (push) Successful in 2m14s

This commit is contained in:
2026-08-23 12:26:36 +02:00
parent 0f0f0ebefd
commit df9367a19d
7 changed files with 206 additions and 141 deletions
+94 -118
View File
@@ -228,96 +228,6 @@ def rollover_student_card_classes(dry_run=False, *, max_class=None, graduate_lab
client.close()
@app.route('/api/library_return_by_code', methods=['POST'])
def api_library_return_by_code():
"""
Return a library item by scanning its code only (no student card required).
This marks active ausleihungen for the item as completed and updates item status.
"""
if 'username' not in session:
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
if not cfg.MODULES.is_enabled('library'):
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
payload = request.get_json(silent=True) or {}
item_code_raw = str(payload.get('item_code') or payload.get('code') or '').strip()
if not item_code_raw:
return jsonify({'ok': False, 'message': 'Mediencode fehlt.'}), 400
normalized_isbn = normalize_and_validate_isbn(item_code_raw)
normalized_code = item_code_raw.upper()
client = None
try:
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
db = client[MONGODB_DB]
items_col = db['items']
ausleihungen_col = db['ausleihungen']
query_or = [
{'Code_4': item_code_raw},
{'Code_4': normalized_code},
]
if normalized_isbn:
query_or.append({'ISBN': normalized_isbn})
item_doc = items_col.find_one({
'ItemType': {'$in': LIBRARY_ITEM_TYPES},
'$or': query_or
})
if not item_doc:
return jsonify({'ok': False, 'message': 'Kein Bibliotheksmedium für diesen Code gefunden.'}), 404
item_id = str(item_doc['_id'])
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
# If item already available -> nothing to return
if item_doc.get('Verfuegbar', True):
return jsonify({'ok': False, 'message': 'Dieses Medium ist nicht als ausgeliehen markiert.'}), 409
# Mark active ausleihungen as completed
update_result = ausleihungen_col.update_many(
{'Item': item_id, 'Status': 'active'},
{'$set': {
'Status': 'completed',
'End': now,
'LastUpdated': now
}}
)
# Update item status to available
borrower_name = str(item_doc.get('User') or '').strip() or ''
it.update_item_status(item_id, True, borrower_name)
_append_audit_event_standalone(
event_type='ausleihung_returned_by_code',
payload={
'channel': 'library_return_code',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'performed_by': session.get('username')
}
)
return jsonify({
'ok': True,
'action': 'returned',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'message': f"{item_doc.get('Name', 'Medium')} wurde zurückgegeben."
}), 200
except Exception as e:
app.logger.error(f"Error in library return by code: {e}")
return jsonify({'ok': False, 'message': 'Fehler beim Verarbeiten der Rückgabe.'}), 500
finally:
if client:
client.close()
# Admin route to trigger rollover manually
@app.route('/admin/trigger_school_year_rollover', methods=['POST'])
def admin_trigger_school_year_rollover():
@@ -333,33 +243,6 @@ def admin_trigger_school_year_rollover():
return jsonify({'ok': True, 'summary': summary}), 200
# Schedule annual rollover job using APScheduler (configurable via env)
try:
if cfg.SCHEDULER_ENABLED:
_rollover_month = getattr(cfg, 'SCHOOL_ROLLOVER_MONTH', 9)
_rollover_day = getattr(cfg, 'SCHOOL_ROLLOVER_DAY', 1)
_rollover_hour = getattr(cfg, 'SCHOOL_ROLLOVER_HOUR', 3)
_rollover_minute = getattr(cfg, 'SCHOOL_ROLLOVER_MIN', 0)
_rollover_max_class = getattr(cfg, 'SCHOOL_ROLLOVER_MAX_CLASS', 13)
_rollover_grad_label = getattr(cfg, 'SCHOOL_ROLLOVER_GRADUATE_LABEL', '')
_scheduler = BackgroundScheduler()
# Use a cron-style yearly job on the configured month/day
_scheduler.add_job(
func=lambda: rollover_student_card_classes(dry_run=False, max_class=_rollover_max_class, graduate_label=_rollover_grad_label),
trigger='cron',
month=_rollover_month,
day=_rollover_day,
hour=_rollover_hour,
minute=_rollover_minute,
id='school_year_rollover',
replace_existing=True
)
_scheduler.start()
app.logger.info('Scheduled annual school year rollover: %s-%s %s:%s', _rollover_month, _rollover_day, _rollover_hour, _rollover_minute)
except Exception as e:
app.logger.warning('Failed to schedule school year rollover: %s', e)
# Thumbnail sizes
THUMBNAIL_SIZE = cfg.THUMBNAIL_SIZE
PREVIEW_SIZE = cfg.PREVIEW_SIZE
@@ -4291,6 +4174,93 @@ def api_library_group(series_group_id):
app.logger.error('Error loading library group %s: %s', series_group_id, exc)
return jsonify({'items': [], 'message': 'Gruppe konnte nicht geladen werden.'}), 500
@app.route('/api/library_return_by_code', methods=['POST'])
def api_library_return_by_code():
"""
Return a library item by scanning its code only (no student card required).
This marks active ausleihungen for the item as completed and updates item status.
"""
if 'username' not in session:
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
if not cfg.MODULES.is_enabled('library'):
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
payload = request.get_json(silent=True) or {}
item_code_raw = str(payload.get('item_code') or payload.get('code') or '').strip()
if not item_code_raw:
return jsonify({'ok': False, 'message': 'Mediencode fehlt.'}), 400
normalized_isbn = normalize_and_validate_isbn(item_code_raw)
normalized_code = item_code_raw.upper()
client = None
try:
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
db = client[MONGODB_DB]
items_col = db['items']
ausleihungen_col = db['ausleihungen']
query_or = [
{'Code_4': item_code_raw},
{'Code_4': normalized_code},
]
if normalized_isbn:
query_or.append({'ISBN': normalized_isbn})
item_doc = items_col.find_one({
'ItemType': {'$in': LIBRARY_ITEM_TYPES},
'$or': query_or
})
if not item_doc:
return jsonify({'ok': False, 'message': 'Kein Bibliotheksmedium für diesen Code gefunden.'}), 404
item_id = str(item_doc['_id'])
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
# If item already available -> nothing to return
if item_doc.get('Verfuegbar', True):
return jsonify({'ok': False, 'message': 'Dieses Medium ist nicht als ausgeliehen markiert.'}), 409
# Mark active ausleihungen as completed
update_result = ausleihungen_col.update_many(
{'Item': item_id, 'Status': 'active'},
{'$set': {
'Status': 'completed',
'End': now,
'LastUpdated': now
}}
)
# Update item status to available
borrower_name = str(item_doc.get('User') or '').strip() or ''
it.update_item_status(item_id, True, borrower_name)
_append_audit_event_standalone(
event_type='ausleihung_returned_by_code',
payload={
'channel': 'library_return_code',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'performed_by': session.get('username')
}
)
return jsonify({
'ok': True,
'action': 'returned',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'message': f"{item_doc.get('Name', 'Medium')} wurde zurückgegeben."
}), 200
except Exception as e:
app.logger.error(f"Error in library return by code: {e}")
return jsonify({'ok': False, 'message': 'Fehler beim Verarbeiten der Rückgabe.'}), 500
finally:
if client:
client.close()
@app.route('/api/library_scan_action', methods=['POST'])
def api_library_scan_action():
@@ -4320,6 +4290,10 @@ def api_library_scan_action():
normalized_isbn = normalize_and_validate_isbn(item_code_raw)
normalized_code = item_code_raw.upper()
if us.student_is_blocked(student_card_id):
flash("User Blockiert!", "error")
return jsonify({'ok': False, 'message': 'User Blockiert.'}), 404
client = None
try:
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
@@ -4373,8 +4347,10 @@ def api_library_scan_action():
card_default = cfg.STUDENT_DEFAULT_BORROW_DAYS
borrow_duration_days = max(1, min(card_default, cfg.STUDENT_MAX_BORROW_DAYS))
due_date = now + datetime.timedelta(days=borrow_duration_days)
it.update_item_status(item_id, False, borrower_name)
au.add_ausleihung(item_id, borrower_name, now)
au.add_ausleihung(item_id, borrower_name, now, due_date)
_append_audit_event_standalone(
event_type='ausleihung_borrowed',