Compare commits

...

3 Commits

5 changed files with 79 additions and 24 deletions
Binary file not shown.
Binary file not shown.
+40 -2
View File
@@ -1196,8 +1196,21 @@ def update_appointment_statuses():
updated_count += 1
if new_status == 'active':
activated_count += 1
# Make item unshareable if no conflict is detected
if old_status == 'planned' and appointment.get('Item') and not extra_fields.get('ConflictDetected', False):
try:
it.update_item_status(str(appointment.get('Item')), False, activation_user)
except Exception as e:
app.logger.warning(f"Could not update item status to False for {appointment['_id']}: {e}")
elif new_status == 'completed':
completed_count += 1
# Make item available again
if appointment.get('Item'):
try:
it.update_item_status(str(appointment.get('Item')), True)
except Exception as e:
app.logger.warning(f"Could not update item status to True for {appointment['_id']}: {e}")
# Create activation notification even if another worker already updated the status.
if old_status == 'planned' and new_status == 'active' and activation_user:
@@ -1246,9 +1259,17 @@ def _initialize_scheduler():
return
try:
# For multi-worker Gunicorn, check if we're in a reasonable scenario
# Using a lock file to ensure only one instance starts the scheduler
# For multi-worker Gunicorn, use a lock file to ensure only one instance starts the scheduler
# Clean up any stale lock file from previous runs (older than 5 minutes)
scheduler_lock_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.scheduler_lock')
try:
if os.path.exists(scheduler_lock_path):
lock_age = time.time() - os.path.getmtime(scheduler_lock_path)
if lock_age > 300: # 5 minutes - indicates a stale lock from a previous container run
os.remove(scheduler_lock_path)
app.logger.info(f"Removed stale scheduler lock file (age: {lock_age:.0f}s)")
except Exception:
pass # If we can't clean up, continue anyway
try:
# Try to create the lock file - only succeeds if it doesn't exist
@@ -9666,6 +9687,13 @@ def schedule_appointment():
# If it became active immediately, log it and send a notification
if initial_status == 'active' and appointment_id:
app.logger.info(f"Appointment {appointment_id} scheduled retroactively as active.")
# Make the item unavailable since it is now actively borrowed
try:
it.update_item_status(item_id, False, session['username'])
except Exception as update_err:
app.logger.warning(f"Failed to update item status when retroactively activating: {update_err}")
# We can also notify the user right away
item_name = item.get('Name', 'Unbekannt')
@@ -9795,6 +9823,16 @@ def cancel_ausleihung_route(id):
if au.cancel_ausleihung(id):
print(f"Successfully canceled ausleihung with ID: {id}")
flash('Ausleihung wurde erfolgreich storniert', 'success')
# If the booking was already active, make the item available again
item_id = str(ausleihung.get('Item')) if ausleihung.get('Item') is not None else None
if ausleihung_status == 'active' and item_id:
try:
it.update_item_status(item_id, True)
print(f"Restored availability of item {item_id} after active cancellation")
except Exception as status_err:
print(f"Warning: could not restore availability of item {item_id}: {status_err}")
_append_audit_event_standalone(
'ausleihung_cancelled',
{
+4 -2
View File
@@ -287,15 +287,17 @@ def update_item_status(id, verfuegbar, user=None):
'LastUpdated': datetime.datetime.now()
}
update_query = {'$set': update_data}
if user is not None:
update_data['User'] = user
elif verfuegbar:
# If item is being marked as available, clear the user field
update_data['$unset'] = {'User': ""}
update_query['$unset'] = {'User': ""}
result = items.update_one(
{'_id': ObjectId(id)},
{'$set': update_data}
update_query
)
client.close()
+35 -20
View File
@@ -69,45 +69,60 @@ with open(compose_file, "r", encoding="utf-8") as f:
out = []
in_app = False
in_build = False
image_set = False
app_indent = None
app_service_indent = None
skip_build_block = False
def leading_spaces(text):
return len(text) - len(text.lstrip(" "))
for line in lines:
stripped = line.lstrip(" ")
indent = len(line) - len(stripped)
indent = leading_spaces(line)
if not in_app and re.match(r"^\s{2}app:\s*$", line):
if not in_app and re.match(r"^\s*app:\s*$", line):
in_app = True
image_set = False
app_indent = indent
app_service_indent = None
skip_build_block = False
out.append(line)
out.append(f" image: {target_image}\n")
image_set = True
continue
if in_app:
if indent == 2 and re.match(r"^[A-Za-z0-9_-]+:\s*$", stripped):
if app_service_indent is None and indent > app_indent:
app_service_indent = indent
if indent <= app_indent and re.match(r"^[A-Za-z0-9_-]+:\s*$", stripped):
in_app = False
in_build = False
app_indent = None
app_service_indent = None
skip_build_block = False
if in_app:
if in_build:
if indent > 4:
continue
in_build = False
if app_service_indent is None:
app_service_indent = indent
if re.match(r"^\s{4}build:\s*$", line):
in_build = True
if skip_build_block:
if indent > app_service_indent:
continue
skip_build_block = False
if re.match(rf"^\s{{{app_service_indent}}}build:\s*$", line):
skip_build_block = True
continue
if re.match(r"^\s{4}image:\s*", line):
if image_set:
continue
out.append(f" image: {target_image}\n")
image_set = True
if re.match(rf"^\s{{{app_service_indent}}}image:\s*", line):
continue
if re.match(rf"^\s{{{app_service_indent}}}[A-Za-z0-9_-]+:\s*$", line):
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
app_service_indent = None
out.append(line)
if in_app and app_service_indent is not None:
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
with open(compose_file, "w", encoding="utf-8") as f:
f.writelines(out)
PY