将新的预订添加到预订列表中,每个预订都有两个 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 日

  1. 关于如何修复此谓词并仍然使用 Java Streams 的任何建议?

  2. 或者有没有其他方法(也许是更好的方法)来完成这种"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)));
}

现在这将检​​查现有预订中是否既没有入住也没有退房,以及现有预订中的入住和退房是否不完全相同。