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