Commit 42ed3f88 authored by Ondřej Pavlica's avatar Ondřej Pavlica
Browse files

Merge branch 'feature/dudik/mvc-user-orders' into 'main'

Feature/dudik/mvc user orders

See merge request !42
parents c73514eb ac79ea19
Pipeline #140843 passed with stages
in 2 minutes and 21 seconds
......@@ -32,6 +32,12 @@ public class OrderItemDto extends PersistentDtoBase {
*/
private long bottleId;
/**
* The ID of the order
*/
@NotNull
private long orderId;
/**
* The ordered wine bottle. Can be null if not explicitly loaded.
*/
......
......@@ -22,6 +22,14 @@ public interface DaoBase<ENTITY extends EntityBase> {
*/
ENTITY get(long id);
/***
* Returns an entity with the given ID from the database.
* @param id Primary key.
* @param useCachedResults Whether to used DB entities already cached in memory or force DB load.
* @return An entity with the given primary key or null if the entity is not found.
*/
ENTITY get(long id, boolean useCachedResults);
/***
* Returns entities with the given IDs from the database.
* @param ids Primary keys.
......
......@@ -44,8 +44,17 @@ public class DaoBaseImpl<ENTITY extends EntityBase> implements DaoBase<ENTITY> {
@Override
public ENTITY get(long id) {
return get(id, true);
}
@Override
public ENTITY get(long id, boolean useCachedResults) {
try {
return entityManager.find(entityClass, id);
var entity = entityManager.find(entityClass, id);
if (!useCachedResults) {
entityManager.refresh(entity);
}
return entity;
}
catch (Exception ex) {
throw new WineryDataAccessException(ex.getMessage(), ex);
......
......@@ -53,7 +53,7 @@ public abstract class PersistenceServiceImpl<DTO extends PersistentDtoBase, ENTI
@Override
@Transactional
public DTO get(long id) {
var entity = dao.get(id);
var entity = dao.get(id, false);
return mapToDto(entity);
}
......
......@@ -5,6 +5,7 @@
package cz.muni.fi.pa165.winery.services.mappers.order;
import cz.muni.fi.pa165.winery.dto.order.OrderItemDto;
import cz.muni.fi.pa165.winery.persistence.entities.Order;
import cz.muni.fi.pa165.winery.persistence.entities.OrderItem;
import cz.muni.fi.pa165.winery.persistence.entities.WineBottle;
import cz.muni.fi.pa165.winery.services.mappers.EntityMapperImpl;
......@@ -38,7 +39,20 @@ public class OrderItemEntityMapper extends EntityMapperImpl<OrderItemDto, OrderI
var entity = super.mapToEntity(record);
var bottle = new WineBottle();
bottle.setId(record.getBottleId());
var order = new Order();
order.setId(record.getOrderId());
entity.setBottle(bottle);
entity.setOrder(order);
return entity;
}
@Override
public OrderItemDto mapToDto(OrderItem orderItem) {
if (orderItem == null) {
return null;
}
var dto = super.mapToDto(orderItem);
dto.setOrderId(orderItem.getOrder().getId());
return dto;
}
}
......@@ -43,10 +43,10 @@ public class OrderServiceImpl extends PersistenceServiceImpl<OrderDto, Order> im
@Override
public OrderDto get(long id, boolean loadBottleData) {
if (!loadBottleData) {
return get(id);
return get(id, false);
}
var entity = orderDao.get(id);
var entity = orderDao.get(id, false);
if (entity == null) {
return null;
}
......
......@@ -46,10 +46,10 @@ public abstract class PersistenceServiceTestBase<DTO extends PersistentDtoBase,
@Test
public void testGet() {
var dao = getMockedDao();
Mockito.when(dao.get(1)).thenReturn(getTestEntity());
Mockito.when(dao.get(1, false)).thenReturn(getTestEntity());
var dto = getPersistenceService().get(1);
assertThat(equals(dto, getTestEntity())).isTrue();
Mockito.verify(dao, Mockito.times(1)).get(1);
Mockito.verify(dao, Mockito.times(1)).get(1, false);
}
@Test
......
......@@ -4,6 +4,7 @@ import cz.muni.fi.pa165.winery.dto.order.OrderItemDto;
import cz.muni.fi.pa165.winery.dto.wine.WineBottleDto;
import cz.muni.fi.pa165.winery.persistence.dao.DaoBase;
import cz.muni.fi.pa165.winery.persistence.dao.order.OrderItemDao;
import cz.muni.fi.pa165.winery.persistence.entities.Order;
import cz.muni.fi.pa165.winery.persistence.entities.OrderItem;
import cz.muni.fi.pa165.winery.persistence.entities.WineBottle;
import cz.muni.fi.pa165.winery.services.PersistenceServiceImpl;
......@@ -39,10 +40,10 @@ public class OrderItemServiceTests extends PersistenceServiceTestBase<OrderItemD
void testGetItem() {
getPersistenceService();
var dao = getMockedDao();
Mockito.when(dao.get(1)).thenReturn(getTestEntity());
Mockito.when(dao.get(1, false)).thenReturn(getTestEntity());
var dto = orderItemService.get(1);
assertThat(equals(dto, getTestEntity())).isTrue();
Mockito.verify(dao, Mockito.times(1)).get(1);
Mockito.verify(dao, Mockito.times(1)).get(1, false);
assertThat(dto.getBottleId())
.isEqualTo(getTestEntity().getBottle().getId());
}
......@@ -50,12 +51,13 @@ public class OrderItemServiceTests extends PersistenceServiceTestBase<OrderItemD
@Override
protected OrderItemDto getTestDto() {
var dto = new OrderItemDto();
dto.setId(1);
dto.setBottleId(1);
dto.setId(10);
dto.setBottleId(11);
dto.setOrderId(1);
dto.setQuantity(new BigDecimal(3));
var bottle = new WineBottleDto();
bottle.setId(1);
bottle.setId(11);
bottle.setName("Some wine");
bottle.setPrice(new BigDecimal(200));
bottle.setStock(3);
......@@ -68,12 +70,16 @@ public class OrderItemServiceTests extends PersistenceServiceTestBase<OrderItemD
@Override
protected OrderItem getTestEntity() {
var order = new Order();
order.setId(1);
var orderItem = new OrderItem();
orderItem.setId(1);
orderItem.setId(10);
orderItem.setOrder(order);
orderItem.setQuantity(new BigDecimal(3));
var bottle = new WineBottle();
bottle.setId(1);
bottle.setId(11);
bottle.setName("Some wine");
bottle.setPrice(new BigDecimal(200));
bottle.setStock(3);
......
......@@ -49,10 +49,10 @@ public class OrderServiceTests extends PersistenceServiceTestBase<OrderDto, Orde
void testGetWithBottle() {
getPersistenceService(); // Force lazy load
var dao = getMockedDao();
Mockito.when(dao.get(1)).thenReturn(getTestEntity());
Mockito.when(dao.get(1, false)).thenReturn(getTestEntity());
var dto = orderService.get(1, true);
assertThat(equals(dto, getTestEntity())).isTrue();
Mockito.verify(dao, Mockito.times(1)).get(1);
Mockito.verify(dao, Mockito.times(1)).get(1, false);
assertThat(dto.getItems().stream().findFirst().get().getWineBottle().getName())
.isEqualTo(getTestEntity().getItems().stream().findFirst().get().getBottle().getName());
}
......@@ -91,6 +91,7 @@ public class OrderServiceTests extends PersistenceServiceTestBase<OrderDto, Orde
item.setId(10);
item.setBottleId(11);
item.setQuantity(new BigDecimal(2));
item.setOrderId(1);
var bottle = new WineBottleDto();
bottle.setId(11);
......@@ -106,6 +107,7 @@ public class OrderServiceTests extends PersistenceServiceTestBase<OrderDto, Orde
protected Order getTestEntity() {
var order = new Order();
order.setId(1);
var user = new User();
user.setId(2);
order.setUser(user);
......@@ -114,6 +116,7 @@ public class OrderServiceTests extends PersistenceServiceTestBase<OrderDto, Orde
var item = new OrderItem();
item.setId(10);
item.setOrder(order);
item.setQuantity(new BigDecimal(2));
var bottle = new WineBottle();
......
......@@ -106,6 +106,17 @@
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<dependency>
<groupId>one.util</groupId>
<artifactId>streamex</artifactId>
<version>0.6.5</version>
</dependency>
</dependencies>
<build>
......
......@@ -20,7 +20,9 @@ public class DataSeederComponent implements DataSeederContext {
new UserRoleDataSeeder(),
new WineTypeDataSeeder(),
new WineBottleDataSeeder(),
new ProductReviewDataSeeder()
new ProductReviewDataSeeder(),
new OrderDataSeeder(),
new OrderItemDataSeeder()
);
private Map<String, List<Object>> seededObjects = new HashMap<>();
......
package cz.muni.fi.pa165.winery.webapp.controllers;
import cz.muni.fi.pa165.winery.dto.order.OrderDto;
import cz.muni.fi.pa165.winery.dto.order.OrderItemDto;
import cz.muni.fi.pa165.winery.dto.wine.HarvestDto;
import cz.muni.fi.pa165.winery.services.CartService;
import cz.muni.fi.pa165.winery.services.CookieService;
import cz.muni.fi.pa165.winery.services.order.OrderItemService;
import cz.muni.fi.pa165.winery.services.order.OrderService;
import cz.muni.fi.pa165.winery.services.wine.WineBottleService;
import cz.muni.fi.pa165.winery.webapp.models.ViewModelBase;
import cz.muni.fi.pa165.winery.webapp.models.cart.CartItemViewModel;
import cz.muni.fi.pa165.winery.webapp.models.cart.CartListingViewModel;
......@@ -13,6 +18,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Map;
import java.util.stream.Collectors;
@Controller
......@@ -20,9 +29,15 @@ import java.util.stream.Collectors;
public class CartController extends ControllerBase {
private final CartService cartService;
private final OrderService orderService;
private final OrderItemService orderItemService;
private final WineBottleService wineBottleService;
public CartController(CartService cartService) {
public CartController(CartService cartService, OrderService orderService, OrderItemService orderItemService, WineBottleService wineBottleService) {
this.cartService = cartService;
this.orderService = orderService;
this.orderItemService = orderItemService;
this.wineBottleService = wineBottleService;
}
@GetMapping("")
......@@ -71,4 +86,39 @@ public class CartController extends ControllerBase {
cartService.saveToCookie(cart);
return "Successfuly removed";
}
@PostMapping("/checkout")
@ResponseBody
public String add() {
var order = new OrderDto();
order.setShipped(false);
order.setDateTime(LocalDateTime.now());
order.setUserId(getCurrentUser().getId());
orderService.create(order);
var cart = cartService.loadFromCookie();
var cartItems = cart.getCartItems();
for (Map.Entry<Long, Integer> entry : cartItems.entrySet()) {
var item = new OrderItemDto();
item.setBottleId(entry.getKey());
item.setQuantity(new BigDecimal(entry.getValue()));
item.setOrderId(order.getId());
item.setOrderId(order.getId());
order.getItems().add(item);
orderItemService.create(item);
cart.remove(entry.getKey());
var bottle = wineBottleService.get(item.getBottleId());
bottle.setStock(bottle.getStock() - item.getQuantity().intValue());
wineBottleService.update(bottle);
}
cartService.saveToCookie(cart);
return "Success";
}
}
package cz.muni.fi.pa165.winery.webapp.controllers;
import cz.muni.fi.pa165.winery.services.order.OrderService;
import cz.muni.fi.pa165.winery.webapp.models.ViewModelBase;
import cz.muni.fi.pa165.winery.webapp.models.order.OrderListingViewModel;
import cz.muni.fi.pa165.winery.webapp.models.order.OrderUpsertViewModel;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@RequestMapping("/myorder")
@Controller
public class MyOrderController extends ControllerBase {
private final OrderService orderService;
public MyOrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("")
public ModelAndView index() {
var orders = orderService.getOrdersForUser(getCurrentUser().getId(), true);
var viewModel = new OrderListingViewModel();
initializeViewModel(viewModel);
viewModel.setOrders(orders);
return view(viewModel);
}
@Override
protected <T extends ViewModelBase> void initializeViewModel(T viewModel) {
super.initializeViewModel(viewModel);
if (OrderUpsertViewModel.class.isAssignableFrom(viewModel.getClass()))
{
var typedViewModel = (OrderUpsertViewModel) viewModel;
}
}
}
\ No newline at end of file
package cz.muni.fi.pa165.winery.webapp.controllers;
import cz.muni.fi.pa165.winery.dto.order.OrderDto;
import cz.muni.fi.pa165.winery.services.order.OrderItemService;
import cz.muni.fi.pa165.winery.services.order.OrderService;
import cz.muni.fi.pa165.winery.services.user.UserService;
import cz.muni.fi.pa165.winery.webapp.models.orders.OrdersListingViewModel;
import cz.muni.fi.pa165.winery.webapp.models.orders.OrdersUpsertViewModel;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.stream.Collectors;
@RequestMapping("/order")
@Controller
public class OrderController extends ControllerBase {
private final OrderService orderService;
private final UserService userService;
private final OrderItemService orderItemService;
public OrderController(OrderService orderService, UserService userService, OrderItemService orderItemService) {
this.orderService = orderService;
this.userService = userService;
this.orderItemService = orderItemService;
}
@GetMapping("")
@Secured("ROLE_ADMIN")
public ModelAndView index() {
// var orders = orderService.getAll();
// TODO not pretty
var orders = new ArrayList<OrderDto>();
for (OrderDto order : orderService.getAll()) {
orders.add(orderService.get(order.getId(), true));
}
var userIds = orders
.stream()
.map(x -> x.getUserId())
.distinct()
.collect(Collectors.toList());
var users = userService.get(userIds)
.stream().collect(Collectors.toMap(x -> x.getId(), x -> x));
var viewModel = new OrdersListingViewModel();
initializeViewModel(viewModel);
viewModel.setOrders(orders);
viewModel.setUserLookup(users);
return view(viewModel);
}
@GetMapping("/edit")
@Secured("ROLE_ADMIN")
public ModelAndView edit(int id) {
var order = orderService.get(id, true);
if (order == null) {
return notFound();
}
var user = userService.get(order.getUserId());
var viewModel = new OrdersUpsertViewModel(order);
viewModel.setUserName(user.getFirstName() + " " + user.getLastName());
viewModel.setEmail(user.getEmail());
initializeViewModel(viewModel);
return view(viewModel);
}
@PostMapping("/edit")
@Secured("ROLE_ADMIN")
public ModelAndView edit(@Valid @ModelAttribute("model") OrdersUpsertViewModel viewModel, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return view(bindingResult.getModel());
}
var dto = viewModel.toDto();
orderService.update(dto);
var order = orderService.get(viewModel.getId(), true);
for (var item : order.getItems()) {
item.setQuantity(viewModel.getQuantities().get(item.getId()));
orderItemService.update(item);
}
var resultViewModel = new OrdersUpsertViewModel(order);
initializeViewModel(resultViewModel);
resultViewModel.setSuccess(true);
resultViewModel.setItems(order.getItems());
var user = userService.get(order.getUserId());
resultViewModel.setUserName(user.getFirstName() + " " + user.getLastName());
resultViewModel.setEmail(user.getEmail());
return view(resultViewModel);
}
@PostMapping("/delete")
@ResponseBody
@Secured("ROLE_ADMIN")
public String delete(long id) {
orderService.delete(OrderDto.builder().id(id).items(new HashSet<>()).build());
return "Successfuly deleted";
}
}
\ No newline at end of file
package cz.muni.fi.pa165.winery.webapp.models.order;
import cz.muni.fi.pa165.winery.dto.order.OrderDto;
import cz.muni.fi.pa165.winery.dto.wine.HarvestDto;
import cz.muni.fi.pa165.winery.webapp.models.ViewModelBase;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Getter
@Setter
public class OrderListingViewModel extends ViewModelBase {
private List<OrderDto> orders;
}
\ No newline at end of file
package cz.muni.fi.pa165.winery.webapp.models.order;
import cz.muni.fi.pa165.winery.dto.order.OrderDto;
import cz.muni.fi.pa165.winery.dto.wine.HarvestDto;
import cz.muni.fi.pa165.winery.webapp.models.ViewModelBase;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Getter
@Setter
@NoArgsConstructor
public class OrderUpsertViewModel extends ViewModelBase {
public OrderUpsertViewModel(OrderDto dto) {
var mapper = new ModelMapper();
mapper.getConfiguration().setAmbiguityIgnored(true);
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
mapper.map(dto, this);
}
private long id;
private boolean success;
/**
* year of the harvest
*/
@NotNull(message = "Cannot be empty")
private boolean shipped;
public OrderDto toDto() {
var mapper = new ModelMapper();
return mapper.map(this, OrderDto.class);
}
}
package cz.muni.fi.pa165.winery.webapp.models.orders;
import cz.muni.fi.pa165.winery.dto.order.OrderDto;
import cz.muni.fi.pa165.winery.dto.user.UserDto;
import cz.muni.fi.pa165.winery.dto.wine.HarvestDto;
import cz.muni.fi.pa165.winery.webapp.models.ViewModelBase;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Getter
@Setter