将新的预订添加到预订列表中,每个预订都有两个 LocalDates(签入、签出)
Adding a new Booking to a list of Bookings, each Booking has two LocalDates (checkin, checkout)
所以我正在做一个个人项目,试图学习 Java 8 和 Spring Boot。
我正在开发一个 REST API,您可以在其中查看和预订住宿。
这些是我的模型classes,这是住宿class:
@Data
@Document(collection = "accommodations")
public class Accommodation {
@Id
private String accommodationId;
private Double pricePerNight;
private Integer guests;
private Address address;
private Landlord landlord;
private List<Review> reviews;
private List<Booking> bookings;
private Accommodation() {
this.reviews = new ArrayList<>();
this.bookings = new ArrayList<>();
}
public Accommodation(Double pricePerNight, Integer guests, Address address, Landlord landlord) {
this();
this.pricePerNight = pricePerNight;
this.guests = guests;
this.address = address;
this.landlord = landlord;
}
public Boolean isAvailableBetween(LocalDate checkin, LocalDate checkout) {
// TODO: fix
boolean available = bookings
.stream()
.anyMatch(b ->
(checkin.isAfter(b.getCheckin()) || checkin.isEqual(b.getCheckin())) &&
(checkout.isBefore(b.getCheckout()) || checkout.isEqual(b.getCheckout()))
);
return !available;
}
}
这是预订单class:
@Data
public class Booking {
private String bookingId;
private LocalDate checkin;
private LocalDate checkout;
private LocalDate bookedAt;
private Booking(){
this.bookedAt = LocalDate.now();
this.bookingId = new ObjectId().toString();
}
public Booking(LocalDate checkin, LocalDate checkout) {
this();
this.checkin = checkin;
this.checkout = checkout;
}
}
现在,我一直坚持的,我想完成的是确保您可以将预订添加到住宿的预订列表中,仅当 新预订的签入和签出 LocalDates 不与同一列表中任何其他预订的任何 LocalDates 范围重叠。
TLDR;需要确保您只能在住宿可用的日期预订住宿。
这是我的服务 class 中的方法,它在 HTTP POST 预订请求后由控制器调用:
public BookingDto bookAccommodation(String accommodationId, BookingDto bookingDto) {
// TODO: fix and improve
if (this.isCheckoutDateInvalid(bookingDto)) {
throw new InvalidRequestException("Invalid request.", Arrays.asList("Check Out must be after Check In."));
}
Optional<Accommodation> accommodation = this.accommodationRepository.findById(accommodationId);
accommodation.orElseThrow(() -> new AccommodationNotFoundException(String.format("Accommodation not found for ID: {}", accommodationId)));
return accommodation
.filter(a -> a.isAvailableBetween(bookingDto.getCheckin(), bookingDto.getCheckout()))
.map(a -> bookAccommodationInternal(bookingDto, a))
.orElseThrow(() -> new AccommodationNotAvailableException(String.format("Accommodation already booked for ID: {}", accommodationId)));
}
private Boolean isCheckoutDateInvalid(BookingDto bookingDto) {
return bookingDto.getCheckout().isBefore(bookingDto.getCheckin());
}
private BookingDto bookAccommodationInternal(BookingDto bookingDto, Accommodation accommodation) {
Booking booking = this.accommodationMapper.toBooking(bookingDto);
accommodation.getBookings().add(booking);
log.info(String.format("New booking created for ID: {}", booking.getBookingId()));
this.accommodationRepository.save(accommodation);
BookingDto newBookingDto = new BookingDto(booking);
return this.linkAssembler.addLinksToBooking(accommodation.getAccommodationId(), newBookingDto);
}
这是 BookingDto class:
@Getter
@NoArgsConstructor
public class BookingDto extends ResourceSupport {
private String bookingId;
@NotNull(message = "Check In must not be null.")
@FutureOrPresent(message = "Check In must not be a past date.")
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy")
private LocalDate checkin;
@NotNull(message = "Check Out must not be null.")
@FutureOrPresent(message = "Check Out must not be a past date.")
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy")
private LocalDate checkout;
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy")
private LocalDate bookedAt;
public BookingDto(Booking booking) {
this.bookingId = booking.getBookingId();
this.checkin = booking.getCheckin();
this.checkout = booking.getCheckout();
this.bookedAt = booking.getBookedAt();
}
}
现在您可以从代码中看到,我尝试在住宿 class 拥有的 "isAvailableBetween" 方法中使用 Stream。我使用的谓词在某些情况下有效:
例如,如果住宿(我们称之为 Acc1)的预订列表有两个具有以下 LocalDates 的预订:
预订1
签到:2019 年 10 月 10 日
结帐:2019 年 10 月 31 日
预订2
签到:2019 年 1 月 11 日
结帐:2019 年 9 月 11 日
添加预订3:
签到:12/10/2019
结帐:2019 年 10 月 25 日
不可能。
但是可以(错误地)添加一个 Booking4:
签到:12/10/2019
结帐:2019 年 2 月 11 日
关于如何修复此谓词并仍然使用 Java Streams 的任何建议?
或者有没有其他方法(也许是更好的方法)来完成这种"booking system"?
我也乐于接受有关我共享的其余代码的任何提示。真的什么都行。尽最大努力改进。 :)
提前致谢。
你的错误在这里:
.anyMatch(b ->
(checkin.isAfter(b.getCheckin()) || checkin.isEqual(b.getCheckin())) &&
(checkout.isBefore(b.getCheckout()) || checkout.isEqual(b.getCheckout()))
);
首先,变量名应该是occupied
,而不是available
,这与你检查的相反。其次,我认为正确的条件是(未测试):
.anyMatch(b -> b.getCheckin().isBefore(checkout)
&& b.getCheckout().isAfter(checkin)
);
我喜欢反过来想:如果预订完全在我们提出的预订之前或之后,那么住宿是可用的。因此,如果 b.getCheckin()
在 checkout
上或之后,或者 b.getCheckout()
在 checkin
之前或之后。上面代码中的条件是这个的否定,所以检查预订b
是否与建议的冲突。
将此添加到您的预订中class:
public boolean isDateInsideCheckedIn(LocalDate date) {
return checkin.isBefore(date) && checkout.isAfter(date);
}
将您的 isAvailableBetween
替换为:
public boolean isAvailableBetween(LocalDate checkin, LocalDate checkout) {
return bookings.stream()
.noneMatch(b ->
(b.isDateInsideCheckedIn(checkin) || b.isDateInsideCheckedIn(checkout))
|| (b.getCheckin().equals(checkin) && b.getCheckout().equals(checkout)));
}
现在这将检查现有预订中是否既没有入住也没有退房,以及现有预订中的入住和退房是否不完全相同。
所以我正在做一个个人项目,试图学习 Java 8 和 Spring Boot。 我正在开发一个 REST API,您可以在其中查看和预订住宿。
这些是我的模型classes,这是住宿class:
@Data
@Document(collection = "accommodations")
public class Accommodation {
@Id
private String accommodationId;
private Double pricePerNight;
private Integer guests;
private Address address;
private Landlord landlord;
private List<Review> reviews;
private List<Booking> bookings;
private Accommodation() {
this.reviews = new ArrayList<>();
this.bookings = new ArrayList<>();
}
public Accommodation(Double pricePerNight, Integer guests, Address address, Landlord landlord) {
this();
this.pricePerNight = pricePerNight;
this.guests = guests;
this.address = address;
this.landlord = landlord;
}
public Boolean isAvailableBetween(LocalDate checkin, LocalDate checkout) {
// TODO: fix
boolean available = bookings
.stream()
.anyMatch(b ->
(checkin.isAfter(b.getCheckin()) || checkin.isEqual(b.getCheckin())) &&
(checkout.isBefore(b.getCheckout()) || checkout.isEqual(b.getCheckout()))
);
return !available;
}
}
这是预订单class:
@Data
public class Booking {
private String bookingId;
private LocalDate checkin;
private LocalDate checkout;
private LocalDate bookedAt;
private Booking(){
this.bookedAt = LocalDate.now();
this.bookingId = new ObjectId().toString();
}
public Booking(LocalDate checkin, LocalDate checkout) {
this();
this.checkin = checkin;
this.checkout = checkout;
}
}
现在,我一直坚持的,我想完成的是确保您可以将预订添加到住宿的预订列表中,仅当 新预订的签入和签出 LocalDates 不与同一列表中任何其他预订的任何 LocalDates 范围重叠。
TLDR;需要确保您只能在住宿可用的日期预订住宿。
这是我的服务 class 中的方法,它在 HTTP POST 预订请求后由控制器调用:
public BookingDto bookAccommodation(String accommodationId, BookingDto bookingDto) {
// TODO: fix and improve
if (this.isCheckoutDateInvalid(bookingDto)) {
throw new InvalidRequestException("Invalid request.", Arrays.asList("Check Out must be after Check In."));
}
Optional<Accommodation> accommodation = this.accommodationRepository.findById(accommodationId);
accommodation.orElseThrow(() -> new AccommodationNotFoundException(String.format("Accommodation not found for ID: {}", accommodationId)));
return accommodation
.filter(a -> a.isAvailableBetween(bookingDto.getCheckin(), bookingDto.getCheckout()))
.map(a -> bookAccommodationInternal(bookingDto, a))
.orElseThrow(() -> new AccommodationNotAvailableException(String.format("Accommodation already booked for ID: {}", accommodationId)));
}
private Boolean isCheckoutDateInvalid(BookingDto bookingDto) {
return bookingDto.getCheckout().isBefore(bookingDto.getCheckin());
}
private BookingDto bookAccommodationInternal(BookingDto bookingDto, Accommodation accommodation) {
Booking booking = this.accommodationMapper.toBooking(bookingDto);
accommodation.getBookings().add(booking);
log.info(String.format("New booking created for ID: {}", booking.getBookingId()));
this.accommodationRepository.save(accommodation);
BookingDto newBookingDto = new BookingDto(booking);
return this.linkAssembler.addLinksToBooking(accommodation.getAccommodationId(), newBookingDto);
}
这是 BookingDto class:
@Getter
@NoArgsConstructor
public class BookingDto extends ResourceSupport {
private String bookingId;
@NotNull(message = "Check In must not be null.")
@FutureOrPresent(message = "Check In must not be a past date.")
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy")
private LocalDate checkin;
@NotNull(message = "Check Out must not be null.")
@FutureOrPresent(message = "Check Out must not be a past date.")
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy")
private LocalDate checkout;
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy")
private LocalDate bookedAt;
public BookingDto(Booking booking) {
this.bookingId = booking.getBookingId();
this.checkin = booking.getCheckin();
this.checkout = booking.getCheckout();
this.bookedAt = booking.getBookedAt();
}
}
现在您可以从代码中看到,我尝试在住宿 class 拥有的 "isAvailableBetween" 方法中使用 Stream。我使用的谓词在某些情况下有效:
例如,如果住宿(我们称之为 Acc1)的预订列表有两个具有以下 LocalDates 的预订:
预订1
签到:2019 年 10 月 10 日 结帐:2019 年 10 月 31 日
预订2
签到:2019 年 1 月 11 日 结帐:2019 年 9 月 11 日
添加预订3:
签到:12/10/2019 结帐:2019 年 10 月 25 日
不可能。
但是可以(错误地)添加一个 Booking4:
签到:12/10/2019 结帐:2019 年 2 月 11 日
关于如何修复此谓词并仍然使用 Java Streams 的任何建议?
或者有没有其他方法(也许是更好的方法)来完成这种"booking system"?
我也乐于接受有关我共享的其余代码的任何提示。真的什么都行。尽最大努力改进。 :)
提前致谢。
你的错误在这里:
.anyMatch(b ->
(checkin.isAfter(b.getCheckin()) || checkin.isEqual(b.getCheckin())) &&
(checkout.isBefore(b.getCheckout()) || checkout.isEqual(b.getCheckout()))
);
首先,变量名应该是occupied
,而不是available
,这与你检查的相反。其次,我认为正确的条件是(未测试):
.anyMatch(b -> b.getCheckin().isBefore(checkout)
&& b.getCheckout().isAfter(checkin)
);
我喜欢反过来想:如果预订完全在我们提出的预订之前或之后,那么住宿是可用的。因此,如果 b.getCheckin()
在 checkout
上或之后,或者 b.getCheckout()
在 checkin
之前或之后。上面代码中的条件是这个的否定,所以检查预订b
是否与建议的冲突。
将此添加到您的预订中class:
public boolean isDateInsideCheckedIn(LocalDate date) {
return checkin.isBefore(date) && checkout.isAfter(date);
}
将您的 isAvailableBetween
替换为:
public boolean isAvailableBetween(LocalDate checkin, LocalDate checkout) {
return bookings.stream()
.noneMatch(b ->
(b.isDateInsideCheckedIn(checkin) || b.isDateInsideCheckedIn(checkout))
|| (b.getCheckin().equals(checkin) && b.getCheckout().equals(checkout)));
}
现在这将检查现有预订中是否既没有入住也没有退房,以及现有预订中的入住和退房是否不完全相同。