Skip to content

Commit f52e3e4

Browse files
committed
Merge branch 'main' into timezone-utils
2 parents 0bd8955 + 0768cdb commit f52e3e4

File tree

11 files changed

+117
-41
lines changed

11 files changed

+117
-41
lines changed

appointment/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ def get_services_offered(self):
244244
def get_service_offered_text(self):
245245
return ', '.join([service.name for service in self.services_offered.all()])
246246

247+
def get_service_is_offered(self, service_id):
248+
return self.services_offered.filter(id=service_id).exists()
249+
247250
def get_appointment_buffer_time(self):
248251
config = Config.objects.first()
249252
return self.appointment_buffer_time or (config.appointment_buffer_time if config else 0)

appointment/services.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,19 +308,22 @@ def get_working_hours_and_days_off_context(request, btn_txt, form_name, form, us
308308

309309

310310
def save_appointment(appt, client_name, client_email, start_time, phone_number, client_address, service_id, request,
311-
want_reminder=False, additional_info=None):
311+
staff_member_id=None, want_reminder=False, additional_info=None):
312312
"""Save an appointment's details.
313313
:return: The modified appointment.
314314
"""
315+
service = Service.objects.get(id=service_id)
316+
if staff_member_id:
317+
staff_member = StaffMember.objects.get(id=staff_member_id)
318+
if not staff_member.get_service_is_offered(service_id):
319+
return None
315320
# Modify and save client details
316321
first_name, last_name = parse_name(client_name)
317322
client = appt.client
318323
client.first_name = first_name
319324
client.last_name = last_name
320325
client.email = client_email
321326
client.save()
322-
323-
service = Service.objects.get(id=service_id)
324327
# convert start time to a time object if it is a string
325328
if isinstance(start_time, str):
326329
start_time = convert_str_to_time(start_time)
@@ -337,6 +340,7 @@ def save_appointment(appt, client_name, client_email, start_time, phone_number,
337340
appt_request.service = service
338341
appt_request.start_time = start_time
339342
appt_request.end_time = end_time
343+
appt_request.staff_member = staff_member
340344
appt_request.save()
341345

342346
# Modify and save appointment details
@@ -559,7 +563,11 @@ def handle_service_management_request(post_data, files_data=None, service_id=Non
559563

560564
def create_new_appointment(data, request):
561565
service = Service.objects.get(id=data.get("service_id"))
562-
staff_member = StaffMember.objects.get(user=request.user)
566+
staff_id = data.get("staff_id")
567+
if staff_id:
568+
staff_member = StaffMember.objects.get(id=staff_id)
569+
else:
570+
staff_member = StaffMember.objects.get(user=request.user)
563571

564572
# Convert date and start time strings to datetime objects
565573
date = convert_str_to_date(data.get("date"))
@@ -610,6 +618,7 @@ def create_new_appointment(data, request):
610618
def update_existing_appointment(data, request):
611619
try:
612620
appt = Appointment.objects.get(id=data.get("appointment_id"))
621+
staff_id = data.get("staff_id")
613622
want_reminder = data.get("want_reminder") == 'true'
614623
appt = save_appointment(
615624
appt,
@@ -621,8 +630,12 @@ def update_existing_appointment(data, request):
621630
service_id=data.get("service_id"),
622631
want_reminder=want_reminder,
623632
additional_info=data.get("additional_info"),
633+
staff_member_id=staff_id,
624634
request=request,
625635
)
636+
if not appt:
637+
return json_response("Service not offered by staff member.", status=400, success=False,
638+
error_code=ErrorCode.SERVICE_NOT_FOUND)
626639
appointments_json = convert_appointment_to_json(request, [appt])[0]
627640
return json_response(appt_updated_successfully, custom_data={'appt': appointments_json})
628641
except Appointment.DoesNotExist:

appointment/static/js/app_admin/staff_index.js

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ document.addEventListener("DOMContentLoaded", function () {
4040
});
4141
});
4242

43-
4443
function initializeCalendar() {
4544
const formattedAppointments = formatAppointmentsForCalendar(appointments);
4645
const calendarEl = document.getElementById('calendar');
@@ -244,7 +243,6 @@ function setUserStaffAdminFlag() {
244243
});
245244
}
246245

