some more improvements for the appointment borrowing function
Release Inventarsystem / release-docker (push) Successful in 2m30s
Release Inventarsystem / release-docker (push) Successful in 2m30s
This commit is contained in:
+81
-54
@@ -8058,18 +8058,20 @@ def plan_booking():
|
|||||||
# Validate inputs
|
# Validate inputs
|
||||||
if not all([item_id, start_date_str, period_start]):
|
if not all([item_id, start_date_str, period_start]):
|
||||||
return {"success": False, "error": "Missing required fields"}, 400
|
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:
|
if not end_date_str:
|
||||||
end_date_str = start_date_str
|
end_date_str = start_date_str
|
||||||
|
|
||||||
# Parse dates
|
# Parse dates
|
||||||
try:
|
try:
|
||||||
if start_date_str:
|
if start_date_str:
|
||||||
start_date = datetime.datetime.fromisoformat(start_date_str)
|
start_date = datetime.datetime.fromisoformat(start_date_str).replace(tzinfo=None)
|
||||||
else:
|
else:
|
||||||
return {"success": False, "error": "Missing start date"}, 400
|
return {"success": False, "error": "Missing start date"}, 400
|
||||||
|
|
||||||
if end_date_str:
|
if end_date_str:
|
||||||
end_date = datetime.datetime.fromisoformat(end_date_str)
|
end_date = datetime.datetime.fromisoformat(end_date_str).replace(tzinfo=None)
|
||||||
else:
|
else:
|
||||||
return {"success": False, "error": "Missing end date"}, 400
|
return {"success": False, "error": "Missing end date"}, 400
|
||||||
|
|
||||||
@@ -8091,16 +8093,23 @@ def plan_booking():
|
|||||||
|
|
||||||
# Handle period range
|
# Handle period range
|
||||||
periods = []
|
periods = []
|
||||||
if period_start:
|
try:
|
||||||
period_start_num = int(period_start)
|
period_start_num = int(period_start)
|
||||||
else:
|
except (TypeError, ValueError):
|
||||||
period_start_num = 1 # Default if None
|
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 is provided, it's a range of periods
|
||||||
if period_end:
|
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
|
# 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:
|
if period_end_num < period_start_num:
|
||||||
return {"success": False, "error": "End period cannot be before start period"}, 400
|
return {"success": False, "error": "End period cannot be before start period"}, 400
|
||||||
|
|
||||||
@@ -8110,51 +8119,50 @@ def plan_booking():
|
|||||||
# Single period booking
|
# Single period booking
|
||||||
periods = [period_start_num]
|
periods = [period_start_num]
|
||||||
|
|
||||||
# For date range bookings, we'll process each date separately
|
if end_date < start_date:
|
||||||
booking_ids = []
|
return {"success": False, "error": "End date cannot be before start date"}, 400
|
||||||
errors = []
|
if booking_type == 'single' and start_date.date() != end_date.date():
|
||||||
|
return {"success": False, "error": "Single bookings must use one date"}, 400
|
||||||
|
|
||||||
# If it's a range of days
|
requested_slots = []
|
||||||
if booking_type == 'range' and start_date != end_date:
|
current_date = start_date
|
||||||
current_date = start_date
|
last_date = end_date if booking_type == 'range' else start_date
|
||||||
while current_date <= end_date:
|
while current_date.date() <= last_date.date():
|
||||||
# For each day in the range
|
for period in periods:
|
||||||
day_booking_ids, day_errors = process_day_bookings(
|
period_times = get_period_times(current_date, period)
|
||||||
item_id,
|
if not period_times:
|
||||||
current_date,
|
return {"success": False, "error": f"Invalid period {period}"}, 400
|
||||||
periods,
|
requested_slots.append((current_date.date(), period, period_times))
|
||||||
notes
|
current_date += datetime.timedelta(days=1)
|
||||||
)
|
|
||||||
booking_ids.extend(day_booking_ids)
|
|
||||||
errors.extend(day_errors)
|
|
||||||
|
|
||||||
# Move to next day
|
# Preflight the complete request so conflicts never create partial ranges.
|
||||||
current_date += datetime.timedelta(days=1)
|
for index, (booking_date, period, period_times) in enumerate(requested_slots):
|
||||||
else:
|
if au.check_booking_conflict(item_id, period_times['start'], period_times['end'], period):
|
||||||
# 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
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": False,
|
||||||
"partial": True,
|
"error": "Booking conflict",
|
||||||
"booking_ids": booking_ids,
|
"conflicts": [{"date": booking_date.isoformat(), "period": period}],
|
||||||
"errors": errors
|
}, 409
|
||||||
}
|
for previous_date, previous_period, _ in requested_slots[:index]:
|
||||||
else:
|
if previous_date == booking_date and previous_period == period:
|
||||||
# All failed
|
return {"success": False, "error": "Duplicate booking period"}, 400
|
||||||
return {"success": False}, 500
|
|
||||||
else:
|
booking_ids = []
|
||||||
# All succeeded
|
try:
|
||||||
return {"success": True, "booking_ids": booking_ids}
|
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:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
@@ -8214,7 +8222,7 @@ def add_booking():
|
|||||||
if 'username' not in session:
|
if 'username' not in session:
|
||||||
return jsonify({'success': False, 'error': 'Not logged in'})
|
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
|
# Check if item exists and is reservable
|
||||||
item = it.get_item(item_id)
|
item = it.get_item(item_id)
|
||||||
@@ -8243,7 +8251,22 @@ def add_booking():
|
|||||||
end_date_str, '%Y-%m-%d %H:%M:%S'
|
end_date_str, '%Y-%m-%d %H:%M:%S'
|
||||||
).replace(tzinfo=ZoneInfo("Europe/Berlin"))
|
).replace(tzinfo=ZoneInfo("Europe/Berlin"))
|
||||||
else:
|
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
|
# Continue with adding the booking
|
||||||
booking_id = au.add_planned_booking(
|
booking_id = au.add_planned_booking(
|
||||||
@@ -8252,12 +8275,15 @@ def add_booking():
|
|||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
notes=notes,
|
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)})
|
return jsonify({'success': True, 'booking_id': str(booking_id)})
|
||||||
except Exception as e:
|
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'])
|
@app.route('/cancel_booking/<id>', methods=['POST'])
|
||||||
def cancel_booking(id):
|
def cancel_booking(id):
|
||||||
@@ -8276,11 +8302,12 @@ def cancel_booking(id):
|
|||||||
current_permissions = us.get_effective_permissions(session['username'])
|
current_permissions = us.get_effective_permissions(session['username'])
|
||||||
|
|
||||||
# Check if user owns this booking
|
# 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
|
return {"success": False, "error": "Not authorized to cancel this booking"}, 403
|
||||||
|
|
||||||
# Cancel the booking
|
# Cancel the booking
|
||||||
result = au.cancel_booking(id)
|
result = au.cancel_ausleihung(id)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ def cancel_ausleihung(id):
|
|||||||
|
|
||||||
# Mark the booking as cancelled
|
# Mark the booking as cancelled
|
||||||
result = ausleihungen.update_one(
|
result = ausleihungen.update_one(
|
||||||
{'_id': ObjectId(id)},
|
{'_id': ObjectId(id), 'Status': {'$in': ['planned', 'active']}},
|
||||||
{'$set': {
|
{'$set': {
|
||||||
'Status': 'cancelled',
|
'Status': 'cancelled',
|
||||||
'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||||
@@ -367,6 +367,16 @@ def cancel_ausleihung(id):
|
|||||||
return False
|
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):
|
def remove_ausleihung(id):
|
||||||
"""
|
"""
|
||||||
Markiert einen Ausleihungsdatensatz als gelöscht (Soft-Delete).
|
Markiert einen Ausleihungsdatensatz als gelöscht (Soft-Delete).
|
||||||
|
|||||||
Reference in New Issue
Block a user