CartController.java
package com.ctrlbuy.webshop.controller;
import com.ctrlbuy.webshop.model.Product;
import com.ctrlbuy.webshop.service.ProductService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import jakarta.servlet.http.HttpSession;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Controller
@RequiredArgsConstructor
@Slf4j
public class CartController {
private final ProductService productService;
private static final String CART_SESSION_KEY = "shopping_cart";
// Visa kundvagn - ENGELSKA URL
@GetMapping("/cart")
public String viewCart(HttpSession session, Model model, Authentication auth) {
return viewCartSwedish(session, model, auth);
}
// SVENSK URL - DIREKT MAPPING (inte under /cart)
@GetMapping("/varukorg")
public String viewCartSwedish(HttpSession session, Model model, Authentication auth) {
log.debug("Visar kundvagn via svensk URL för användare: {}",
auth != null ? auth.getName() : "anonym");
try {
List<CartItem> cartItems = getCartItemsFromSession(session);
// VIKTIGT: Beräkna totaler för templaten
BigDecimal subtotal = cartItems.stream()
.map(CartItem::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// Frakt: 49 kr om under 499 kr, annars gratis
BigDecimal shipping = subtotal.compareTo(new BigDecimal("499")) >= 0 ?
BigDecimal.ZERO : new BigDecimal("49");
BigDecimal total = subtotal.add(shipping);
int cartItemCount = cartItems.stream()
.mapToInt(CartItem::getQuantity)
.sum();
// Lägg till EXAKT det som templaten förväntar sig
model.addAttribute("cartItems", cartItems);
model.addAttribute("subtotal", subtotal); // För templaten
model.addAttribute("shipping", shipping); // För templaten
model.addAttribute("total", total); // För templaten
model.addAttribute("cartTotal", total); // Backup
model.addAttribute("cartItemCount", cartItemCount);
log.debug("Cart contains {} items, subtotal: {} kr, shipping: {} kr, total: {} kr",
cartItemCount, subtotal, shipping, total);
} catch (Exception e) {
log.error("Error loading cart", e);
model.addAttribute("errorMessage", "Ett fel uppstod vid hämtning av kundvagnen.");
model.addAttribute("cartItems", new ArrayList<>());
model.addAttribute("subtotal", BigDecimal.ZERO);
model.addAttribute("shipping", BigDecimal.ZERO);
model.addAttribute("total", BigDecimal.ZERO);
model.addAttribute("cartItemCount", 0);
}
return "cart/view";
}
// Lägg till produkt i kundvagn - STÖDER BÅDE FORM OCH AJAX
@PostMapping("/cart/add")
public String addToCart(
@RequestParam Long productId,
@RequestParam(defaultValue = "1") Integer quantity,
HttpSession session,
RedirectAttributes redirectAttributes) {
log.debug("Lägger till produkt {} i kundvagn, kvantitet: {}", productId, quantity);
try {
Optional<Product> productOpt = productService.getProductByIdWithoutView(productId);
if (productOpt.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "Produkten hittades inte");
return "redirect:/varukorg";
}
Product product = productOpt.get();
// Kontrollera lager
if (product.getStockQuantity() == null || product.getStockQuantity() < quantity) {
redirectAttributes.addFlashAttribute("errorMessage",
"Otillräckligt lager. Endast " + (product.getStockQuantity() != null ? product.getStockQuantity() : 0) + " tillgängliga");
return "redirect:/produkter"; // Tillbaka till produktsidan så man kan fortsätta handla
}
// Lägg till i kundvagn
List<CartItem> cartItems = getCartItemsFromSession(session);
// Hitta befintlig item eller skapa ny
CartItem existingItem = cartItems.stream()
.filter(item -> item.getProduct().getId().equals(productId))
.findFirst()
.orElse(null);
if (existingItem != null) {
int newQuantity = existingItem.getQuantity() + quantity;
if (newQuantity > product.getStockQuantity()) {
redirectAttributes.addFlashAttribute("errorMessage",
"Kan inte lägga till fler. Max " + product.getStockQuantity() + " tillgängliga");
return "redirect:/produkter"; // Tillbaka till produktsidan
}
existingItem.setQuantity(newQuantity);
existingItem.updatePrice();
} else {
cartItems.add(new CartItem(product, quantity));
}
// Spara i session
session.setAttribute(CART_SESSION_KEY, cartItems);
redirectAttributes.addFlashAttribute("successMessage",
product.getName() + " har lagts till i kundvagnen");
log.info("Produkt {} tillagd i kundvagn", product.getName());
} catch (Exception e) {
log.error("Fel vid tillägg i kundvagn för produkt: " + productId, e);
redirectAttributes.addFlashAttribute("errorMessage", "Ett fel uppstod vid tillägg i kundvagn");
}
// VIKTIGT: Redirect till produktsidan så man kan fortsätta handla
return "redirect:/produkter";
}
// Uppdatera kvantitet - FORM-BASERAD
@PostMapping("/cart/update")
public String updateQuantity(
@RequestParam Long productId,
@RequestParam Integer quantity,
HttpSession session,
RedirectAttributes redirectAttributes) {
log.debug("Uppdaterar kvantitet för produkt {} till {}", productId, quantity);
try {
if (quantity <= 0) {
return removeFromCart(productId, session, redirectAttributes);
}
List<CartItem> cartItems = getCartItemsFromSession(session);
CartItem item = cartItems.stream()
.filter(cartItem -> cartItem.getProduct().getId().equals(productId))
.findFirst()
.orElse(null);
if (item == null) {
redirectAttributes.addFlashAttribute("errorMessage", "Produkten finns inte i kundvagnen");
return "redirect:/varukorg";
}
// Kontrollera lager
Optional<Product> productOpt = productService.getProductByIdWithoutView(productId);
if (productOpt.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "Produkten hittades inte");
return "redirect:/varukorg";
}
Product product = productOpt.get();
if (quantity > product.getStockQuantity()) {
redirectAttributes.addFlashAttribute("errorMessage",
"Otillräckligt lager. Max " + product.getStockQuantity() + " tillgängliga");
return "redirect:/varukorg";
}
// Uppdatera kvantitet
item.setQuantity(quantity);
item.updatePrice();
session.setAttribute(CART_SESSION_KEY, cartItems);
redirectAttributes.addFlashAttribute("successMessage", "Kvantiteten har uppdaterats");
} catch (Exception e) {
log.error("Fel vid uppdatering av kundvagn för produkt: " + productId, e);
redirectAttributes.addFlashAttribute("errorMessage", "Ett fel uppstod vid uppdatering");
}
return "redirect:/varukorg";
}
// Ta bort från kundvagn - FORM-BASERAD
@PostMapping("/cart/remove")
public String removeFromCart(
@RequestParam Long productId,
HttpSession session,
RedirectAttributes redirectAttributes) {
log.debug("Tar bort produkt {} från kundvagn", productId);
try {
List<CartItem> cartItems = getCartItemsFromSession(session);
boolean removed = cartItems.removeIf(item -> item.getProduct().getId().equals(productId));
if (removed) {
session.setAttribute(CART_SESSION_KEY, cartItems);
redirectAttributes.addFlashAttribute("successMessage", "Produkten har tagits bort från kundvagnen");
} else {
redirectAttributes.addFlashAttribute("errorMessage", "Produkten fanns inte i kundvagnen");
}
} catch (Exception e) {
log.error("Fel vid borttagning från kundvagn för produkt: " + productId, e);
redirectAttributes.addFlashAttribute("errorMessage", "Ett fel uppstod vid borttagning");
}
return "redirect:/varukorg";
}
// Rensa kundvagn
@PostMapping("/cart/clear")
public String clearCart(HttpSession session, RedirectAttributes redirectAttributes) {
log.debug("Rensar kundvagn");
try {
session.removeAttribute(CART_SESSION_KEY);
redirectAttributes.addFlashAttribute("successMessage", "Kundvagnen har rensats");
} catch (Exception e) {
log.error("Fel vid rensning av kundvagn", e);
redirectAttributes.addFlashAttribute("errorMessage", "Ett fel uppstod vid rensning av kundvagn");
}
return "redirect:/varukorg";
}
// AJAX-ENDPOINTS FÖR MODERN FUNKTIONALITET
// Lägg till produkt i kundvagn (AJAX)
@PostMapping("/cart/add/{productId}")
@ResponseBody
public ResponseEntity<Map<String, Object>> addToCartAjax(
@PathVariable Long productId,
@RequestParam(defaultValue = "1") Integer quantity,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
try {
Optional<Product> productOpt = productService.getProductByIdWithoutView(productId);
if (productOpt.isEmpty()) {
response.put("success", false);
response.put("message", "Produkten hittades inte");
return ResponseEntity.badRequest().body(response);
}
Product product = productOpt.get();
if (product.getStockQuantity() == null || product.getStockQuantity() < quantity) {
response.put("success", false);
response.put("message", "Otillräckligt lager");
response.put("availableStock", product.getStockQuantity());
return ResponseEntity.badRequest().body(response);
}
List<CartItem> cartItems = getCartItemsFromSession(session);
CartItem existingItem = cartItems.stream()
.filter(item -> item.getProduct().getId().equals(productId))
.findFirst()
.orElse(null);
if (existingItem != null) {
int newQuantity = existingItem.getQuantity() + quantity;
if (newQuantity > product.getStockQuantity()) {
response.put("success", false);
response.put("message", "Kan inte lägga till fler. Max " + product.getStockQuantity() + " tillgängliga");
return ResponseEntity.badRequest().body(response);
}
existingItem.setQuantity(newQuantity);
existingItem.updatePrice();
} else {
cartItems.add(new CartItem(product, quantity));
}
session.setAttribute(CART_SESSION_KEY, cartItems);
// Beräkna nya totaler
int totalItems = cartItems.stream()
.mapToInt(CartItem::getQuantity)
.sum();
BigDecimal total = cartItems.stream()
.map(CartItem::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
response.put("success", true);
response.put("message", "Produkten har lagts till i kundvagnen");
response.put("cartCount", totalItems);
response.put("cartTotal", total);
} catch (Exception e) {
log.error("Fel vid AJAX-tillägg i kundvagn för produkt: " + productId, e);
response.put("success", false);
response.put("message", "Ett fel uppstod vid tillägg i kundvagn");
return ResponseEntity.internalServerError().body(response);
}
return ResponseEntity.ok(response);
}
// Hämta antal items i kundvagn (AJAX)
@GetMapping("/cart/count")
@ResponseBody
public ResponseEntity<Map<String, Object>> getCartCount(HttpSession session) {
List<CartItem> cartItems = getCartItemsFromSession(session);
int totalItems = cartItems.stream()
.mapToInt(CartItem::getQuantity)
.sum();
Map<String, Object> response = new HashMap<>();
response.put("count", totalItems);
return ResponseEntity.ok(response);
}
// Helper metod för att hämta cart items från session
@SuppressWarnings("unchecked")
private List<CartItem> getCartItemsFromSession(HttpSession session) {
List<CartItem> cartItems = (List<CartItem>) session.getAttribute(CART_SESSION_KEY);
if (cartItems == null) {
cartItems = new ArrayList<>();
session.setAttribute(CART_SESSION_KEY, cartItems);
}
return cartItems;
}
// CartItem class som är kompatibel med din template
public static class CartItem {
private Product product;
private Integer quantity;
private BigDecimal unitPrice;
private BigDecimal totalPrice;
public CartItem(Product product, Integer quantity) {
this.product = product;
this.quantity = quantity;
this.unitPrice = product.getPrice();
updatePrice();
}
public void updatePrice() {
this.totalPrice = unitPrice.multiply(BigDecimal.valueOf(quantity));
}
// Getters and setters
public Product getProduct() { return product; }
public void setProduct(Product product) { this.product = product; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) {
this.quantity = quantity;
updatePrice();
}
public BigDecimal getUnitPrice() { return unitPrice; }
public void setUnitPrice(BigDecimal unitPrice) {
this.unitPrice = unitPrice;
updatePrice();
}
public BigDecimal getTotalPrice() { return totalPrice; }
public void setTotalPrice(BigDecimal totalPrice) { this.totalPrice = totalPrice; }
}
}