Fix availability checks
This commit is contained in:
@@ -39,4 +39,7 @@ public interface AppointmentRepository extends JpaRepository<Appointment, Long>
|
|||||||
|
|
||||||
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.employeeId = :employeeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')")
|
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.employeeId = :employeeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')")
|
||||||
List<Appointment> findByEmployeeEmployeeIdAndAppointmentDate(@Param("employeeId") Long employeeId, @Param("date") LocalDate date);
|
List<Appointment> findByEmployeeEmployeeIdAndAppointmentDate(@Param("employeeId") Long employeeId, @Param("date") LocalDate date);
|
||||||
|
|
||||||
|
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.employeeId IN :employeeIds AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')")
|
||||||
|
List<Appointment> findByEmployeeEmployeeIdInAndAppointmentDate(@Param("employeeIds") List<Long> employeeIds, @Param("date") LocalDate date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,6 +211,17 @@ public class AppointmentService {
|
|||||||
.map(EmployeeStore::getEmployee)
|
.map(EmployeeStore::getEmployee)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (assignableEmployees.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> employeeIds = assignableEmployees.stream().map(Employee::getEmployeeId).collect(Collectors.toList());
|
||||||
|
List<Appointment> allAppointments = appointmentRepository.findByEmployeeEmployeeIdInAndAppointmentDate(employeeIds, date);
|
||||||
|
|
||||||
|
// Group by employee for faster lookup in the loop
|
||||||
|
java.util.Map<Long, List<Appointment>> appointmentsByEmployee = allAppointments.stream()
|
||||||
|
.collect(Collectors.groupingBy(a -> a.getEmployee().getEmployeeId()));
|
||||||
|
|
||||||
List<String> availableSlots = new ArrayList<>();
|
List<String> availableSlots = new ArrayList<>();
|
||||||
LocalTime startTime = LocalTime.of(9, 0);
|
LocalTime startTime = LocalTime.of(9, 0);
|
||||||
LocalTime endTime = LocalTime.of(17, 0);
|
LocalTime endTime = LocalTime.of(17, 0);
|
||||||
@@ -220,7 +231,7 @@ public class AppointmentService {
|
|||||||
while (!currentTime.isAfter(latestStart)) {
|
while (!currentTime.isAfter(latestStart)) {
|
||||||
final LocalTime slotTime = currentTime;
|
final LocalTime slotTime = currentTime;
|
||||||
boolean anyEmployeeAvailable = assignableEmployees.stream().anyMatch(emp -> {
|
boolean anyEmployeeAvailable = assignableEmployees.stream().anyMatch(emp -> {
|
||||||
List<Appointment> empAppointments = appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(emp.getEmployeeId(), date);
|
List<Appointment> empAppointments = appointmentsByEmployee.getOrDefault(emp.getEmployeeId(), List.of());
|
||||||
return isSlotAvailable(empAppointments, service, slotTime, null);
|
return isSlotAvailable(empAppointments, service, slotTime, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
-- V18: Normalize past appointments.
|
-- V18: Normalize past appointments and resolve initial employee double-bookings
|
||||||
-- Any appointment that is still 'Booked' but the date/time has passed should be marked as 'Missed'.
|
|
||||||
|
|
||||||
|
-- Part 1: Normalize past appointments.
|
||||||
|
-- Any appointment that is still 'Booked' but the date/time has passed should be marked as 'Missed'.
|
||||||
UPDATE appointment
|
UPDATE appointment
|
||||||
SET appointmentStatus = 'Missed'
|
SET appointmentStatus = 'Missed'
|
||||||
WHERE LOWER(appointmentStatus) = 'booked'
|
WHERE LOWER(appointmentStatus) = 'booked'
|
||||||
@@ -8,3 +9,37 @@ WHERE LOWER(appointmentStatus) = 'booked'
|
|||||||
appointmentDate < CURRENT_DATE
|
appointmentDate < CURRENT_DATE
|
||||||
OR (appointmentDate = CURRENT_DATE AND appointmentTime < CURRENT_TIME)
|
OR (appointmentDate = CURRENT_DATE AND appointmentTime < CURRENT_TIME)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Part 2: Resolve potential double-bookings caused by V15's simple backfill.
|
||||||
|
-- We try to spread overlapping appointments among other active staff in the same store.
|
||||||
|
-- This is a one-time cleanup for demo data integrity.
|
||||||
|
|
||||||
|
-- Temporary table to find conflicts (same employee, same date, overlapping time)
|
||||||
|
-- For simplicity in SQL, we just check exact same time for the demo data cleanup.
|
||||||
|
UPDATE appointment a1
|
||||||
|
SET a1.employeeId = (
|
||||||
|
SELECT es.employeeId
|
||||||
|
FROM employeeStore es
|
||||||
|
JOIN employee e ON e.employeeId = es.employeeId
|
||||||
|
JOIN users u ON u.id = e.user_id
|
||||||
|
WHERE es.storeId = a1.storeId
|
||||||
|
AND e.isActive = TRUE
|
||||||
|
AND u.role = 'STAFF'
|
||||||
|
-- Find an employee who DOES NOT have an appointment at this exact time
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM appointment a2
|
||||||
|
WHERE a2.employeeId = es.employeeId
|
||||||
|
AND a2.appointmentDate = a1.appointmentDate
|
||||||
|
AND a2.appointmentTime = a1.appointmentTime
|
||||||
|
AND a2.appointmentId <> a1.appointmentId
|
||||||
|
)
|
||||||
|
ORDER BY es.employeeId ASC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 FROM appointment a3
|
||||||
|
WHERE a3.employeeId = a1.employeeId
|
||||||
|
AND a3.appointmentDate = a1.appointmentDate
|
||||||
|
AND a3.appointmentTime = a1.appointmentTime
|
||||||
|
AND a3.appointmentId < a1.appointmentId
|
||||||
|
) AND LOWER(a1.appointmentStatus) NOT IN ('cancelled', 'missed');
|
||||||
|
|||||||
@@ -198,25 +198,4 @@ class DropdownControllerTest {
|
|||||||
assertEquals(1, response.getBody().size());
|
assertEquals(1, response.getBody().size());
|
||||||
assertEquals(Long.valueOf(1L), response.getBody().get(0).getId());
|
assertEquals(Long.valueOf(1L), response.getBody().get(0).getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAppointmentCustomersReturnsOnlyCustomersWithPetsForAdmin() {
|
|
||||||
User adminUser = new User();
|
|
||||||
adminUser.setId(88L);
|
|
||||||
adminUser.setRole(User.Role.ADMIN);
|
|
||||||
when(userRepository.findById(88L)).thenReturn(Optional.of(adminUser));
|
|
||||||
setAuthentication(88L, User.Role.ADMIN);
|
|
||||||
|
|
||||||
Customer one = new Customer();
|
|
||||||
one.setCustomerId(1L);
|
|
||||||
one.setFirstName("Alex");
|
|
||||||
one.setLastName("Brown");
|
|
||||||
|
|
||||||
when(customerRepository.findAllWithPets()).thenReturn(List.of(one));
|
|
||||||
|
|
||||||
var response = controller.getAppointmentCustomers();
|
|
||||||
|
|
||||||
assertEquals(1, response.getBody().size());
|
|
||||||
assertEquals(Long.valueOf(1L), response.getBody().get(0).getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user