feat: implement waitlist promotion service
This commit is contained in:
1 parent
84dabf7252
commit
959b400f5a
3 files changed
+120
No files matched your search
@@ -0,0 +1,80 @@
|
|||||||
|
package com.slotline.reservation;
|
||||||
|
|
||||||
|
import com.slotline.common.InvalidReservationException;
|
||||||
|
import com.slotline.notification.NotificationMessage;
|
||||||
|
import com.slotline.notification.NotificationService;
|
||||||
|
import com.slotline.notification.NotificationType;
|
||||||
|
import com.slotline.reservation.dto.WaitlistRequest;
|
||||||
|
import com.slotline.reservation.dto.WaitlistResponse;
|
||||||
|
import com.slotline.resource.Resource;
|
||||||
|
import com.slotline.resource.ResourceService;
|
||||||
|
import com.slotline.security.AuthenticatedUser;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class WaitlistService {
|
||||||
|
|
||||||
|
private final WaitlistRepository waitlist;
|
||||||
|
private final ReservationRepository reservations;
|
||||||
|
private final ResourceService resources;
|
||||||
|
private final NotificationService notifications;
|
||||||
|
|
||||||
|
public WaitlistService(WaitlistRepository waitlist, ReservationRepository reservations, ResourceService resources,
|
||||||
|
NotificationService notifications) {
|
||||||
|
this.waitlist = waitlist;
|
||||||
|
this.reservations = reservations;
|
||||||
|
this.resources = resources;
|
||||||
|
this.notifications = notifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public WaitlistResponse join(AuthenticatedUser actor, WaitlistRequest request) {
|
||||||
|
if (!request.startTime().isBefore(request.endTime())) {
|
||||||
|
throw new InvalidReservationException("startTime must be strictly before endTime");
|
||||||
|
}
|
||||||
|
Resource resource = resources.require(actor.organizationId(), request.resourceId());
|
||||||
|
WaitlistEntry entry = new WaitlistEntry(resource.getId(), resource.getOrganizationId(), actor.id(),
|
||||||
|
request.startTime(), request.endTime(), Instant.now());
|
||||||
|
return WaitlistResponse.from(waitlist.save(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<WaitlistResponse> listForUser(Long userId) {
|
||||||
|
return waitlist.findByUserIdOrderByCreatedAtDesc(userId).stream()
|
||||||
|
.map(WaitlistResponse::from)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void promoteAfterCancellation(Reservation cancelled) {
|
||||||
|
Resource resource = cancelled.getResource();
|
||||||
|
List<WaitlistEntry> candidates =
|
||||||
|
waitlist.findWaitingOverlaps(resource.getId(), cancelled.getStartTime(), cancelled.getEndTime());
|
||||||
|
for (WaitlistEntry candidate : candidates) {
|
||||||
|
boolean stillFree = reservations
|
||||||
|
.findActiveOverlaps(resource.getId(), candidate.getStartTime(), candidate.getEndTime())
|
||||||
|
.isEmpty();
|
||||||
|
if (!stillFree) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Reservation promoted = new Reservation(resource, candidate.getUserId(), candidate.getStartTime(),
|
||||||
|
candidate.getEndTime(), Instant.now());
|
||||||
|
reservations.save(promoted);
|
||||||
|
candidate.promote(promoted.getId());
|
||||||
|
notifications.dispatch(new NotificationMessage(
|
||||||
|
NotificationType.WAITLIST_PROMOTED,
|
||||||
|
resource.getOrganizationId(),
|
||||||
|
candidate.getUserId(),
|
||||||
|
resource.getId(),
|
||||||
|
resource.getName(),
|
||||||
|
promoted.getStartTime(),
|
||||||
|
promoted.getEndTime(),
|
||||||
|
"A waitlisted slot became available and was booked for you"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.slotline.reservation.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public record WaitlistRequest(
|
||||||
|
@NotNull Long resourceId,
|
||||||
|
@NotNull Instant startTime,
|
||||||
|
@NotNull Instant endTime) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.slotline.reservation.dto;
|
||||||
|
|
||||||
|
import com.slotline.reservation.WaitlistEntry;
|
||||||
|
import com.slotline.reservation.WaitlistStatus;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public record WaitlistResponse(
|
||||||
|
Long id,
|
||||||
|
Long resourceId,
|
||||||
|
Long userId,
|
||||||
|
Instant startTime,
|
||||||
|
Instant endTime,
|
||||||
|
WaitlistStatus status,
|
||||||
|
Long promotedReservationId,
|
||||||
|
Instant createdAt) {
|
||||||
|
|
||||||
|
public static WaitlistResponse from(WaitlistEntry entry) {
|
||||||
|
return new WaitlistResponse(
|
||||||
|
entry.getId(),
|
||||||
|
entry.getResourceId(),
|
||||||
|
entry.getUserId(),
|
||||||
|
entry.getStartTime(),
|
||||||
|
entry.getEndTime(),
|
||||||
|
entry.getStatus(),
|
||||||
|
entry.getPromotedReservationId(),
|
||||||
|
entry.getCreatedAt());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user