Compare commits

..

1 Commits

Author SHA1 Message Date
Aiirondev_dev 9ad81d9b6d some more improvements for the appointment borrowing function
Release Inventarsystem / release-docker (push) Successful in 2m30s
2026-09-15 20:20:52 +02:00
2 changed files with 95 additions and 58 deletions
+84 -57
View File
@@ -8058,18 +8058,20 @@ def plan_booking():
# Validate inputs
if not all([item_id, start_date_str, period_start]):
return {"success": False, "error": "Missing required fields"}, 400
if booking_type not in {'single', 'range'}:
return {"success": False, "error": "Invalid booking type"}, 400
if not end_date_str:
end_date_str = start_date_str
# Parse dates
try:
if start_date_str:
start_date = datetime.datetime.fromisoformat(start_date_str)
start_date = datetime.datetime.fromisoformat(start_date_str).replace(tzinfo=None)
else:
return {"success": False, "error": "Missing start date"}, 400
if end_date_str:
end_date = datetime.datetime.fromisoformat(end_date_str)
end_date = datetime.datetime.fromisoformat(end_date_str).replace(tzinfo=None)
else:
return {"success": False, "error": "Missing end date"}, 400
@@ -8091,16 +8093,23 @@ def plan_booking():
# Handle period range
periods = []
if period_start:
try:
period_start_num = int(period_start)
else:
period_start_num = 1 # Default if None
except (TypeError, ValueError):
return {"success": False, "error": "Invalid start period"}, 400
if not 1 <= period_start_num <= 10:
return {"success": False, "error": "Invalid start period"}, 400
# If period_end is provided, it's a range of periods
if period_end:
period_end_num = int(period_end)
try:
period_end_num = int(period_end)
except (TypeError, ValueError):
return {"success": False, "error": "Invalid end period"}, 400
# Validate period range
if not 1 <= period_end_num <= 10:
return {"success": False, "error": "Invalid end period"}, 400
if period_end_num < period_start_num:
return {"success": False, "error": "End period cannot be before start period"}, 400
@@ -8110,51 +8119,50 @@ def plan_booking():
# Single period booking
periods = [period_start_num]
# For date range bookings, we'll process each date separately
booking_ids = []
errors = []
# If it's a range of days
if booking_type == 'range' and start_date != end_date:
current_date = start_date
while current_date <= end_date:
# For each day in the range
day_booking_ids, day_errors = process_day_bookings(
item_id,
current_date,
periods,
notes
)
booking_ids.extend(day_booking_ids)
errors.extend(day_errors)
# Move to next day
current_date += datetime.timedelta(days=1)
else:
# Single day with multiple periods
booking_ids, errors = process_day_bookings(
item_id,
start_date,
periods,
notes
)
# Return results
if errors:
if booking_ids:
# Some succeeded, some failed
if end_date < start_date:
return {"success": False, "error": "End date cannot be before start date"}, 400
if booking_type == 'single' and start_date.date() != end_date.date():
return {"success": False, "error": "Single bookings must use one date"}, 400
requested_slots = []
current_date = start_date
last_date = end_date if booking_type == 'range' else start_date
while current_date.date() <= last_date.date():
for period in periods:
period_times = get_period_times(current_date, period)
if not period_times:
return {"success": False, "error": f"Invalid period {period}"}, 400
requested_slots.append((current_date.date(), period, period_times))
current_date += datetime.timedelta(days=1)
# Preflight the complete request so conflicts never create partial ranges.
for index, (booking_date, period, period_times) in enumerate(requested_slots):
if au.check_booking_conflict(item_id, period_times['start'], period_times['end'], period):
return {
"success": True,
"partial": True,
"booking_ids": booking_ids,
"errors": errors
}
else:
# All failed
return {"success": False}, 500
else:
# All succeeded
return {"success": True, "booking_ids": booking_ids}
"success": False,
"error": "Booking conflict",
"conflicts": [{"date": booking_date.isoformat(), "period": period}],
}, 409
for previous_date, previous_period, _ in requested_slots[:index]:
if previous_date == booking_date and previous_period == period:
return {"success": False, "error": "Duplicate booking period"}, 400
booking_ids = []
try:
for _, period, period_times in requested_slots:
booking_id = au.add_planned_booking(
item_id, session['username'], period_times['start'],
period_times['end'], notes, period=period
)
if not booking_id:
raise RuntimeError(f"Failed to create booking for period {period}")
booking_ids.append(str(booking_id))
except Exception:
for booking_id in booking_ids:
au.cancel_ausleihung(booking_id)
raise
return {"success": True, "booking_ids": booking_ids}
except Exception as e:
import traceback
@@ -8214,7 +8222,7 @@ def add_booking():
if 'username' not in session:
return jsonify({'success': False, 'error': 'Not logged in'})
item_id = html.escape(request.form.get('item_id'))
item_id = html.escape((request.form.get('item_id') or '').strip())
# Check if item exists and is reservable
item = it.get_item(item_id)
@@ -8243,7 +8251,22 @@ def add_booking():
end_date_str, '%Y-%m-%d %H:%M:%S'
).replace(tzinfo=ZoneInfo("Europe/Berlin"))
else:
end_date = None
return jsonify({'success': False, 'error': 'Missing end date'}), 400
if end_date <= start_date:
return jsonify({'success': False, 'error': 'End date must be after start date'}), 400
period_value = None
if period not in (None, ''):
try:
period_value = int(period)
except (TypeError, ValueError):
return jsonify({'success': False, 'error': 'Invalid period'}), 400
if not 1 <= period_value <= 10:
return jsonify({'success': False, 'error': 'Invalid period'}), 400
if au.check_booking_conflict(item_id, start_date, end_date, period_value):
return jsonify({'success': False, 'error': 'Booking conflict'}), 409
# Continue with adding the booking
booking_id = au.add_planned_booking(
@@ -8252,12 +8275,15 @@ def add_booking():
start_date=start_date,
end_date=end_date,
notes=notes,
period=period
period=period_value
)
if not booking_id:
return jsonify({'success': False, 'error': 'Failed to create booking'}), 500
return jsonify({'success': True, 'booking_id': str(booking_id)})
except Exception as e:
return jsonify({'success': False})
app.logger.error(f"Error creating booking: {e}")
return jsonify({'success': False, 'error': 'Invalid booking data'}), 400
@app.route('/cancel_booking/<id>', methods=['POST'])
def cancel_booking(id):
@@ -8276,11 +8302,12 @@ def cancel_booking(id):
current_permissions = us.get_effective_permissions(session['username'])
# Check if user owns this booking
if booking.get('User') != session['username'] and not current_permissions['actions'].get('can_manage_users', False):
booking_user = au.dp.decrypt_text(booking.get('User')) if booking.get('User') else ''
if booking_user != session['username'] and not current_permissions['actions'].get('can_manage_users', False):
return {"success": False, "error": "Not authorized to cancel this booking"}, 403
# Cancel the booking
result = au.cancel_booking(id)
result = au.cancel_ausleihung(id)
if result:
return {"success": True}
+11 -1
View File
@@ -353,7 +353,7 @@ def cancel_ausleihung(id):
# Mark the booking as cancelled
result = ausleihungen.update_one(
{'_id': ObjectId(id)},
{'_id': ObjectId(id), 'Status': {'$in': ['planned', 'active']}},
{'$set': {
'Status': 'cancelled',
'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
@@ -367,6 +367,16 @@ def cancel_ausleihung(id):
return False
def get_booking(id):
"""Compatibility wrapper for the booking route."""
return get_ausleihung(id)
def cancel_booking(id):
"""Compatibility wrapper for the booking route."""
return cancel_ausleihung(id)
def remove_ausleihung(id):
"""
Markiert einen Ausleihungsdatensatz als gelöscht (Soft-Delete).