247-
248246
function handleCalendarRightClick(event, date) {
249247
if (!AppState.isUserStaffAdmin) {
250248
showErrorModal(notStaffMemberTxt)
@@ -376,6 +374,15 @@ function fetchServices(isEditMode = false) {
376374
.catch(error => console.error("Error fetching services: ", error));
377375
}
378376

377+
function fetchStaffMembers(isEditMode = false) {
378+
let url = isEditMode && AppState.eventIdSelected ? `${fetchStaffListURL}?appointmentId=${AppState.eventIdSelected}` : fetchStaffListURL;
379+
return fetch(url)
380+
.then(response => response.json())
381+
.then(data => data.staff_members)
382+
.catch(error => console.error("Error fetching staff members: ", error));
383+
384+
}
385+
379386
async function populateServices(selectedServiceId, isEditMode = false) {
380387
const services = await fetchServices(isEditMode);
381388
if (!services) {
@@ -394,6 +401,24 @@ async function populateServices(selectedServiceId, isEditMode = false) {
394401
return selectElement;
395402
}
396403

404+
async function populateStaffMembers(selectedStaffId, isEditMode = false) {
405+
const staffMembers = await fetchStaffMembers(isEditMode);
406+
if (!staffMembers) {
407+
showErrorModal(noStaffMemberTxt)
408+
}
409+
const selectElement = document.createElement('select');
410+
staffMembers.forEach(staff => {
411+
const option = document.createElement('option');
412+
option.value = staff.id; // Accessing the id
413+
option.textContent = staff.name; // Accessing the name
414+
if (staff.id === selectedStaffId) {
415+
option.defaultSelected = true;
416+
}
417+
selectElement.appendChild(option);
418+
});
419+
return selectElement;
420+
}
421+
397422
function getCSRFToken() {
398423
const metaTag = document.querySelector('meta[name="csrf-token"]');
399424
if (metaTag) {
@@ -483,13 +508,13 @@ function createNewAppointment(dateInput) {
483508
});
484509
}
485510

486-
487511
async function showCreateAppointmentModal(defaultStartTime, formattedDate) {
488512
const servicesDropdown = await populateServices(null, false);
513+
const staffDropdown = await populateStaffMembers(null, false);
489514
servicesDropdown.id = "serviceSelect";
490515
servicesDropdown.disabled = false; // Enable dropdown
491516

492-
document.getElementById('eventModalBody').innerHTML = prepareCreateAppointmentModalContent(servicesDropdown, defaultStartTime, formattedDate);
517+
document.getElementById('eventModalBody').innerHTML = prepareCreateAppointmentModalContent(servicesDropdown, staffDropdown, defaultStartTime, formattedDate);
493518

494519
adjustCreateAppointmentModalButtons();
495520
AppState.isCreating = true;
@@ -504,7 +529,6 @@ function adjustCreateAppointmentModalButtons() {
504529
document.getElementById("eventGoBtn").style.display = "none";
505530
}
506531

507-
508532
// ################################################################ //
509533
// Show Event Modal //
510534
// ################################################################ //
@@ -543,13 +567,23 @@ async function getServiceDropdown(serviceId, isEditMode) {
543567
return servicesDropdown;
544568
}
545569

570+
// Populate Staff Dropdown
571+
async function getStaffDropdown(staffId, isEditMode) {
572+
const staffDropdown = await populateStaffMembers(staffId, !isEditMode);
573+
staffDropdown.id = "staffSelect";
574+
staffDropdown.disabled = !isEditMode;
575+
return staffDropdown;
576+
}
577+
546578
// Show Event Modal
547579
async function showEventModal(eventId = null, isEditMode, isCreatingMode = false, defaultStartTime = '') {
548580
const appointment = await getAppointmentData(eventId, isCreatingMode, defaultStartTime);
549581
if (!appointment) return;
550582

551583
const servicesDropdown = await getServiceDropdown(appointment.service_id, isEditMode);
552-
document.getElementById('eventModalBody').innerHTML = generateModalContent(appointment, servicesDropdown, isEditMode);
584+
const staffDropdown = await getStaffDropdown(appointment.staff_id, isEditMode);
585+
586+
document.getElementById('eventModalBody').innerHTML = generateModalContent(appointment, servicesDropdown, isEditMode, staffDropdown);
553587
adjustModalButtonsVisibility(isEditMode, isCreatingMode);
554588
$('#eventDetailsModal').modal('show');
555589
}
@@ -587,6 +621,7 @@ function toggleEditMode() {
587621

588622
function updateModalUIForEditMode(modal, isEditingAppointment) {
589623
const inputs = modal.querySelectorAll("input");
624+
const staffDropdown = document.getElementById("staffSelect");
590625
const servicesDropdown = document.getElementById("serviceSelect");
591626
const editButton = document.getElementById("eventEditBtn");
592627
const submitButton = document.getElementById("eventSubmitBtn");
@@ -599,6 +634,7 @@ function updateModalUIForEditMode(modal, isEditingAppointment) {
599634

600635
// Toggle input and dropdown enable/disable state
601636
inputs.forEach(input => input.disabled = !isEditingAppointment);
637+
staffDropdown.disabled = !isEditingAppointment;
602638
servicesDropdown.disabled = !isEditingAppointment;
603639

604640
// Toggle visibility of UI elements
@@ -618,7 +654,6 @@ function toggleElementVisibility(element, isVisible) {
618654
}
619655
}
620656

621-
622657
// ################################################################ //
623658
// Submit Logic //
624659
// ################################################################ //
@@ -651,7 +686,13 @@ async function submitChanges() {
651686
function collectFormDataFromModal(modal) {
652687
const inputs = modal.querySelectorAll("input");
653688
const serviceId = modal.querySelector("#serviceSelect").value;
654-
const data = {isCreating: AppState.isCreating, service_id: serviceId, appointment_id: AppState.eventIdSelected};
689+
const staffId = modal.querySelector("#staffSelect").value;
690+
const data = {
691+
isCreating: AppState.isCreating,
692+
service_id: serviceId,
693+
staff_id: staffId,
694+
appointment_id: AppState.eventIdSelected
695+
};
655696

656697
inputs.forEach(input => {
657698
if (input.name !== "date") {
@@ -711,22 +752,6 @@ async function sendAppointmentData(data) {
711752
});
712753
}
713754

714-
// Handle response from server
715-
async function handleAppointmentResponse(response) {
716-
if (!response.ok) {
717-
throw new Error(response.message);
718-
}
719-
720-
const responseData = await response.json();
721-
if (AppState.isCreating) {
722-
addNewAppointmentToCalendar(responseData.appt[0]);
723-
} else {
724-
updateExistingAppointmentInCalendar(responseData.appt);
725-
}
726-
727-
AppState.calendar.render();
728-
}
729-
730755
// Add new appointment to calendar
731756
function addNewAppointmentToCalendar(newAppointment) {
732757
const newEvent = formatAppointmentsForCalendar([newAppointment])[0];

appointment/templates/administration/staff_index.html

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
margin-right: 10px;
116116
}
117117

118-
#serviceSelect {
118+
#serviceSelect, #staffSelect {
119119
width: 100%;
120120
padding: 8px 12px;
121121
border: 1px solid #ccc;
@@ -125,7 +125,7 @@
125125
transition: background-color 0.3s ease;
126126
}
127127

128-
#serviceSelect:disabled {
128+
#serviceSelect:disabled, #staffSelect:disabled {
129129
background-color: #e9ecef;
130130
}
131131

@@ -311,10 +311,12 @@
311311
const serviceDuration = parseInt("{{ service.duration.total_seconds }}") / 60;
312312
let appointments = {{ appointments|safe }};
313313
const fetchServiceListForStaffURL = "{% url 'appointment:fetch_service_list_for_staff' %}";
314+
const fetchStaffListURL = "{% url 'appointment:fetch_staff_list' %}";
314315
const updateApptMinInfoURL = "{% url 'appointment:update_appt_min_info' %}";
315316
const updateApptDateURL = "{% url 'appointment:update_appt_date_time' %}";
316317
const validateApptDateURL = "{% url 'appointment:validate_appointment_date' %}";
317318
const isUserStaffAdminURL = "{% url 'appointment:is_user_staff_admin' %}";
319+
const isUserSuperUser = "{{ is_superuser }}";
318320
</script>
319321
<script>
320322
{# Text for translation #}
@@ -330,6 +332,7 @@
330332
const apptNotFoundTxt = "{% trans 'Appointment not found.' %}";
331333
const notStaffMemberTxt = "{% trans "You're not a staff member. Can't perform this action !" %}";
332334
const noServiceOfferedTxt = "{% trans "You don't offer any service. Add new service from your profile." %}";
335+
const noStaffMemberTxt = "{% trans "No staff members found." %}";
333336
</script>
334337

335338
<script src="{% static 'js/modal/error_modal.js' %}"></script>
@@ -338,11 +341,25 @@
338341
<script src="{% static 'js/js-utils.js' %}"></script>
339342

340343
<script>
341-
function createCommonInputFields(appointment, servicesDropdown, isEditMode, defaultStartTime) {
344+
console.log("isUserSuperUser", isUserSuperUser)
345+
346+
function createCommonInputFields(appointment, servicesDropdown, isEditMode, defaultStartTime, staffDropdown) {
342347
const startTimeValue = isEditMode ? moment(appointment.start_time).format('HH:mm:ss') : defaultStartTime;
343348
const disabledAttribute = isEditMode ? '' : 'disabled';
349+
350+
const isSuperuser = isUserSuperUser === "True";
351+
let superuserInputField = '';
352+
if (isSuperuser) {
353+
superuserInputField = `
354+
<div class="flex-container-appt">
355+
<label>{% trans 'Staff Member' %}:</label>
356+
${staffDropdown.outerHTML}
357+
</div>
358+
`;
359+
}
344360

345361
return `
362+
${superuserInputField}
346363
<div class="flex-container-appt">
347364
<label>{% trans 'Service Name' %}:</label>
348365
${servicesDropdown.outerHTML}
@@ -369,9 +386,9 @@
369386
`;
370387
}
371388

372-
function generateModalContent(appointment, servicesDropdown, isEditMode) {
389+
function generateModalContent(appointment, servicesDropdown, isEditMode, staffDropdown) {
373390
const startTimeValue = moment(appointment.start_time).format('HH:mm:ss');
374-
const commonInputs = createCommonInputFields(appointment, servicesDropdown, isEditMode, startTimeValue);
391+
const commonInputs = createCommonInputFields(appointment, servicesDropdown, isEditMode, startTimeValue, staffDropdown);
375392
return `
376393
${commonInputs}
377394
<div class="flex-container-appt">
@@ -381,9 +398,9 @@
381398
`;
382399
}
383400

384-
function prepareCreateAppointmentModalContent(servicesDropdown, defaultStartTime, formattedDate) {
401+
function prepareCreateAppointmentModalContent(servicesDropdown, staffDropdown, defaultStartTime, formattedDate) {
385402
const appointment = {client_name: '', client_email: '', client_phone: '', client_address: ''};
386-
const commonInputs = createCommonInputFields(appointment, servicesDropdown, true, defaultStartTime);
403+
const commonInputs = createCommonInputFields(appointment, servicesDropdown, true, defaultStartTime, staffDropdown);
387404

388405
return `
389406
${commonInputs}

appointment/tests/test_services.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,11 @@ def test_save_appointment(self):
349349
phone_number = "+1234567890"
350350
client_address = "123 New St, TestCity"
351351
service_id = self.service2.id
352+
staff_member_id = self.staff_member2.id
352353

353354
# Call the function
354355
updated_appt = save_appointment(self.appt, client_name, client_email, start_time_str, phone_number,
355-
client_address, service_id, self.request)
356+
client_address, service_id, self.request, staff_member_id)
356357

357358
# Check client details
358359
self.assertEqual(updated_appt.client.get_full_name(), client_name)

appointment/tests/test_views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def setUp(self):
6161
self.data = {
6262
'isCreating': False, 'service_id': '1', 'appointment_id': self.appt.id, 'client_name': 'Bryan Zap',
6363
'client_email': '[email protected]', 'client_phone': '+33769992738', 'client_address': 'Naples, Florida',
64-
'want_reminder': 'false', 'additional_info': '', 'start_time': '15:00:26',
64+
'want_reminder': 'false', 'additional_info': '', 'start_time': '15:00:26', 'staff_id': self.staff_member.id,
6565
'date': self.tomorrow.strftime('%Y-%m-%d')
6666
}
6767
self.url_display_appt = reverse('appointment:display_appointment', args=[self.appointment.id])
@@ -405,6 +405,7 @@ def test_update_appt_min_info_update(self):
405405
# Making the request
406406
response = self.client.post(url, data=json.dumps(self.data), content_type='application/json',
407407
**{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})
408+
print(f"response: {response.content}")
408409

409410
# Check response status
410411
self.assertEqual(response.status_code, 200)

appointment/urls.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from appointment.views_admin import (
1717
add_day_off, add_or_update_service, add_or_update_staff_info, add_working_hours, create_new_staff_member,
1818
delete_appointment, delete_appointment_ajax, delete_day_off, delete_service, delete_working_hours,
19-
display_appointment, email_change_verification_code, fetch_service_list_for_staff, get_service_list,
20-
get_user_appointments, is_user_staff_admin, make_superuser_staff_member, remove_staff_member,
19+
display_appointment, email_change_verification_code, fetch_service_list_for_staff, fetch_staff_list,
20+
get_service_list, get_user_appointments, is_user_staff_admin, make_superuser_staff_member, remove_staff_member,
2121
remove_superuser_staff_member, update_appt_date_time, update_appt_min_info, update_day_off, update_personal_info,
2222
update_working_hours, user_profile, validate_appointment_date
2323
)
@@ -87,6 +87,7 @@
8787
name='request_next_available_slot'),
8888
path('request_staff_info/', get_non_working_days_ajax, name='get_non_working_days_ajax'),
8989
path('fetch_service_list_for_staff/', fetch_service_list_for_staff, name='fetch_service_list_for_staff'),
90+
path('fetch_staff_list/', fetch_staff_list, name='fetch_staff_list'),
9091
path('update_appt_min_info/', update_appt_min_info, name="update_appt_min_info"),
9192
path('update_appt_date_time/', update_appt_date_time, name="update_appt_date_time"),
9293
path('validate_appointment_date/', validate_appointment_date, name="validate_appointment_date"),

0 commit comments

Comments
 (0)