From 9998b31ab495246daae1a411e5b414ce5b991ba7 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:16:18 -0600 Subject: [PATCH] updated backend so booked appointment automatically changes to completed --- .../AppointmentDetailFragment.java | 90 +++++++++++++++++++ .../layout/fragment_appointment_detail.xml | 3 + .../petshop/backend/BackendApplication.java | 2 + .../config/ApplicationStartupListener.java | 22 +++++ .../repository/AppointmentRepository.java | 3 + .../backend/service/AppointmentService.java | 15 ++++ 6 files changed, 135 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java index 86da3ae7..169e0532 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java @@ -37,6 +37,7 @@ public class AppointmentDetailFragment extends Fragment { private long appointmentId = -1; private boolean isEditing = false; + private boolean isPastAppointment = false; private long preselectedPetId = -1; private long preselectedServiceId = -1; private long preselectedCustomerId = -1; @@ -161,6 +162,7 @@ public class AppointmentDetailFragment extends Fragment { */ private void setupDatePicker() { binding.etAppointmentDate.setOnClickListener(v -> { + if (isPastAppointment) return; Calendar c = Calendar.getInstance(); DatePickerDialog d = new DatePickerDialog(requireContext(), (dp,y,m,d1) -> binding.etAppointmentDate.setText( @@ -376,6 +378,8 @@ public class AppointmentDetailFragment extends Fragment { SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, formattedStatus); } + checkIfPastAndDisable(a.getAppointmentDate(), time); + refreshPetSpinner(); refreshServiceSpinner(); refreshCustomerSpinner(); @@ -387,6 +391,92 @@ public class AppointmentDetailFragment extends Fragment { }); } + /** + * Checks if the appointment is in the past and disables fields. + */ + private void checkIfPastAndDisable(String date, String time) { + if (date == null || time == null) return; + try { + Calendar selected = Calendar.getInstance(); + String[] dateParts = date.split("-"); + String[] timeParts = time.split(":"); + selected.set( + Integer.parseInt(dateParts[0]), + Integer.parseInt(dateParts[1]) - 1, + Integer.parseInt(dateParts[2]), + Integer.parseInt(timeParts[0]), + Integer.parseInt(timeParts[1]), + 0 + ); + + Object selectedItem = binding.spinnerAppointmentStatus.getSelectedItem(); + String currentStatus = selectedItem != null ? selectedItem.toString() : ""; + + // If the appointment is already Cancelled, disable all fields + if ("Cancelled".equalsIgnoreCase(currentStatus)) { + isPastAppointment = true; + disableAllExceptStatus(); + binding.spinnerAppointmentStatus.setEnabled(false); + binding.spinnerAppointmentStatus.setAlpha(0.5f); + binding.btnSaveAppointment.setVisibility(View.GONE); + return; + } + + // If the appointment date/time is in the past + if (selected.before(Calendar.getInstance())) { + isPastAppointment = true; + disableAllExceptStatus(); + + // Make status spinner only have Completed or Missed + SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, + new String[]{"Completed", "Missed"}); + + // Restore selection if it's already one of the valid options + if (currentStatus.equals("Completed") || currentStatus.equals("Missed")) { + SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, currentStatus); + } + } + } catch (Exception e) { + Log.e("APPT_DETAIL", "Error parsing date/time for past check: " + e.getMessage()); + } + } + + /** + * Disables all input fields except the status spinner + */ + private void disableAllExceptStatus() { + binding.spinnerCustomer.setEnabled(false); + binding.spinnerStore.setEnabled(false); + binding.spinnerPet.setEnabled(false); + binding.spinnerService.setEnabled(false); + binding.spinnerStaff.setEnabled(false); + binding.etAppointmentDate.setEnabled(false); + binding.spinnerHour.setEnabled(false); + binding.spinnerMinute.setEnabled(false); + + float disabledAlpha = 0.5f; + binding.spinnerCustomer.setAlpha(disabledAlpha); + binding.spinnerStore.setAlpha(disabledAlpha); + binding.spinnerPet.setAlpha(disabledAlpha); + binding.spinnerService.setAlpha(disabledAlpha); + binding.spinnerStaff.setAlpha(disabledAlpha); + binding.etAppointmentDate.setAlpha(disabledAlpha); + binding.spinnerHour.setAlpha(disabledAlpha); + binding.spinnerMinute.setAlpha(disabledAlpha); + + binding.tvLabelCustomer.setAlpha(disabledAlpha); + binding.tvLabelStore.setAlpha(disabledAlpha); + binding.tvLabelPet.setAlpha(disabledAlpha); + binding.tvLabelService.setAlpha(disabledAlpha); + binding.tvLabelStaff.setAlpha(disabledAlpha); + binding.tvLabelDate.setAlpha(disabledAlpha); + binding.tvLabelTime.setAlpha(disabledAlpha); + + // Keep status enabled + binding.spinnerAppointmentStatus.setEnabled(true); + binding.spinnerAppointmentStatus.setAlpha(1.0f); + } + /** * Validates input and saves the appointment to the backend. */ diff --git a/android/app/src/main/res/layout/fragment_appointment_detail.xml b/android/app/src/main/res/layout/fragment_appointment_detail.xml index f8a87fa0..4248311c 100644 --- a/android/app/src/main/res/layout/fragment_appointment_detail.xml +++ b/android/app/src/main/res/layout/fragment_appointment_detail.xml @@ -136,6 +136,7 @@ { + + private final AppointmentService appointmentService; + + public ApplicationStartupListener(AppointmentService appointmentService) { + this.appointmentService = appointmentService; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + //update booked appointments to complete on startup + appointmentService.updatePastAppointmentsStatus(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index dc7d40ef..9da6efdd 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -47,4 +47,7 @@ public interface AppointmentRepository extends JpaRepository @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.id IN :employeeIds AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") List findByEmployeeIdInAndAppointmentDate(@Param("employeeIds") List employeeIds, @Param("date") LocalDate date); + + @Query("SELECT a FROM Appointment a WHERE (a.appointmentDate < :currentDate OR (a.appointmentDate = :currentDate AND a.appointmentTime < :currentTime)) AND LOWER(a.appointmentStatus) = 'booked'") + List findPastBookedAppointments(@Param("currentDate") LocalDate currentDate, @Param("currentTime") LocalTime currentTime); } diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java index 1a32d2aa..26e0a512 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -16,6 +16,7 @@ import com.petshop.backend.repository.UserRepository; import com.petshop.backend.util.AuthenticationHelper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -209,6 +210,20 @@ public class AppointmentService { return availableSlots; } + //Update booked status to completed at every midnight + @Scheduled(cron = "0 0 0 * * ?") + @Transactional + public void updatePastAppointmentsStatus() { + LocalDate currentDate = LocalDate.now(); + LocalTime currentTime = LocalTime.now(); + List pastBookedAppointments = appointmentRepository.findPastBookedAppointments(currentDate, currentTime); + + for (Appointment appointment : pastBookedAppointments) { + appointment.setAppointmentStatus("COMPLETED"); + appointmentRepository.save(appointment); + } + } + private String normalizeFilter(String value) { if (value == null) { return null;