Skip to content

Commit 648fa16

Browse files
authored
50 create appointment on calendar day item (#55)
* Updated documentation's version * Feature and UI Enhancements for v2.1.5 - Implemented dynamic AJAX-based appointment management functionalities. - Revamped user interface with updated staff_index.js for enhanced interactivity and mobile responsiveness. - Improved security measures and data handling in appointment processing. - Added new methods for appointment data representation in dictionary format. - Introduced dynamic labeling feature for appointment pages with app_offered_by_label. - Updated documentation and workflow processes for better clarity and efficiency. - Strengthened community engagement with new community guidelines and issue templates. - Performed crucial library updates and applied security patches. - Refined translation inconsistencies for improved internationalization. - Added endpoint for deleting appointments. - Fixed access issue for staff members without a staff profile in the appointment page. - Various CSS and JavaScript optimizations for a more responsive and user-friendly interface. - Starting to update translations. * Updated translation * Updated tests * Updated tests * Added more tests * Added more tests for admin views
1 parent 3d3ada6 commit 648fa16

38 files changed

+1742
-818
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,4 @@ cython_debug/
177177
**/.DS_Store/**
178178
**/migrations/**
179179
/services/
180+
locale/

appointment/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
__description__ = "Managing appointment scheduling with customizable slots, staff features, and conflict handling."
55
__package_name__ = "django-appointment"
66
__url__ = "https://github.com/adamspd/django-appointment"
7-
__version__ = "2.1.2"
7+
__version__ = "2.1.5"
88
__test_version__ = True

appointment/email_messages.py renamed to appointment/messages_.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
thank_you_payment = _("""We're excited to have you on board! Thank you for booking us. The next step is to pay for
1919
the booking.""")
2020

21+
appt_updated_successfully = _("Appointment updated successfully.")

appointment/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ def is_paid(self):
458458
return True
459459
return self.paid
460460

461+
def is_paid_txt(self):
462+
return _("Yes") if self.is_paid() else _("No")
463+
461464
def get_appointment_amount_to_pay(self):
462465
# Check if the decimal part is 0
463466
if self.amount_to_pay % 1 == 0:
@@ -526,6 +529,22 @@ def is_valid_date(appt_date, start_time, staff_member, current_appointment_id, w
526529
def is_owner(self, staff_user_id):
527530
return self.appointment_request.staff_member.user.id == staff_user_id
528531

532+
def to_dict(self):
533+
return {
534+
"id": self.id,
535+
"client_name": self.client.get_full_name(),
536+
"client_email": self.client.email,
537+
"start_time": self.appointment_request.start_time.strftime('%Y-%m-%d %H:%M'),
538+
"end_time": self.appointment_request.end_time.strftime('%Y-%m-%d %H:%M'),
539+
"service_name": self.appointment_request.service.name,
540+
"address": self.address,
541+
"want_reminder": self.want_reminder,
542+
"additional_info": self.additional_info,
543+
"paid": self.paid,
544+
"amount_to_pay": self.amount_to_pay,
545+
"id_request": self.id_request,
546+
}
547+
529548

530549
class Config(models.Model):
531550
"""

appointment/services.py

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616
from django.utils.translation import gettext as _, gettext_lazy as _
1717

1818
from appointment.forms import PersonalInformationForm, ServiceForm, StaffDaysOffForm, StaffWorkingHoursForm
19+
from appointment.messages_ import appt_updated_successfully
1920
from appointment.settings import APPOINTMENT_PAYMENT_URL
20-
from appointment.utils.date_time import convert_12_hour_time_to_24_hour_time, convert_str_to_time, get_ar_end_time
21-
from appointment.utils.db_helpers import Appointment, EmailVerificationCode, Service, StaffMember, WorkingHours, \
22-
calculate_slots, calculate_staff_slots, check_day_off_for_staff, create_new_user, day_off_exists_for_date_range, \
23-
exclude_booked_slots, get_all_appointments, get_all_staff_members, get_appointment_by_id, \
24-
get_appointments_for_date_and_time, get_staff_member_appointment_list, get_staff_member_from_user_id_or_logged_in, \
25-
get_times_from_config, get_user_by_email, get_working_hours_for_staff_and_day, working_hours_exist
21+
from appointment.utils.date_time import (
22+
convert_12_hour_time_to_24_hour_time, convert_str_to_date, convert_str_to_time, get_ar_end_time)
23+
from appointment.utils.db_helpers import (
24+
Appointment, AppointmentRequest, EmailVerificationCode, Service, StaffMember, WorkingHours, calculate_slots,
25+
calculate_staff_slots, check_day_off_for_staff, create_and_save_appointment, create_new_user,
26+
day_off_exists_for_date_range, exclude_booked_slots, get_all_appointments, get_all_staff_members,
27+
get_appointment_by_id, get_appointments_for_date_and_time, get_staff_member_appointment_list,
28+
get_staff_member_from_user_id_or_logged_in, get_times_from_config, get_user_by_email,
29+
get_working_hours_for_staff_and_day, working_hours_exist)
2630
from appointment.utils.error_codes import ErrorCode
27-
from appointment.utils.json_context import get_generic_context, json_response
31+
from appointment.utils.json_context import convert_appointment_to_json, get_generic_context, json_response
2832
from appointment.utils.permissions import check_entity_ownership
2933
from appointment.utils.session import handle_email_change
3034

@@ -65,7 +69,7 @@ def prepare_appointment_display_data(user, appointment_id):
6569
return None, None, _("You are not authorized to view this appointment."), 403
6670

6771
# Prepare the data for display
68-
page_title = _("Appointment details: {client_name}").format(client_name=appointment.get_client_name())
72+
page_title = _("Appointment details") + _(": {client_name}").format(client_name=appointment.get_client_name())
6973
if user.is_superuser:
7074
page_title += f' (by: {appointment.get_staff_member_name()})'
7175

@@ -300,16 +304,9 @@ def get_working_hours_and_days_off_context(request, btn_txt, form_name, form, us
300304
return context
301305

302306

303-
def save_appointment(appt, client_name, client_email, start_time, phone_number, client_address, service_id):
307+
def save_appointment(appt, client_name, client_email, start_time, phone_number, client_address, service_id,
308+
want_reminder=False, additional_info=None):
304309
"""Save an appointment's details.
305-
306-
:param appt: The appointment to modify.
307-
:param client_name: The name of the client.
308-
:param client_email: The email of the client.
309-
:param start_time: The start time of the appointment.
310-
:param phone_number: The phone number of the client.
311-
:param client_address: The address of the client.
312-
:param service_id: The ID of the service.
313310
:return: The modified appointment.
314311
"""
315312
# Modify and save client details
@@ -335,6 +332,8 @@ def save_appointment(appt, client_name, client_email, start_time, phone_number,
335332
# Modify and save appointment details
336333
appt.phone = phone_number
337334
appt.address = client_address
335+
appt.want_reminder = want_reminder
336+
appt.additional_info = additional_info
338337
appt.save()
339338
return appt
340339

@@ -536,3 +535,81 @@ def handle_service_management_request(post_data, files_data=None, service_id=Non
536535
return None, False, get_error_message_in_form(form=form)
537536
except Exception as e:
538537
return None, False, str(e)
538+
539+
540+
def create_new_appointment(data, request):
541+
service = Service.objects.get(id=data.get("service_id"))
542+
staff_member = StaffMember.objects.get(user=request.user)
543+
544+
# Convert date and start time strings to datetime objects
545+
date = convert_str_to_date(data.get("date"))
546+
start_time = convert_str_to_time(data.get("start_time"))
547+
start_datetime = datetime.datetime.combine(date, start_time)
548+
549+
appointment_request = AppointmentRequest(
550+
date=start_datetime.date(),
551+
start_time=start_datetime.time(),
552+
end_time=(start_datetime + service.duration).time(),
553+
service=service,
554+
staff_member=staff_member,
555+
)
556+
appointment_request.full_clean() # Validates the model
557+
appointment_request.save()
558+
559+
# Prepare client data
560+
email = data.get("client_email", "")
561+
name_parts = data.get("client_name", "").split()
562+
first_name = name_parts[0] if name_parts else ""
563+
last_name = name_parts[-1] if len(name_parts) > 1 else "" # Use an empty string if no last name
564+
565+
client_data = {
566+
'email': email,
567+
'first_name': first_name,
568+
'last_name': last_name,
569+
}
570+
571+
# Use your custom user creation logic
572+
user = get_user_by_email(email)
573+
if not user:
574+
create_new_user(client_data)
575+
576+
# Create and save the new appointment
577+
appointment_data = {
578+
'phone': data.get("client_phone", ""),
579+
'address': data.get("client_address", ""),
580+
'want_reminder': data.get("want_reminder") == 'true',
581+
'additional_info': data.get("additional_info", ""),
582+
'paid': False
583+
}
584+
appointment = create_and_save_appointment(appointment_request, client_data, appointment_data)
585+
appointment_list = convert_appointment_to_json(request, [appointment])
586+
587+
return json_response("Appointment created successfully.", custom_data={'appt': appointment_list})
588+
589+
590+
def update_existing_appointment(data, request):
591+
try:
592+
appt = Appointment.objects.get(id=data.get("appointment_id"))
593+
print(f"want_reminder: {data.get('want_reminder')}")
594+
want_reminder = data.get("want_reminder") == 'true'
595+
appt = save_appointment(
596+
appt,
597+
client_name=data.get("client_name"),
598+
client_email=data.get("client_email"),
599+
start_time=data.get("start_time"),
600+
phone_number=data.get("client_phone"),
601+
client_address=data.get("client_address"),
602+
service_id=data.get("service_id"),
603+
want_reminder=want_reminder,
604+
additional_info=data.get("additional_info")
605+
)
606+
appointments_json = convert_appointment_to_json(request, [appt])[0]
607+
return json_response(appt_updated_successfully, custom_data={'appt': appointments_json})
608+
except Appointment.DoesNotExist:
609+
return json_response("Appointment does not exist.", status=404, success=False,
610+
error_code=ErrorCode.APPOINTMENT_NOT_FOUND)
611+
except Service.DoesNotExist:
612+
return json_response("Service does not exist.", status=404, success=False,
613+
error_code=ErrorCode.SERVICE_NOT_FOUND)
614+
except Exception as e:
615+
return json_response(str(e.args[0]), status=400, success=False)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* Hide scrollbar for day grid month view on small screens */
2+
3+
@media (max-width: 500px) {
4+
.djangoAppt_no-events {
5+
margin-bottom: 10px;
6+
}
7+
8+
.djangoAppt_btn-new-event {
9+
padding: 5px 8px !important;
10+
font-size: 13px !important;
11+
}
12+
13+
.fc-dayGridMonth-view .fc-scroller {
14+
overflow: hidden !important;
15+
}
16+
17+
.modal-content {
18+
margin: 0 auto !important;
19+
}
20+
21+
.modal-footer {
22+
text-align: left !important;
23+
flex-wrap: inherit !important;
24+
justify-content: center !important;
25+
align-content: flex-start;
26+
}
27+
28+
#eventDetailsModal .btn {
29+
margin-right: 2px !important;
30+
font-size: 13px !important;
31+
}
32+
33+
#eventModalBody, #serviceSelect {
34+
font-size: 13px !important;
35+
}
36+
37+
#eventModalLabel {
38+
font-size: 15px !important;
39+
}
40+
}
41+
42+
/* Hide scrollbar for time grid day view on larger screens */
43+
@media (min-width: 450px) {
44+
.fc-timeGridDay-view .fc-scroller {
45+
overflow: hidden !important;
46+
}
47+
}
48+
49+
@media (min-width: 600px) {
50+
.fc-scroller {
51+
overflow: hidden !important;
52+
}
53+
}

0 commit comments

Comments
 (0)