Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
public enum RentalStatus {
RENTED,
RETURNED,
RETURN_IN_PROGRESS,
PARTIALLY_RETURNED
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
package edu.zut.bookrider.repository;

import edu.zut.bookrider.model.Rental;
import edu.zut.bookrider.model.RentalReturnItem;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface RentalReturnItemRepository extends JpaRepository<RentalReturnItem, Integer> {
boolean existsByRental(Rental rental);
@Query("""
SELECT COALESCE(SUM(rri.returnedQuantity), 0)
FROM RentalReturnItem rri
WHERE rri.rental.id = :rentalId
""")
int sumReturnedQuantityByRentalId(@Param("rentalId") Integer rentalId);

@Query("""
SELECT rri.rental.id, SUM(rri.returnedQuantity)
FROM RentalReturnItem rri
WHERE rri.rental.id IN :rentalIds
GROUP BY rri.rental.id
""")
List<Object[]> sumReturnedQuantitiesByRentalIds(@Param("rentalIds") List<Integer> rentalIds);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package edu.zut.bookrider.service;

import edu.zut.bookrider.dto.RentalWithQuantityDTO;
import edu.zut.bookrider.exception.RentalAlreadyReturnedException;
import edu.zut.bookrider.model.Rental;
import edu.zut.bookrider.model.RentalReturn;
import edu.zut.bookrider.model.RentalReturnItem;
import edu.zut.bookrider.repository.RentalReturnItemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RequiredArgsConstructor
Expand All @@ -18,20 +21,45 @@ public class RentalReturnItemService {
private final RentalReturnItemRepository rentalReturnItemRepository;

public List<RentalReturnItem> createRentalReturnItems(RentalReturn rentalReturn, List<RentalWithQuantityDTO> rentals) {
List<RentalReturnItem> returnItems = rentals.stream().map(rentalDto -> {
List<Integer> rentalIds = rentals.stream()
.map(r -> r.getRental().getId())
.toList();

List<Object[]> results = rentalReturnItemRepository.sumReturnedQuantitiesByRentalIds(rentalIds);

Map<Integer, Integer> returnedQuantities = results.stream()
.collect(Collectors.toMap(
row -> (Integer) row[0],
row -> ((Number) row[1]).intValue()
));

List<RentalReturnItem> returnItems = new ArrayList<>();

for (RentalWithQuantityDTO dto : rentals) {
Rental rental = dto.getRental();
int alreadyReturned = returnedQuantities.getOrDefault(rental.getId(), 0);
int remaining = rental.getQuantity() - alreadyReturned;
int toReturn = dto.getQuantityToReturn();

if (toReturn < 1 || toReturn > remaining) {
throw new RentalAlreadyReturnedException("Invalid return quantity for rental ID " + rental.getId() +
". Requested: " + toReturn + ", Available to return: " + remaining);
}

RentalReturnItem returnItem = new RentalReturnItem();
returnItem.setRentalReturn(rentalReturn);
returnItem.setRental(rentalDto.getRental());
returnItem.setBook(rentalDto.getRental().getBook());
returnItem.setReturnedQuantity(rentalDto.getQuantityToReturn());
return returnItem;
}).collect(Collectors.toList());
returnItem.setRental(rental);
returnItem.setBook(rental.getBook());
returnItem.setReturnedQuantity(toReturn);

returnItems.add(returnItem);
}

rentalReturnItemRepository.saveAll(returnItems);
return returnItems;
}

public boolean isAlreadyReturned(Rental rental) {
return rentalReturnItemRepository.existsByRental(rental);
int sumReturnedQuantityByRentalId(Integer rentalId) {
return rentalReturnItemRepository.sumReturnedQuantityByRentalId(rentalId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,9 @@ public void checkRentals(List<RentalReturnWithQuantityRequestDTO> rentalReturnRe
Integer rentalId = requestDTO.getRentalId();
Rental rental = rentalService.getRentalById(rentalId);

boolean isAlreadyReturned = rentalReturnItemService.isAlreadyReturned(rental);
if (isAlreadyReturned) {
if (rental.getStatus() == RentalStatus.RETURNED) {
throw new RentalAlreadyReturnedException("Rental " + rental.getId() + " has already been returned.");
}

rentalService.updateRentalStatus(rentalId, RentalStatus.RETURN_IN_PROGRESS);
}
}

Expand All @@ -229,7 +226,7 @@ public RentalReturn createRentalReturnForOrder(Order returnOrder, List<RentalWit
RentalReturn savedRentalReturn = rentalReturnRepository.save(rentalReturn);

List<RentalReturnItem> rentalReturnItems = rentalReturnItemService.createRentalReturnItems(savedRentalReturn, rentals);
savedRentalReturn.setRentalReturnItems(rentalReturnItems);
savedRentalReturn.getRentalReturnItems().addAll(rentalReturnItems);

return rentalReturnRepository.save(rentalReturn);
}
Expand Down Expand Up @@ -262,7 +259,7 @@ public List<RentalReturnDTO> createInPersonRentalReturn(InPersonRentalReturnRequ
RentalReturn savedRentalReturn = rentalReturnRepository.save(rentalReturn);

List<RentalReturnItem> rentalReturnItems = rentalReturnItemService.createRentalReturnItems(savedRentalReturn, rentalsForLibrary);
savedRentalReturn.setRentalReturnItems(rentalReturnItems);
savedRentalReturn.getRentalReturnItems().addAll(rentalReturnItems);

BigDecimal totalLateFees = BigDecimal.ZERO;
for (RentalWithQuantityDTO rentalWithQuantityDTO : rentalsForLibrary) {
Expand Down
21 changes: 11 additions & 10 deletions backend/src/main/java/edu/zut/bookrider/service/RentalService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class RentalService {

private final RentalRepository rentalRepository;
private final RentalMapper rentalMapper;
private final RentalReturnItemService rentalReturnItemService;

@Transactional
public void createRental(OrderItem orderItem) {
Expand Down Expand Up @@ -100,9 +101,16 @@ public PageResponseDTO<RentalDTO> getUserRentals(Pageable pageable) {
User user = userService.getUser();
Page<Rental> rentals = rentalRepository.findAllByUser(user, pageable);

List<RentalDTO> rentalDTOs = rentals.getContent()
.stream()
.map(rentalMapper::map)
List<RentalDTO> rentalDTOs = rentals.getContent().stream()
.map(rental -> {
int remainingQuantity = rental.getQuantity();
if (rental.getStatus() == RentalStatus.PARTIALLY_RETURNED) {
int returned = rentalReturnItemService.sumReturnedQuantityByRentalId(rental.getId());
remainingQuantity -= returned;
}
rental.setQuantity(remainingQuantity);
return rentalMapper.map(rental);
})
.toList();

return new PageResponseDTO<>(
Expand All @@ -113,11 +121,4 @@ public PageResponseDTO<RentalDTO> getUserRentals(Pageable pageable) {
rentals.getTotalPages()
);
}

public Rental updateRentalStatus(Integer rentalId, RentalStatus status) {
Rental rental = getRentalById(rentalId);
rental.setStatus(status);

return rentalRepository.save(rental);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import edu.zut.bookrider.model.*;
import edu.zut.bookrider.model.enums.OrderStatus;
import edu.zut.bookrider.model.enums.PaymentStatus;
import edu.zut.bookrider.model.enums.RentalReturnStatus;
import edu.zut.bookrider.model.enums.RentalStatus;
import edu.zut.bookrider.repository.*;
import edu.zut.bookrider.service.UserIdGeneratorService;
Expand Down Expand Up @@ -57,6 +58,8 @@ public class RentalControllerIT {
private RoleRepository roleRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private RentalReturnRepository rentalReturnRepository;

private User user;
private User driver;
Expand Down Expand Up @@ -203,4 +206,45 @@ void whenUserRequestsRentalsWithDifferentStatuses_thenReturnRentalsPage() throws
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(2));
}

@Test
@WithMockUser(username = "testuser@rcit.com", roles = {"user"})
void whenUserRequestsRentalsWithPartialReturns_thenReturnRentalsPageWithCorrectQuantities() throws Exception {

Author author = createAuthor("Author");
Language language = createLanguage("language");

Book book1 = createBook("Book1", "113109022025", language, author);
Book book2 = createBook("Book2", "113209022025", language, author);

Order order1 = createOrder(user, driver, library1, address, book1, 2, OrderStatus.PENDING, false);
Order order2 = createOrder(user, driver, library1, address, book2, 1, OrderStatus.PENDING, false);

createRental(order1, 2, RentalStatus.RENTED);
Rental rental1 = createRental(order2, 3, RentalStatus.PARTIALLY_RETURNED);

Order returnOrder = createOrder(user, driver, library1, address, book2, 2, OrderStatus.AWAITING_LIBRARY_CONFIRMATION, true);

RentalReturn rentalReturn = new RentalReturn();
rentalReturn.setReturnOrder(returnOrder);
rentalReturn.setStatus(RentalReturnStatus.IN_PROGRESS);

RentalReturnItem rentalReturnItem = new RentalReturnItem();
rentalReturnItem.setRentalReturn(rentalReturn);
rentalReturnItem.setRental(rental1);
rentalReturnItem.setBook(rental1.getBook());
rentalReturnItem.setReturnedQuantity(2);

List<RentalReturnItem> rentalReturnItems = new ArrayList<>();
rentalReturnItems.add(rentalReturnItem);
rentalReturn.setRentalReturnItems(rentalReturnItems);

rentalReturn = rentalReturnRepository.save(rentalReturn);

mockMvc.perform(MockMvcRequestBuilders.get("/api/rentals")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(2))
.andExpect(jsonPath("$.content[1].quantity").value(1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ private Rental createRental(Order order, int quantity, boolean addDays) {
return rentalRepository.save(rental);
}

private GeneralRentalReturnRequestDTO createSingleRentalReturnRequest(Address address, Rental rental, int quantityToReturn) {
RentalReturnWithQuantityRequestDTO rentalReturnRequestDTO = new RentalReturnWithQuantityRequestDTO();
rentalReturnRequestDTO.setRentalId(rental.getId());
rentalReturnRequestDTO.setQuantityToReturn(quantityToReturn);

CreateAddressDTO createAddressDTO = new CreateAddressDTO();
createAddressDTO.setStreet(address.getStreet());
createAddressDTO.setCity(address.getCity());
createAddressDTO.setPostalCode(address.getPostalCode());

GeneralRentalReturnRequestDTO request = new GeneralRentalReturnRequestDTO();
request.setCreateAddressDTO(createAddressDTO);
request.setRentalReturnRequests(List.of(rentalReturnRequestDTO));

return request;
}

@BeforeEach
void setUp() {
user = createUser("testuser@rrcit.com", "user", 9999);
Expand Down Expand Up @@ -998,6 +1015,53 @@ void whenUserRequestsReturnOrderForAlreadyReturnedReturn_thenReturnConflict() th
.andExpect(status().isConflict());
}

@Test
@WithMockUser(username = "testuser@rrcit.com:user", roles = {"user"})
void whenUserRequestsReturnOrderPartiallyReturnedReturn_thenReturnOk() throws Exception {

Author author = createAuthor("Author");
Language language = createLanguage("language");

Book book = createBook("Book1", "113109022025", language, author);

Order deliveryOrder = createOrder(user, driver, library1, address, book, 3, OrderStatus.DELIVERED, false);

Rental rental = new Rental();
rental.setUser(user);
rental.setBook(deliveryOrder.getOrderItems().get(0).getBook());
rental.setLibrary(deliveryOrder.getLibrary());
rental.setOrder(deliveryOrder);
rental.setQuantity(3);
rental.setReturnDeadline(LocalDateTime.now().plusDays(30));
rental.setStatus(RentalStatus.RENTED);
rental = rentalRepository.save(rental);

Order returnOrder = createOrder(user, driver, library1, address, book, 1, OrderStatus.DRIVER_ACCEPTED, true);

RentalReturn rentalReturn = new RentalReturn();
rentalReturn.setReturnOrder(returnOrder);
rentalReturn.setStatus(RentalReturnStatus.IN_PROGRESS);

RentalReturnItem rentalReturnItem = new RentalReturnItem();
rentalReturnItem.setRentalReturn(rentalReturn);
rentalReturnItem.setRental(rental);
rentalReturnItem.setBook(rental.getBook());
rentalReturnItem.setReturnedQuantity(1);

List<RentalReturnItem> rentalReturnItems = new ArrayList<>();
rentalReturnItems.add(rentalReturnItem);
rentalReturn.setRentalReturnItems(rentalReturnItems);

rentalReturnRepository.save(rentalReturn);

GeneralRentalReturnRequestDTO requestDTO = createSingleRentalReturnRequest(address, rental, 2);

mockMvc.perform(MockMvcRequestBuilders.post("/api/rental-returns")
.content(new ObjectMapper().writeValueAsString(requestDTO))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

@Test
@WithMockUser(username = "librarianRrcit:1", roles = {"librarian"})
void whenLibrarianRequestsDeliveryReturnCompletionFromWrongLibrary_thenReturnConflict() throws Exception {
Expand Down