Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,4 @@ cython_debug/
**/.DS_Store/**
**/migrations/**
/services/
locale/
2 changes: 1 addition & 1 deletion appointment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
__description__ = "Managing appointment scheduling with customizable slots, staff features, and conflict handling."
__package_name__ = "django-appointment"
__url__ = "https://github.com/adamspd/django-appointment"
__version__ = "2.1.2"
__version__ = "2.1.5"
__test_version__ = True
1 change: 1 addition & 0 deletions appointment/email_messages.py → appointment/messages_.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
thank_you_payment = _("""We're excited to have you on board! Thank you for booking us. The next step is to pay for
the booking.""")

appt_updated_successfully = _("Appointment updated successfully.")
19 changes: 19 additions & 0 deletions appointment/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ def is_paid(self):
return True
return self.paid

def is_paid_txt(self):
return _("Yes") if self.is_paid() else _("No")

def get_appointment_amount_to_pay(self):
# Check if the decimal part is 0
if self.amount_to_pay % 1 == 0:
Expand Down Expand Up @@ -526,6 +529,22 @@ def is_valid_date(appt_date, start_time, staff_member, current_appointment_id, w
def is_owner(self, staff_user_id):
return self.appointment_request.staff_member.user.id == staff_user_id

def to_dict(self):
return {
"id": self.id,
"client_name": self.client.get_full_name(),
"client_email": self.client.email,
"start_time": self.appointment_request.start_time.strftime('%Y-%m-%d %H:%M'),
"end_time": self.appointment_request.end_time.strftime('%Y-%m-%d %H:%M'),
"service_name": self.appointment_request.service.name,
"address": self.address,
"want_reminder": self.want_reminder,
"additional_info": self.additional_info,
"paid": self.paid,
"amount_to_pay": self.amount_to_pay,
"id_request": self.id_request,
}


class Config(models.Model):
"""
Expand Down
111 changes: 94 additions & 17 deletions appointment/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@
from django.utils.translation import gettext as _, gettext_lazy as _

from appointment.forms import PersonalInformationForm, ServiceForm, StaffDaysOffForm, StaffWorkingHoursForm
from appointment.messages_ import appt_updated_successfully
from appointment.settings import APPOINTMENT_PAYMENT_URL
from appointment.utils.date_time import convert_12_hour_time_to_24_hour_time, convert_str_to_time, get_ar_end_time
from appointment.utils.db_helpers import Appointment, EmailVerificationCode, Service, StaffMember, WorkingHours, \
calculate_slots, calculate_staff_slots, check_day_off_for_staff, create_new_user, day_off_exists_for_date_range, \
exclude_booked_slots, get_all_appointments, get_all_staff_members, get_appointment_by_id, \
get_appointments_for_date_and_time, get_staff_member_appointment_list, get_staff_member_from_user_id_or_logged_in, \
get_times_from_config, get_user_by_email, get_working_hours_for_staff_and_day, working_hours_exist
from appointment.utils.date_time import (
convert_12_hour_time_to_24_hour_time, convert_str_to_date, convert_str_to_time, get_ar_end_time)
from appointment.utils.db_helpers import (
Appointment, AppointmentRequest, EmailVerificationCode, Service, StaffMember, WorkingHours, calculate_slots,
calculate_staff_slots, check_day_off_for_staff, create_and_save_appointment, create_new_user,
day_off_exists_for_date_range, exclude_booked_slots, get_all_appointments, get_all_staff_members,
get_appointment_by_id, get_appointments_for_date_and_time, get_staff_member_appointment_list,
get_staff_member_from_user_id_or_logged_in, get_times_from_config, get_user_by_email,
get_working_hours_for_staff_and_day, working_hours_exist)
from appointment.utils.error_codes import ErrorCode
from appointment.utils.json_context import get_generic_context, json_response
from appointment.utils.json_context import convert_appointment_to_json, get_generic_context, json_response
from appointment.utils.permissions import check_entity_ownership
from appointment.utils.session import handle_email_change

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

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

Expand Down Expand Up @@ -300,16 +304,9 @@ def get_working_hours_and_days_off_context(request, btn_txt, form_name, form, us
return context


def save_appointment(appt, client_name, client_email, start_time, phone_number, client_address, service_id):
def save_appointment(appt, client_name, client_email, start_time, phone_number, client_address, service_id,
want_reminder=False, additional_info=None):
"""Save an appointment's details.

:param appt: The appointment to modify.
:param client_name: The name of the client.
:param client_email: The email of the client.
:param start_time: The start time of the appointment.
:param phone_number: The phone number of the client.
:param client_address: The address of the client.
:param service_id: The ID of the service.
:return: The modified appointment.
"""
# Modify and save client details
Expand All @@ -335,6 +332,8 @@ def save_appointment(appt, client_name, client_email, start_time, phone_number,
# Modify and save appointment details
appt.phone = phone_number
appt.address = client_address
appt.want_reminder = want_reminder
appt.additional_info = additional_info
appt.save()
return appt

Expand Down Expand Up @@ -536,3 +535,81 @@ def handle_service_management_request(post_data, files_data=None, service_id=Non
return None, False, get_error_message_in_form(form=form)
except Exception as e:
return None, False, str(e)


def create_new_appointment(data, request):
service = Service.objects.get(id=data.get("service_id"))
staff_member = StaffMember.objects.get(user=request.user)

# Convert date and start time strings to datetime objects
date = convert_str_to_date(data.get("date"))
start_time = convert_str_to_time(data.get("start_time"))
start_datetime = datetime.datetime.combine(date, start_time)

appointment_request = AppointmentRequest(
date=start_datetime.date(),
start_time=start_datetime.time(),
end_time=(start_datetime + service.duration).time(),
service=service,
staff_member=staff_member,
)
appointment_request.full_clean() # Validates the model
appointment_request.save()

# Prepare client data
email = data.get("client_email", "")
name_parts = data.get("client_name", "").split()
first_name = name_parts[0] if name_parts else ""
last_name = name_parts[-1] if len(name_parts) > 1 else "" # Use an empty string if no last name

client_data = {
'email': email,
'first_name': first_name,
'last_name': last_name,
}

# Use your custom user creation logic
user = get_user_by_email(email)
if not user:
create_new_user(client_data)

# Create and save the new appointment
appointment_data = {
'phone': data.get("client_phone", ""),
'address': data.get("client_address", ""),
'want_reminder': data.get("want_reminder") == 'true',
'additional_info': data.get("additional_info", ""),
'paid': False
}
appointment = create_and_save_appointment(appointment_request, client_data, appointment_data)
appointment_list = convert_appointment_to_json(request, [appointment])

return json_response("Appointment created successfully.", custom_data={'appt': appointment_list})


def update_existing_appointment(data, request):
try:
appt = Appointment.objects.get(id=data.get("appointment_id"))
print(f"want_reminder: {data.get('want_reminder')}")
want_reminder = data.get("want_reminder") == 'true'
appt = save_appointment(
appt,
client_name=data.get("client_name"),
client_email=data.get("client_email"),
start_time=data.get("start_time"),
phone_number=data.get("client_phone"),
client_address=data.get("client_address"),
service_id=data.get("service_id"),
want_reminder=want_reminder,
additional_info=data.get("additional_info")
)
appointments_json = convert_appointment_to_json(request, [appt])[0]
return json_response(appt_updated_successfully, custom_data={'appt': appointments_json})
except Appointment.DoesNotExist:
return json_response("Appointment does not exist.", status=404, success=False,
error_code=ErrorCode.APPOINTMENT_NOT_FOUND)
except Service.DoesNotExist:
return json_response("Service does not exist.", status=404, success=False,
error_code=ErrorCode.SERVICE_NOT_FOUND)
except Exception as e:
return json_response(str(e.args[0]), status=400, success=False)
53 changes: 53 additions & 0 deletions appointment/static/css/app_admin/admin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Hide scrollbar for day grid month view on small screens */

@media (max-width: 500px) {
.djangoAppt_no-events {
margin-bottom: 10px;
}

.djangoAppt_btn-new-event {
padding: 5px 8px !important;
font-size: 13px !important;
}

.fc-dayGridMonth-view .fc-scroller {
overflow: hidden !important;
}

.modal-content {
margin: 0 auto !important;
}

.modal-footer {
text-align: left !important;
flex-wrap: inherit !important;
justify-content: center !important;
align-content: flex-start;
}

#eventDetailsModal .btn {
margin-right: 2px !important;
font-size: 13px !important;
}

#eventModalBody, #serviceSelect {
font-size: 13px !important;
}

#eventModalLabel {
font-size: 15px !important;
}
}

/* Hide scrollbar for time grid day view on larger screens */
@media (min-width: 450px) {
.fc-timeGridDay-view .fc-scroller {
overflow: hidden !important;
}
}

@media (min-width: 600px) {
.fc-scroller {
overflow: hidden !important;
}
}
Loading