Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e76d525e4b | |||
| 7117dac67c | |||
| 5313c507ed | |||
| 419fed4492 | |||
| c7112c7a42 | |||
| fb73c9d4e7 | |||
| 8414e27f25 | |||
| 5903f2afdd |
+1
-1
@@ -1 +1 @@
|
||||
v0.8.23
|
||||
v0.8.26
|
||||
|
||||
+67
-40
@@ -37,6 +37,7 @@ if _CURRENT_DIR not in sys.path:
|
||||
import Web.modules.database.user as us
|
||||
import Web.modules.database.items as it
|
||||
import Web.modules.database.ausleihung as au
|
||||
import Web.modules.database.termine as termin
|
||||
import Web.modules.log.audit_log as al
|
||||
import push_notifications as pn
|
||||
import Web.modules.inventarsystem.pdf_export as pdf_export
|
||||
@@ -4817,56 +4818,82 @@ def get_user_appointments():
|
||||
db = client[MONGODB_DB]
|
||||
items_col = db['items']
|
||||
|
||||
# Use the established user-specific route logic for appointments.
|
||||
bookings = au.get_ausleihung_by_user(
|
||||
username,
|
||||
status=['planned', 'active', 'completed'],
|
||||
use_client_side_verification=True,
|
||||
)
|
||||
bookings = sorted(bookings, key=lambda b: b.get('Start') or datetime.datetime.min)
|
||||
# Use the appointment collection for the user's appointments
|
||||
appointments = termin.get_upcoming_for_user(username, limit=250)
|
||||
|
||||
result = []
|
||||
for booking in bookings:
|
||||
start_dt = booking.get('Start')
|
||||
if not start_dt:
|
||||
import re as _re
|
||||
for appt in appointments:
|
||||
appt_id = str(appt.get('_id') or '')
|
||||
if not appt_id:
|
||||
continue
|
||||
|
||||
end_dt = booking.get('End')
|
||||
if not end_dt and isinstance(start_dt, datetime.datetime):
|
||||
end_dt = start_dt + datetime.timedelta(minutes=45)
|
||||
elif not end_dt:
|
||||
end_dt = start_dt
|
||||
date_start = str(appt.get('date_start') or '')
|
||||
date_end = str(appt.get('date_end') or date_start)
|
||||
time_span = appt.get('time_span', []) or []
|
||||
|
||||
item_id = str(booking.get('Item') or '')
|
||||
item_doc = None
|
||||
if item_id:
|
||||
try:
|
||||
item_doc = items_col.find_one({'_id': ObjectId(item_id)})
|
||||
except Exception:
|
||||
item_doc = None
|
||||
# Determine a sensible start and end datetime for the event.
|
||||
# Prefer a time from the time_span for the first day and a time for the last day when multi-day.
|
||||
start_dt_str = date_start
|
||||
end_dt_str = date_end
|
||||
first_time = None
|
||||
last_time = None
|
||||
generic_first = None
|
||||
generic_last = None
|
||||
try:
|
||||
# collect times from spans
|
||||
for entry in time_span:
|
||||
s = str(entry or '').strip()
|
||||
if not s:
|
||||
continue
|
||||
m_date = _re.match(r"^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
||||
if m_date:
|
||||
d = m_date.group(1)
|
||||
if d == date_start and not first_time:
|
||||
first_time = m_date.group(2)
|
||||
if d == date_end:
|
||||
last_time = m_date.group(3)
|
||||
continue
|
||||
m = _re.match(r"^(\d{2}:\d{2})-(\d{2}:\d{2})$", s)
|
||||
if m:
|
||||
if generic_first is None:
|
||||
generic_first = m.group(1)
|
||||
generic_last = m.group(2)
|
||||
|
||||
item_name = item_id or 'Termin'
|
||||
if item_doc:
|
||||
item_name = item_doc.get('Name') or item_doc.get('Code_4') or item_name
|
||||
if not first_time and generic_first:
|
||||
first_time = generic_first
|
||||
if not last_time and generic_last:
|
||||
last_time = generic_last
|
||||
|
||||
period = booking.get('Period')
|
||||
title = item_name
|
||||
if period:
|
||||
title = f"{title} - {period}. Std"
|
||||
if first_time:
|
||||
start_dt_str = f"{date_start}T{first_time}"
|
||||
|
||||
status = booking.get('VerifiedStatus') or booking.get('Status') or 'unknown'
|
||||
if status == 'active':
|
||||
status = 'current'
|
||||
# If appointment spans multiple days and we have a last_time for the final day, use it
|
||||
if date_end != date_start:
|
||||
if last_time:
|
||||
end_dt_str = f"{date_end}T{last_time}"
|
||||
else:
|
||||
# span until end of final day
|
||||
end_dt_str = f"{date_end}T23:59:59"
|
||||
else:
|
||||
if last_time:
|
||||
end_dt_str = f"{date_start}T{last_time}"
|
||||
except Exception:
|
||||
# fallback: use whole-day span
|
||||
if date_end != date_start:
|
||||
end_dt_str = f"{date_end}T23:59:59"
|
||||
|
||||
title = appt.get('note') or f"Termin von {appt.get('user') or ''}"
|
||||
result.append({
|
||||
'id': str(booking.get('_id')),
|
||||
'id': appt_id,
|
||||
'title': title,
|
||||
'start': start_dt.isoformat() if isinstance(start_dt, datetime.datetime) else str(start_dt),
|
||||
'end': end_dt.isoformat() if isinstance(end_dt, datetime.datetime) else str(end_dt),
|
||||
'status': status,
|
||||
'itemId': item_id,
|
||||
'userName': str(booking.get('User') or ''),
|
||||
'notes': str(booking.get('Notes') or ''),
|
||||
'period': period,
|
||||
'start': start_dt_str,
|
||||
'end': end_dt_str,
|
||||
'status': 'planned',
|
||||
'itemId': appt_id,
|
||||
'userName': str(appt.get('user') or ''),
|
||||
'notes': str(appt.get('note') or ''),
|
||||
'period': None,
|
||||
'isCurrentUser': True,
|
||||
'itemBorrower': '',
|
||||
})
|
||||
|
||||
@@ -267,8 +267,9 @@
|
||||
const slotStartSet = new Set(candidateSlots.map(function (slot) { return slot.start; }));
|
||||
const allDays = dateRangeInclusive(String(available.date_start || ''), String(available.date_end || ''));
|
||||
|
||||
let slotMinTime = '08:00:00';
|
||||
let slotMaxTime = '18:00:00';
|
||||
// Show the requested UI time window from 08:15 to 20:00 and highlight available slots
|
||||
let slotMinTime = '08:15:00';
|
||||
let slotMaxTime = '20:00:00';
|
||||
if (candidateSlots.length > 0) {
|
||||
const starts = candidateSlots.map(function (slot) {
|
||||
return new Date(slot.start.replace(' ', 'T') + ':00');
|
||||
@@ -277,13 +278,14 @@
|
||||
return new Date(slot.end.replace(' ', 'T') + ':00');
|
||||
}).filter(function (d) { return !Number.isNaN(d.getTime()); });
|
||||
|
||||
// Keep the computed min/max for gap calculations below
|
||||
var computedMinStart = null;
|
||||
var computedMaxEnd = null;
|
||||
if (starts.length > 0 && ends.length > 0) {
|
||||
let minStart = starts[0];
|
||||
let maxEnd = ends[0];
|
||||
starts.forEach(function (d) { if (d < minStart) minStart = d; });
|
||||
ends.forEach(function (d) { if (d > maxEnd) maxEnd = d; });
|
||||
slotMinTime = formatTimeForCalendar(minStart);
|
||||
slotMaxTime = formatTimeForCalendar(maxEnd);
|
||||
computedMinStart = starts[0];
|
||||
computedMaxEnd = ends[0];
|
||||
starts.forEach(function (d) { if (d < computedMinStart) computedMinStart = d; });
|
||||
ends.forEach(function (d) { if (d > computedMaxEnd) computedMaxEnd = d; });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +357,8 @@
|
||||
}
|
||||
});
|
||||
|
||||
// No background greying: show full calendar skeleton and only mark possible slots
|
||||
|
||||
function updateSliderLabel() {
|
||||
const dateList = dateRangeInclusive(String(available.date_start || ''), String(available.date_end || ''));
|
||||
const idx = Number.parseInt(slider.value, 10) || 0;
|
||||
@@ -411,6 +415,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
// No background gaps: keep full skeleton/grid visible
|
||||
|
||||
// Add candidate free slots (blue)
|
||||
candidateSlots.forEach(function (slot) {
|
||||
const start = new Date(slot.start.replace(' ', 'T') + ':00');
|
||||
const end = new Date(slot.end.replace(' ', 'T') + ':00');
|
||||
@@ -418,8 +425,8 @@
|
||||
title: 'Freier Slot',
|
||||
start: start,
|
||||
end: end,
|
||||
color: '#20c997',
|
||||
textColor: '#062b1f',
|
||||
color: '#0d6efd',
|
||||
textColor: '#ffffff',
|
||||
editable: false,
|
||||
extendedProps: {
|
||||
slotStart: slot.start,
|
||||
|
||||
@@ -287,7 +287,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const filteredEvents = showCompletedBookings
|
||||
? processedEvents
|
||||
: processedEvents.filter(event => event.extendedProps.status !== 'completed');
|
||||
|
||||
|
||||
// If any event spans multiple days, switch to week view for better visibility
|
||||
try {
|
||||
const multiDay = processedEvents.some(ev => {
|
||||
try {
|
||||
const s = new Date(ev.start);
|
||||
const e = new Date(ev.end);
|
||||
return s && e && s.toDateString() !== e.toDateString();
|
||||
} catch (e) { return false; }
|
||||
});
|
||||
if (multiDay && calendar && calendar.view && calendar.view.type === 'timeGridDay') {
|
||||
calendar.changeView('timeGridWeek');
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Provide events to calendar
|
||||
successCallback(filteredEvents);
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user