Commit ef752cef authored by Samuel Dudík's avatar Samuel Dudík
Browse files

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

Shopping cart and orders

See merge request !44
parents 6b6a4b2b 8e539b3d
Pipeline #141069 passed with stages
in 2 minutes and 19 seconds
......@@ -6,5 +6,6 @@ import cz.muni.fi.pa165.winery.dto.order.OrderDto;
public interface CartService {
CartDto loadFromCookie();
void saveToCookie(CartDto cart);
void clear();
OrderDto convertToOrder(CartDto cart, long userId);
}
......@@ -51,7 +51,7 @@ public class DaoBaseImpl<ENTITY extends EntityBase> implements DaoBase<ENTITY> {
public ENTITY get(long id, boolean useCachedResults) {
try {
var entity = entityManager.find(entityClass, id);
if (!useCachedResults) {
if (!useCachedResults && entity != null) {
entityManager.refresh(entity);
}
return entity;
......
......@@ -53,6 +53,11 @@ public class CartServiceImpl implements CartService {
cookieService.setCookie(getCartCookieName(), json);
}
@Override
public void clear() {
cookieService.setCookie(getCartCookieName(), "{}");
}
@Override
public OrderDto convertToOrder(CartDto cart, long userId) {
var order = new OrderDto();
......
......@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.ModelAndView;
import java.math.BigDecimal;
......@@ -24,6 +25,8 @@ import java.util.HashSet;
import java.util.Map;
import java.util.stream.Collectors;
import static org.springframework.http.HttpStatus.GONE;
@Controller
@RequestMapping("/cart")
public class CartController extends ControllerBase {
......@@ -41,7 +44,7 @@ public class CartController extends ControllerBase {
}
@GetMapping("")
public ModelAndView test() {
public ModelAndView index() {
var viewModel = new CartListingViewModel();
initializeViewModel(viewModel);
......@@ -63,6 +66,9 @@ public class CartController extends ControllerBase {
@PostMapping("/add")
@ResponseBody
public String add(long id, int count) {
if (count < 1)
return "Fail";
var cart = cartService.loadFromCookie();
cart.add(id, count);
cartService.saveToCookie(cart);
......@@ -90,6 +96,19 @@ public class CartController extends ControllerBase {
@PostMapping("/checkout")
@ResponseBody
public String add() {
var cart = cartService.loadFromCookie();
var cartItems = cart.getCartItems();
for (Map.Entry<Long, Integer> entry : cartItems.entrySet()) {
var bottle = wineBottleService.get(entry.getKey());
if (bottle == null) {
continue;
}
if (bottle.getStock() < entry.getValue())
throw new ResponseStatusException(GONE, "Not enough products on stock.");
}
var order = new OrderDto();
order.setShipped(false);
......@@ -98,27 +117,35 @@ public class CartController extends ControllerBase {
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());
if (bottle == null) {
continue;
}
bottle.setStock(bottle.getStock() - item.getQuantity().intValue());
orderItemService.create(item);
wineBottleService.update(bottle);
}
cartService.saveToCookie(cart);
cartService.clear();
return "Success";
}
@Override
protected <T extends ViewModelBase> void initializeViewModel(T viewModel) {
super.initializeViewModel(viewModel);
if (viewModel instanceof CartListingViewModel) {
var cartViewModel = (CartListingViewModel) viewModel;
var productNames = wineBottleService.getAll().stream().collect(Collectors.toMap(x -> x.getId(), x -> x.getName()));
cartViewModel.setProductNames(productNames);
}
}
}
......@@ -112,7 +112,9 @@ public class ControllerBase {
private Pair<String, String> getControllerAndAction() {
var callerFrame = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.walk(s ->
s.filter(c -> ControllerBase.class.isAssignableFrom(c.getDeclaringClass()) && c.getDeclaringClass() != ControllerBase.class)
s.filter(c -> ControllerBase.class.isAssignableFrom(c.getDeclaringClass()) &&
c.getDeclaringClass() != ControllerBase.class &&
!c.getMethodName().equals("initializeViewModel"))
.findFirst());
if (!callerFrame.isPresent()) {
......
......@@ -33,9 +33,6 @@ public class OrderController extends ControllerBase {
@GetMapping("")
@Secured("ROLE_ADMIN")
public ModelAndView index() {
// var orders = orderService.getAll();
// TODO not pretty
var orders = new ArrayList<OrderDto>();
for (OrderDto order : orderService.getAll()) {
......
......@@ -5,9 +5,11 @@ import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Getter
@Setter
public class CartListingViewModel extends ViewModelBase {
private List<CartItemViewModel> cart;
private Map<Long, String> productNames;
}
......@@ -24,8 +24,14 @@ function setCartAmount(itemId, count) {
function checkout() {
return fetch(`${getUrlPrefix()}cart/checkout`,
{method: 'POST'}).then(() => {
window.location.href = `${getUrlPrefix()}myorder`;
{method: 'POST'}).then((res) => {
if (res.status == 410) {
document.getElementById("failure-alert").style.display = "block";
document.getElementById("success-alert").style.display = "none";
} else {
document.getElementById("success-alert").style.display = "block";
document.getElementById("failure-alert").style.display = "none";
}
});
}
......
......@@ -10,22 +10,34 @@
<div class="container px-4 px-lg-5">
<main role="main" class="pb-3">
<h4>Cart contents:</h4>
<div style="display: none;" id="success-alert">
<div class="alert alert-success mt-2" role="alert">
The order was sent successfully!
</div>
</div>
<div style="display: none;" id="failure-alert">
<div class="alert alert-danger mt-2" role="alert">
Not enough products on stock!
</div>
</div>
<table class="table">
<thead>
<th>Item ID</th>
<th>Product Name</th>
<th>Count</th>
<th>Delete</th>
</thead>
<tbody>
<tr th:each="item : ${model.cart}">
<td th:text="${item.productId}">ID</td>
<td th:text="${item.count}">Count</td>
<td th:text="${model.productNames[__${item.productId}__]}">Product Name</td>
<td><input th:onchange="'setCartAmount(' + ${item.productId} + ', document.getElementById(\'quantity\').value)'" type="number" id="quantity" name="quantity" min="1" th:value="${item.count}"/></td>
<td><a href="#" th:onclick="'removeFromCart(' + ${item.productId} + ').then(x => refreshPage())'">Delete</a></td>
</tr>
</tbody>
</table>
<a class="btn btn-primary" href="#" onclick="addToCart(1, 3).then(x => refreshPage())">Add to cart</a>
<a class="btn btn-primary" href="#" onclick="removeFromCart(1).then(x => refreshPage())">Remove from cart</a>
<a class="btn btn-primary" href="#" onclick="setCartAmount(1, 2).then(x => refreshPage())">Set cart amount</a>
<a class="btn btn-primary" href="#" onclick="checkout()">Checkout</a>
</main>
</div>
......
......@@ -31,7 +31,6 @@
<li class="nav-item"><a class="nav-link active" th:href="@{/productreview}" >Reviews</a></li>
<li class="nav-item"><a class="nav-link active" th:href="@{/user}" th:if='${model.currentUser != null && model.currentUser.hasRole("ADMIN")}'>Users</a></li>
<li class="nav-item"><a class="nav-link active" th:href="@{/order}" th:if='${model.currentUser != null && model.currentUser.hasRole("ADMIN")}'>Orders</a></li>
<li class="nav-item"><a class="nav-link active" th:href="@{/cart}" th:if='${model.currentUser != null && model.currentUser.hasRole("ADMIN")}'>Cart test</a></li>
<div class="vr" th:if="${model.currentUser}"></div>
<li class="nav-item dropdown" th:if="${model.currentUser}">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="${model.currentUser.email}">
......
......@@ -43,7 +43,7 @@
<h3>Items</h3>
<div th:each="entry, stat : *{items}">
<span th:text="*{wineNames[__${entry.bottleId}__]}"/>
<input type="number" id="quantity" name="quantity" min="1" max="5" th:field="*{quantities[__${entry.id}__]}">
<input type="number" id="quantity" name="quantity" min="1" th:field="*{quantities[__${entry.id}__]}">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
......
......@@ -42,7 +42,7 @@
</table>
<div class="mb-2">
<input id="itemCount" type="number" value="1"/>
<input id="itemCount" type="number" min="1" value="1"/>
<a class="btn btn-primary btn-sm" href="#" th:onclick="'addToCart(' + ${model.id} + ', document.getElementById(\'itemCount\').value).then(x => document.getElementById(\'success-alert\').style.display = \'block\')'">Add to cart</a>
</div>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment