-
Notifications
You must be signed in to change notification settings - Fork 0
312 feature deletion and deactivation validation of rooms #316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c3972c7
e806566
eea4eda
aeb6953
7d25a32
016597b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package de.muenchen.raumreservierung.booking; | ||
|
|
||
| import de.muenchen.raumreservierung.booking.events.FutureBookingCheckEvent; | ||
| import de.muenchen.raumreservierung.booking.events.RemoveRoomFromBookingsEvent; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.event.EventListener; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class BookingEventListener { | ||
|
|
||
| private final BookingService bookingService; | ||
|
|
||
| @EventListener | ||
| public void onFutureBookingCheck(final FutureBookingCheckEvent event) { | ||
| event.setFutureBookingExists(bookingService.existsFutureBookingForRoom(event.getRoomId())); | ||
| } | ||
|
|
||
| @Transactional | ||
| @EventListener | ||
| public void onRemoveRoomFromBookings(final RemoveRoomFromBookingsEvent event) { | ||
| bookingService.removeRoomFromBookings(event.getRoomId()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package de.muenchen.raumreservierung.booking; | ||
|
|
||
| import de.muenchen.raumreservierung.person.domain.Person; | ||
| import de.muenchen.raumreservierung.room.Room_; | ||
| import jakarta.persistence.criteria.CriteriaBuilder; | ||
| import java.time.OffsetDateTime; | ||
| import java.util.List; | ||
| import java.util.UUID; | ||
| import org.springframework.data.domain.Sort; | ||
| import org.springframework.data.jpa.domain.Specification; | ||
|
|
||
| @SuppressWarnings("PMD.CommentDefaultAccessModifier") | ||
| public final class BookingSpecifications { | ||
|
|
||
| private BookingSpecifications() { | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterForRoomId(final UUID roomId) { | ||
| if (roomId == null) { | ||
| return null; | ||
| } | ||
| return (root, query, cb) -> cb.equal(root.get(Booking_.room).get(Room_.id), roomId); | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterForStart(final OffsetDateTime start) { | ||
| if (start == null) { | ||
| return null; | ||
| } | ||
| return (root, query, cb) -> cb.greaterThanOrEqualTo(root.get(Booking_.schedule).get(ScheduleTemplate_.occupancyStart), start); | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterForEnd(final OffsetDateTime end) { | ||
| if (end == null) { | ||
| return null; | ||
| } | ||
| return (root, query, cb) -> cb.lessThanOrEqualTo(root.get(Booking_.schedule).get(ScheduleTemplate_.occupancyEnd), end); | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterForStatusNotNew() { | ||
| return (root, query, cb) -> cb.notEqual(root.get(Booking_.status), BookingStatus.NEW); | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterForStatus(final List<BookingStatus> status) { | ||
| if (status == null || status.isEmpty()) { | ||
| return null; | ||
| } | ||
| return (root, query, cb) -> root.get(Booking_.status).in(status); | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterForPerson(final Person person) { | ||
| if (person == null || person.getId() == null) { | ||
| return null; | ||
| } | ||
| return (root, query, cb) -> cb.or( | ||
| cb.equal(root.get(Booking_.bookedBy), person), | ||
| cb.equal(root.get(Booking_.bookedFor), person)); | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> withFixedStatusOrder(final Sort.Direction direction) { | ||
| return (root, query, cb) -> { | ||
| CriteriaBuilder.Case<Integer> order = cb.selectCase(); | ||
|
|
||
| for (final BookingStatus value : BookingStatus.values()) { | ||
| order = order.when(cb.equal(root.get(Booking_.status), value), value.getSortOrder()); | ||
| } | ||
|
|
||
| if (query != null) { | ||
| query.orderBy(direction.isAscending() ? cb.asc(order) : cb.desc(order)); | ||
| } | ||
| return null; | ||
| }; | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterForOccupancyEndAfter(final OffsetDateTime now) { | ||
| return (root, query, cb) -> cb.greaterThan(root.get(Booking_.schedule).get(ScheduleTemplate_.occupancyEnd), now); | ||
| } | ||
|
|
||
| static <T extends Booking> Specification<T> filterExcludingStatus(final BookingStatus... status) { | ||
| return (root, query, cb) -> cb.not(root.get(Booking_.status).in(List.of(status))); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package de.muenchen.raumreservierung.booking.events; | ||
|
|
||
| import java.util.UUID; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public class FutureBookingCheckEvent { | ||
| private final UUID roomId; | ||
| @Setter | ||
| private boolean futureBookingExists; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package de.muenchen.raumreservierung.booking.events; | ||
|
|
||
| import java.util.UUID; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public class RemoveRoomFromBookingsEvent { | ||
| private final UUID roomId; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| package de.muenchen.raumreservierung.room; | ||
|
|
||
| import de.muenchen.raumreservierung.booking.BookingService; | ||
| import de.muenchen.raumreservierung.room.dto.RoomDetailsResponseDTO; | ||
| import de.muenchen.raumreservierung.room.dto.RoomListResponseDTO; | ||
| import de.muenchen.raumreservierung.room.dto.RoomMapper; | ||
|
|
@@ -31,6 +32,7 @@ public class RoomController { | |
| private final RoomService roomService; | ||
|
|
||
| private final RoomMapper roomMapper; | ||
| private final BookingService bookingService; | ||
|
|
||
| @Transactional | ||
| @GetMapping | ||
|
|
@@ -71,4 +73,17 @@ public RoomDetailsResponseDTO updateRoom(@Valid @RequestBody final RoomRequestDT | |
| public void deleteRoom(@Valid @PathVariable("roomId") final UUID roomId) { | ||
| roomService.deleteRoom(roomId); | ||
| } | ||
|
|
||
| /** | ||
| * Check whether a room can be deleted. | ||
| * Returns false if the room is still referenced in a future booking. | ||
| * | ||
| * @param roomId the UUID of the room to check | ||
| * @return true if the room can be safely deleted, false otherwise | ||
| */ | ||
| @GetMapping("/{roomId}/deletable") | ||
| @ResponseStatus(HttpStatus.OK) | ||
| public boolean isRoomDeletable(@PathVariable final UUID roomId) { | ||
| return !roomService.existsFutureBookingForRoom(roomId); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Check all deletion requirements before returning An active room with no future bookings returns
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Check future appointment occurrences.
filterForOccupancyEndAfterreads onlyBooking.schedule. A recurring booking can have a past booking schedule and anAppointmentthat ends in the future. This predicate then returns no match, so room deletion can detach a room that still has a future occurrence. Query the associated appointment schedules for future occupancy and add a recurring-series integration test.🤖 Prompt for AI Agents