diff --git a/.gitignore b/.gitignore index 3b718f8..3912d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,5 @@ local.properties # SQLite prototype/*.db + +prototype/images diff --git a/prototype/.gitignore b/prototype/.gitignore index 59529a7..52b353d 100644 --- a/prototype/.gitignore +++ b/prototype/.gitignore @@ -1,4 +1,4 @@ -./test.db +e-commerce.db ./build ./gradle ./out diff --git a/prototype/scripts/addarticles.sql b/prototype/scripts/addarticles.sql new file mode 100644 index 0000000..f7e5f5b --- /dev/null +++ b/prototype/scripts/addarticles.sql @@ -0,0 +1,6 @@ +INSERT INTO article_offers ("manufacturer", "article_number", "vat_percent", "should_be_advertised") +VALUES ("McDonalds", "1", 7, 1); + +INSERT INTO articles ("related_id", "shop_price_per_unit_net_cent", "warehouse_units_per_slot", "should_reorder", "reorder_max_price", "title", "description", "image_id") +VALUES (1, 19.99, 10, 1, 15, "Huge Hamburger", "This huge Hamburger is awesome!", NULL); + diff --git a/prototype/scripts/addusers.sql b/prototype/scripts/addusers.sql index 86b6d43..db3d6ed 100644 --- a/prototype/scripts/addusers.sql +++ b/prototype/scripts/addusers.sql @@ -1,9 +1,9 @@ /* password is 123 */ -INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b") -VALUES (datetime('now','localtime') ||'.0', "emp@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "0", "1", "1", "0"); +INSERT INTO users ("created", "email", "password_hash", "is_active", "is_employee") +VALUES (datetime('now','localtime') ||'.0', "emp@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "1"); -INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b") -VALUES (datetime('now','localtime') ||'.0', "user@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "1", "0", "0"); +INSERT INTO users ("created", "email", "password_hash", "is_active", "is_employee") +VALUES (datetime('now','localtime') ||'.0', "user@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "0"); -INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b") -VALUES (datetime('now','localtime') ||'.0', "blocked@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "0", "0", "0"); \ No newline at end of file +INSERT INTO users ("created", "email", "password_hash", "is_active", "is_employee") +VALUES (datetime('now','localtime') ||'.0', "blocked@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "0", "0"); \ No newline at end of file diff --git a/prototype/src/main/java/org/hso/ecommerce/action/booking/CreateBookingAction.java b/prototype/src/main/java/org/hso/ecommerce/action/booking/CreateBookingAction.java new file mode 100644 index 0000000..f01e6a3 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/action/booking/CreateBookingAction.java @@ -0,0 +1,30 @@ +package org.hso.ecommerce.action.booking; + +import org.hso.ecommerce.entities.booking.Booking; +import org.hso.ecommerce.entities.booking.BookingAccountEntry; +import org.hso.ecommerce.entities.booking.BookingReason; + +public class CreateBookingAction { + + private Booking booking; + + public CreateBookingAction(BookingAccountEntry source, BookingAccountEntry destination, BookingReason reason, int amountCent) { + booking = new Booking(); + booking.reason = reason; + booking.amountCent = amountCent; + + assert source != null || destination != null; + + if (source != null) { + booking.source = source.copyAddAmount(-amountCent); + } + if (destination != null) { + booking.destination = destination.copyAddAmount(amountCent); + } + + } + + public Booking finish() { + return booking; + } +} diff --git a/prototype/src/main/java/org/hso/ecommerce/action/shop/CreateOrderAction.java b/prototype/src/main/java/org/hso/ecommerce/action/shop/CreateOrderAction.java new file mode 100644 index 0000000..8b35be8 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/action/shop/CreateOrderAction.java @@ -0,0 +1,195 @@ +package org.hso.ecommerce.action.shop; + +import org.hso.ecommerce.action.booking.CreateBookingAction; +import org.hso.ecommerce.entities.booking.*; +import org.hso.ecommerce.entities.shop.Address; +import org.hso.ecommerce.entities.shop.Article; +import org.hso.ecommerce.entities.shop.CustomerOrder; +import org.hso.ecommerce.entities.shop.CustomerOrderPosition; +import org.hso.ecommerce.entities.user.User; +import org.hso.ecommerce.entities.warehouse.WarehouseBooking; +import org.hso.ecommerce.entities.warehouse.WarehouseBookingPosition; +import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class CreateOrderAction { + + private User user; + Address destination; + + private int expectedTotalGrossCent; + + private int totalNetCent; + private int totalVatCent; + private PaymentMethod method; + + private BookingAccountEntry latestUserBooking; + private BookingAccountEntry latestVatBooking; + private BookingAccountEntry latestMainBooking; + + private List<OrderItem> orderItems = new ArrayList<>(); + + public CreateOrderAction( + User user, + int expectedTotalGrossCent, + Address destination, + PaymentMethod method, + BookingAccountEntry latestUserBooking, + BookingAccountEntry latestVatBooking, + BookingAccountEntry latestMainBooking + ) { + this.user = user; + this.expectedTotalGrossCent = expectedTotalGrossCent; + this.destination = destination; + this.method = method; + + this.latestUserBooking = latestUserBooking; + assert latestUserBooking.userAccount.id == user.id; + + this.latestVatBooking = latestVatBooking; + assert latestVatBooking.isVATAccount; + + this.latestMainBooking = latestMainBooking; + assert latestMainBooking.isMainAccount; + } + + public void addArticle(Article article, int quantity, List<WarehouseBookingPositionSlotEntry> availableSlots) { + for (WarehouseBookingPositionSlotEntry slot : availableSlots) { + assert slot.article.id == article.id; + } + + orderItems.add(new OrderItem(article, availableSlots, quantity)); + + totalNetCent += article.shopPricePerUnitNetCent * quantity; + totalVatCent += article.getVat() * quantity; + } + + + public Result finish() throws ArticleNotInStockException { + CustomerOrder order = createOrder(); + CustomerPayment payment = createPayment(); + + List<Booking> bookingList = new ArrayList<>(); + bookingList.add(new CreateBookingAction(latestUserBooking, latestMainBooking, new BookingReason(order), order.totalGrossCent).finish()); + bookingList.add(new CreateBookingAction(null, latestUserBooking, new BookingReason(payment), order.totalGrossCent).finish()); + bookingList.add(new CreateBookingAction(latestMainBooking, latestVatBooking, new BookingReason(order), order.totalVatCent).finish()); + + WarehouseBooking warehouseBooking = createWarehouseBooking(order); + + return new Result( + order, + warehouseBooking, + bookingList + ); + } + + private WarehouseBooking createWarehouseBooking(CustomerOrder order) throws ArticleNotInStockException { + WarehouseBooking booking = new WarehouseBooking(); + booking.created = new Timestamp(new Date().getTime()); + booking.reason = new BookingReason(order); + + for (OrderItem item : orderItems) { + int needed = item.quantity; + + for (WarehouseBookingPositionSlotEntry slot : item.availableSlots) { + int remove = Math.min(slot.newSumSlot, needed); + needed -= remove; + + WarehouseBookingPosition bookingPosition = new WarehouseBookingPosition(); + + bookingPosition.article = item.article; + bookingPosition.amount = -remove; + bookingPosition.slotEntry = slot.copyAddAmount(-remove); + bookingPosition.booking = booking; + + booking.positions.add(bookingPosition); + + if (needed == 0) { + break; + } + } + + if (needed > 0) { + throw new ArticleNotInStockException(item.article); + } + } + + return booking; + } + + private CustomerPayment createPayment() { + CustomerPayment payment = new CustomerPayment(); + payment.amountCent = totalNetCent + totalVatCent; + payment.payment = method; + return payment; + } + + + private CustomerOrder createOrder() { + assert totalNetCent + totalVatCent == expectedTotalGrossCent; + + CustomerOrder customerOrder = new CustomerOrder(); + customerOrder.customer = user; + customerOrder.destination = destination; + + for (OrderItem item : orderItems) { + CustomerOrderPosition position = new CustomerOrderPosition(); + position.article = item.article; + position.pricePerUnit = item.article.shopPricePerUnitNetCent; + position.quantity = item.quantity; + + position.order = customerOrder; + + customerOrder.positions.add(position); + } + + customerOrder.created = new Timestamp(new Date().getTime()); + + customerOrder.totalNetCent = totalNetCent; + customerOrder.totalVatCent = totalVatCent; + customerOrder.totalGrossCent = totalNetCent + totalVatCent; + + return customerOrder; + } + + public static class Result { + public final CustomerOrder customerOrder; + public final WarehouseBooking warehouseBooking; + public final List<Booking> bookings; + + Result(CustomerOrder customerOrder, WarehouseBooking warehouseBooking, List<Booking> bookings) { + this.customerOrder = customerOrder; + this.warehouseBooking = warehouseBooking; + this.bookings = bookings; + } + } + + private static class OrderItem { + List<WarehouseBookingPositionSlotEntry> availableSlots; + int quantity; + Article article; + + public OrderItem(Article article, List<WarehouseBookingPositionSlotEntry> availableSlots, int quantity) { + this.article = article; + this.availableSlots = availableSlots; + this.quantity = quantity; + } + } + + public static class ArticleNotInStockException extends Exception { + private Article article; + + public ArticleNotInStockException(Article article) { + super("The quantity of article '" + article.title + "' is not in stock."); + this.article = article; + } + + public Article getArticle() { + return article; + } + } +} diff --git a/prototype/src/main/java/org/hso/ecommerce/action/shop/EnableTrackingAction.java b/prototype/src/main/java/org/hso/ecommerce/action/shop/EnableTrackingAction.java new file mode 100644 index 0000000..d3e2dc7 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/action/shop/EnableTrackingAction.java @@ -0,0 +1,11 @@ +package org.hso.ecommerce.action.shop; + +import org.hso.ecommerce.entities.shop.CustomerOrder; + +public class EnableTrackingAction { + + public static void addTrackingInfo(CustomerOrder customerOrder) { + // TODO: + customerOrder.trackingId = "555-NASE"; + } +} diff --git a/prototype/src/main/java/org/hso/ecommerce/action/shop/GetRandomArticlesAction.java b/prototype/src/main/java/org/hso/ecommerce/action/shop/GetRandomArticlesAction.java new file mode 100644 index 0000000..62c70c7 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/action/shop/GetRandomArticlesAction.java @@ -0,0 +1,19 @@ +package org.hso.ecommerce.action.shop; + +import org.hso.ecommerce.entities.shop.Article; + +import java.util.ArrayList; +import java.util.List; + +public class GetRandomArticlesAction { + + public static List<Article> getRandomArticles(int quantity, List<Article> advertisedArticles) { + List<Article> randomisedArticles = new ArrayList<Article>(); + int loopcount = Math.min(quantity, advertisedArticles.size()); + for (int i = 0; i < loopcount; i++) { + int index = (int) (Math.random() * advertisedArticles.size()); + randomisedArticles.add(advertisedArticles.remove(index)); + } + return randomisedArticles; + } +} diff --git a/prototype/src/main/java/org/hso/ecommerce/action/somepackage/DemoAction.java b/prototype/src/main/java/org/hso/ecommerce/action/somepackage/DemoAction.java deleted file mode 100644 index 22f8946..0000000 --- a/prototype/src/main/java/org/hso/ecommerce/action/somepackage/DemoAction.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.hso.ecommerce.action.somepackage; - -public class DemoAction { - // TODO: remove me. - // mksubpackage - -} diff --git a/prototype/src/main/java/org/hso/ecommerce/app/Config.java b/prototype/src/main/java/org/hso/ecommerce/app/Config.java index 7eda415..961f7ba 100644 --- a/prototype/src/main/java/org/hso/ecommerce/app/Config.java +++ b/prototype/src/main/java/org/hso/ecommerce/app/Config.java @@ -3,6 +3,7 @@ package org.hso.ecommerce.app; import org.hso.ecommerce.components.ErrorDemoInterceptor; import org.hso.ecommerce.components.InfoDemoInterceptor; import org.hso.ecommerce.components.LoginIntercepter; +import org.hso.ecommerce.components.ShoppingCartInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -23,5 +24,7 @@ public class Config implements WebMvcConfigurer { registry.addInterceptor(buildLoginIntercepter()); registry.addInterceptor(new ErrorDemoInterceptor()); registry.addInterceptor(new InfoDemoInterceptor()); + registry.addInterceptor(new ShoppingCartInterceptor()); + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/app/RequestController.java b/prototype/src/main/java/org/hso/ecommerce/app/RequestController.java index 3602236..3adf74e 100644 --- a/prototype/src/main/java/org/hso/ecommerce/app/RequestController.java +++ b/prototype/src/main/java/org/hso/ecommerce/app/RequestController.java @@ -1,10 +1,12 @@ package org.hso.ecommerce.app; -import org.hso.ecommerce.repos.user.UserRepository; import org.hso.ecommerce.entities.user.User; +import org.hso.ecommerce.repos.user.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -22,11 +24,6 @@ public class RequestController { static int notSoRandom = 0; - @GetMapping("/") - public String home() { - return "redirect:/shop/"; - } - @GetMapping("/login") public String login() { return "login"; @@ -43,7 +40,7 @@ public class RequestController { String gto = (String) session.getAttribute("afterLogin"); Optional<User> user = userRepository.findByEmail(username); - if (user.isEmpty()) { + if (!user.isPresent()) { request.setAttribute("error", "Email Adresse falsch."); response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); return "login"; @@ -88,71 +85,11 @@ public class RequestController { return "redirect:/"; } - @GetMapping("/shop/") - public String shop() { - return "shop/index"; - } - @GetMapping("/shop/search") public String shopSearch() { return "shop/search"; } - @GetMapping("/shop/checkout") - public String shopCheckout(HttpSession session, HttpServletRequest request) { - session.setAttribute("afterLogin", request.getRequestURI()); - return "shop/checkout"; - } - - @PostMapping("/shop/checkoutFinish") - public String shopCheckoutFinish() { - return "shop/checkoutFinish"; - } - - @GetMapping("/shop/checkoutFinish") - public String shopCheckoutFinishGET() { - return "shop/checkoutFinish"; - } - - @GetMapping("/shop/articles/{id}") - public String shopArticlesById() { - return "shop/articles/id"; - } - - @PostMapping("/shop/articles/{id}") - public String shopArticlesByIdBuy(HttpSession session, - @RequestAttribute(value = "user", required = false) User customer, - @PathVariable("id") Integer id, - @RequestParam("fastcheckout") Boolean fastcheckout - ) { - if (customer != null) { - if (!fastcheckout) { - return "shop/articles/post_add"; - } else { - return "shop/checkout"; - } - } else { - session.setAttribute("afterLogin", "/shop/articles/" + id); - return "redirect:/login"; - } - } - - @GetMapping("/about") - public String about() { - return "about"; - } - - @GetMapping("/terms") - public String terms() { - return "terms"; - } - - @GetMapping("/privacy") - public String privacy() { - return "privacy"; - } - - @GetMapping("/intern/") public String intern() { return "intern/index"; diff --git a/prototype/src/main/java/org/hso/ecommerce/components/ShoppingCartInterceptor.java b/prototype/src/main/java/org/hso/ecommerce/components/ShoppingCartInterceptor.java new file mode 100644 index 0000000..3a67897 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/components/ShoppingCartInterceptor.java @@ -0,0 +1,43 @@ +package org.hso.ecommerce.components; + +import org.hso.ecommerce.entities.shop.ShoppingCart; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +public class ShoppingCartInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + HttpSession session = request.getSession(); + Object shoppingCart = session.getAttribute("shoppingCart"); + + if (shoppingCart == null) { + shoppingCart = new ShoppingCart(); + } + + request.setAttribute("shoppingCart", shoppingCart); + + return true; + } + + @Override + public void postHandle( + HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) throws Exception { + + HttpSession session = request.getSession(); + Object shoppingCart = request.getAttribute("shoppingCart"); + session.setAttribute("shoppingCart", shoppingCart); + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception exception) throws Exception { + } +} diff --git a/prototype/src/main/java/org/hso/ecommerce/components/SlotInitializer.java b/prototype/src/main/java/org/hso/ecommerce/components/SlotInitializer.java new file mode 100644 index 0000000..ce8c39b --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/components/SlotInitializer.java @@ -0,0 +1,32 @@ +package org.hso.ecommerce.components; + +import org.hso.ecommerce.entities.warehouse.Slot; +import org.hso.ecommerce.repos.warehouse.SlotRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +@Component +public class SlotInitializer { + + @Autowired + private final SlotRepository slotRepository = null; + + // TODO: use values form cfg. + private final int NUM_SLOTS = 50; + + @PostConstruct + public void init() { + for (int i = 1; i <= NUM_SLOTS; i++) { + if (!slotRepository.findBySlotNum(i).isPresent()) { + Slot slotAdded = new Slot(); + slotAdded.slotNum = i; + slotRepository.save(slotAdded); + + System.out.println("Added Slot " + i + " to DB"); + } + } + } + +} diff --git a/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopArticleController.java b/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopArticleController.java index 27ccf63..2fc0aa2 100644 --- a/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopArticleController.java +++ b/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopArticleController.java @@ -1,8 +1,103 @@ package org.hso.ecommerce.controller.shop; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.hso.ecommerce.action.shop.GetRandomArticlesAction; +import org.hso.ecommerce.entities.shop.Article; +import org.hso.ecommerce.entities.shop.ShoppingCart; +import org.hso.ecommerce.repos.shop.ArticleRepository; +import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; @Controller -//@RequestMapping("...") +@RequestMapping("/shop/articles") public class ShopArticleController { + + @Autowired + private final ArticleRepository articleRepository = null; + + @Autowired + private final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null; + + @GetMapping("/{id}") + public String shopArticlesById(Model model, + @PathVariable("id") Long id, + HttpServletRequest request, + HttpServletResponse response + ) { + Article article = articleRepository.findArticleById(id); + + if (article == null) { + request.setAttribute("error", "Der Artikel wurde nicht gefunden."); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return "error/404"; + } + model.addAttribute("article", article); + + //if (warehouseBookingPositionSlotEntryRepository.getByArticle(id).get(0).newSumSlot > 0) { //TODO: use this as soon as warehouse works + if (true) { + model.addAttribute("inStock", true); + } else { + model.addAttribute("inStock", false); + } + + List<Article> commercialArticles = GetRandomArticlesAction.getRandomArticles(3, articleRepository.getAdvertisedArticles()); + model.addAttribute("commercialArticles", commercialArticles); + + return "shop/articles/id"; + } + + @PostMapping("/{id}") + public String shopArticlesByIdBuy(HttpServletRequest request, + HttpServletResponse response, + HttpSession session, + @RequestAttribute(value = "shoppingCart") ShoppingCart shoppingCart, + @PathVariable("id") Long id, + @RequestParam("quantity") Integer quantity, + @RequestParam(value = "set_amount", required = false) Boolean setAmount, + @RequestParam("fastcheckout") Boolean fastcheckout + ) { + Article article = articleRepository.findArticleById(id); + + if (article == null) { + request.setAttribute("error", "Der Artikel wurde nicht gefunden."); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return "error/404"; + } + + if (setAmount != null && setAmount) { + shoppingCart.setArticleCount(article, quantity); + } else { + shoppingCart.addArticle(article, quantity); + } + + if (!fastcheckout) { + return "shop/articles/post_add"; + } else { + return "redirect:/shop/checkout"; + } + } + + @GetMapping("/{id}/image.jpg") + public void getImageAsByteArray(HttpServletRequest request, + HttpServletResponse response, + @PathVariable("id") Long id + ) throws IOException { + Article article = articleRepository.findArticleById(id); + InputStream in = new FileInputStream(article.image.path); + response.setContentType(MediaType.IMAGE_JPEG_VALUE); + IOUtils.copy(in, response.getOutputStream()); + } } + diff --git a/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopCheckoutController.java b/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopCheckoutController.java index dd127da..c2c1c3a 100644 --- a/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopCheckoutController.java +++ b/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopCheckoutController.java @@ -1,8 +1,160 @@ package org.hso.ecommerce.controller.shop; +import org.hso.ecommerce.action.shop.CreateOrderAction; +import org.hso.ecommerce.action.shop.EnableTrackingAction; +import org.hso.ecommerce.entities.booking.BookingAccountEntry; +import org.hso.ecommerce.entities.booking.PaymentMethod; +import org.hso.ecommerce.entities.shop.Address; +import org.hso.ecommerce.entities.shop.Article; +import org.hso.ecommerce.entities.shop.ShoppingCart; +import org.hso.ecommerce.entities.user.User; +import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository; +import org.hso.ecommerce.repos.booking.BookingRepository; +import org.hso.ecommerce.repos.shop.ArticleRepository; +import org.hso.ecommerce.repos.shop.CustomerOderRepository; +import org.hso.ecommerce.repos.user.UserRepository; +import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository; +import org.hso.ecommerce.repos.warehouse.WarehouseBookingRepository; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.ArrayList; +import java.util.TreeMap; @Controller -//@RequestMapping("...") +@RequestMapping("/shop/") public class ShopCheckoutController { + + @Autowired + private final UserRepository userRepository = null; + + @Autowired + private final ArticleRepository articleRepository = null; + + @Autowired + private final BookingAccountEntryRepository bookingEntryRepository = null; + + @Autowired + private final BookingRepository bookingRepository = null; + + @Autowired + private final WarehouseBookingRepository warehouseBookingRepository = null; + + @Autowired + private final CustomerOderRepository customerOderRepository = null; + + @Autowired + private final WarehouseBookingPositionSlotEntryRepository wbeseRepo = null; + + @GetMapping("/checkout") + public String shopCheckout(HttpSession session, + HttpServletRequest request, + @RequestAttribute(value = "shoppingCart") ShoppingCart shoppingCart) { + session.setAttribute("afterLogin", request.getRequestURI()); + + CheckoutListTotals totals = new CheckoutListTotals(); + ArrayList<CheckoutListItem> items = new ArrayList<>(); + for (ShoppingCart.ShoppingCartItem item : shoppingCart.getItems()) { + Article article = articleRepository.findById(item.getArticleId()).get(); + + totals.addItem(article, item.getAmount()); + items.add(new CheckoutListItem(item.getAmount(), article)); + } + + request.setAttribute("checkoutItems", items); + request.setAttribute("checkoutTotals", totals); + + return "shop/checkout"; + } + + public static class CheckoutListTotals { + public int net; + public TreeMap<Integer, Integer> vatAmounts = new TreeMap<>(); + public int total; + + void addItem(Article article, int amount) { + net += article.shopPricePerUnitNetCent * amount; + Integer vatPos = vatAmounts.getOrDefault(article.related.vatPercent, 0) + article.getVat() * amount; + vatAmounts.put(article.related.vatPercent, vatPos); + total += article.getPriceGross() * amount; + } + } + + public static class CheckoutListItem { + public int amount; + public Article article; + public int total; + + public CheckoutListItem(int amount, Article article) { + this.amount = amount; + this.article = article; + this.total = amount * article.getPriceGross(); + } + } + + @PostMapping("/checkoutFinish") + public String shopCheckoutFinish( + HttpSession session, + HttpServletRequest request, + HttpServletResponse response, + @RequestAttribute(value = "user") User user, + @RequestAttribute(value = "shoppingCart") ShoppingCart shoppingCart, + @RequestParam("address") String address, + @RequestParam("cardnumber") String cardnumber, + @RequestParam("shopping_cart_revision") Integer cartRevision, + @RequestParam("expected_total") Integer expectedPrice + ) { + + if (shoppingCart.getRevision() != cartRevision) { + request.setAttribute("error", "Der Warenkorb wurde zwischenzeitlich bearbeitet. Daher die Kaufvorgang nicht abgeschlossen werden. Bitte versuchen Sie es erneut."); + response.setStatus(HttpServletResponse.SC_CONFLICT); + return "shop/checkout"; + } + + // Must be refetched for persitence. + user = userRepository.findById(user.id).get(); + + CreateOrderAction action = new CreateOrderAction( + user, + expectedPrice, + Address.fromString(address), + PaymentMethod.fromCreditCarNumber(cardnumber), + bookingEntryRepository.getByUser(user.id).orElse(BookingAccountEntry.newUser(user)), + bookingEntryRepository.getByVat().orElse(BookingAccountEntry.newVat()), + bookingEntryRepository.getByMain().orElse(BookingAccountEntry.newMain()) + ); + + for (ShoppingCart.ShoppingCartItem item : shoppingCart.getItems()) { + Article article = articleRepository.findById(item.getArticleId()).get(); + action.addArticle(article, item.getAmount(), wbeseRepo.getByArticle(article.id)); + } + + CreateOrderAction.Result result = null; + try { + result = action.finish(); + EnableTrackingAction.addTrackingInfo(result.customerOrder); + + customerOderRepository.save(result.customerOrder); + bookingRepository.saveAll(result.bookings); + warehouseBookingRepository.save(result.warehouseBooking); + + shoppingCart.clear(); + + } catch (CreateOrderAction.ArticleNotInStockException e) { + request.setAttribute("error", "Der Artikel '" + e.getArticle().title + "' ist leider nicht mehr in ausreichender Menge verfügbar. Bitte passen Sie die Artikelmenge an."); + return shopCheckout(session, request, shoppingCart); + } + + + return "shop/checkoutFinish"; + } + + @GetMapping("/checkoutFinish") + public String shopCheckoutFinishGET() { + return "shop/checkoutFinish"; + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopIndexController.java b/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopIndexController.java index 0f423db..15b6067 100644 --- a/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopIndexController.java +++ b/prototype/src/main/java/org/hso/ecommerce/controller/shop/ShopIndexController.java @@ -1,8 +1,69 @@ package org.hso.ecommerce.controller.shop; +import org.hso.ecommerce.action.shop.GetRandomArticlesAction; +import org.hso.ecommerce.entities.shop.Article; +import org.hso.ecommerce.repos.shop.ArticleRepository; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.HttpSession; +import java.util.List; @Controller -//@RequestMapping("...") +@RequestMapping("/") public class ShopIndexController { + + @Autowired + private final ArticleRepository articleRepository = null; + + @GetMapping("/") + public String home() { + return "redirect:/shop/"; + } + + @GetMapping("/shop/") + public String shop(Model model, HttpSession session) { + + List<Article> commercialArticles = GetRandomArticlesAction.getRandomArticles(8, articleRepository.getAdvertisedArticles()); + model.addAttribute("commercialArticles", commercialArticles); + + boolean isLoggedIn = false; + boolean hasOrders = false; + + if (session != null && session.getAttribute("userId") != null) { + long userId = (long) session.getAttribute("userId"); + isLoggedIn = true; + + List<Article> suggestedArticles = articleRepository.getOrderedArticles(userId); + suggestedArticles = suggestedArticles.size() > 3 ? suggestedArticles.subList(0, 4) : suggestedArticles; //only latest 4 articles + if (suggestedArticles.size() > 0) { + model.addAttribute("suggestedArticles", suggestedArticles); + hasOrders = true; + } + } + + model.addAttribute("isLoggedIn", isLoggedIn); + model.addAttribute("hasOrders", hasOrders); + + return "shop/index"; + } + + @GetMapping("/about") + public String about() { + return "about"; + } + + @GetMapping("/terms") + public String terms() { + return "terms"; + } + + @GetMapping("/privacy") + public String privacy() { + return "privacy"; + } + } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/booking/Booking.java b/prototype/src/main/java/org/hso/ecommerce/entities/booking/Booking.java index 997f578..e61ea07 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/booking/Booking.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/booking/Booking.java @@ -14,12 +14,12 @@ public class Booking { // always >= 0 public int amountCent; - @ManyToOne(optional = true) + @ManyToOne(optional = true, cascade = CascadeType.ALL) public BookingAccountEntry source; - @ManyToOne(optional = true) + @ManyToOne(optional = true, cascade = CascadeType.ALL) public BookingAccountEntry destination; - @OneToOne(optional = false) + @OneToOne(optional = false, cascade = CascadeType.ALL) public BookingReason reason; } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingAccountEntry.java b/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingAccountEntry.java index 92e282f..04b3b8a 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingAccountEntry.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingAccountEntry.java @@ -16,13 +16,49 @@ public class BookingAccountEntry { public int newSumCent; - @ManyToOne(optional = true) + @ManyToOne(optional = true, cascade = CascadeType.ALL) public User userAccount; - @ManyToOne(optional = true) + @ManyToOne(optional = true, cascade = CascadeType.ALL) public Supplier supplierAccount; public boolean isMainAccount; public boolean isVATAccount; + public BookingAccountEntry copyAddAmount(int amountCent) { + BookingAccountEntry e = new BookingAccountEntry(); + + e.userAccount = userAccount; + e.supplierAccount = supplierAccount; + e.isMainAccount = isMainAccount; + e.isVATAccount = isVATAccount; + + e.newSumCent = newSumCent + amountCent; + + return e; + } + + public static BookingAccountEntry newUser(User user) { + BookingAccountEntry e = new BookingAccountEntry(); + e.userAccount = user; + e.newSumCent = 0; + + return e; + } + + public static BookingAccountEntry newMain() { + BookingAccountEntry e = new BookingAccountEntry(); + e.isMainAccount = true; + e.newSumCent = 0; + + return e; + } + + public static BookingAccountEntry newVat() { + BookingAccountEntry e = new BookingAccountEntry(); + e.isVATAccount = true; + e.newSumCent = 0; + + return e; + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingReason.java b/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingReason.java index b2bc6cc..78e8ab5 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingReason.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/booking/BookingReason.java @@ -22,9 +22,21 @@ public class BookingReason { @ManyToOne(optional = true) public CustomerOrder customerOrder; - @ManyToOne(optional = true) + @OneToOne(optional = true, cascade = CascadeType.ALL) public CustomerPayment customerPayment; @ManyToOne(optional = true) public SupplierOrder supplierOrder; + + // Default Constructor is needed for construction by ORM + public BookingReason() { + } + + public BookingReason(CustomerOrder order) { + this.customerOrder = order; + } + + public BookingReason(CustomerPayment customerPayment) { + this.customerPayment = customerPayment; + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/booking/PaymentMethod.java b/prototype/src/main/java/org/hso/ecommerce/entities/booking/PaymentMethod.java index 6cb5307..4f81062 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/booking/PaymentMethod.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/booking/PaymentMethod.java @@ -7,4 +7,11 @@ import javax.validation.constraints.NotNull; public class PaymentMethod { @NotNull public String creditCardNumber; + + public static PaymentMethod fromCreditCarNumber(String cardnumber) { + PaymentMethod m = new PaymentMethod(); + m.creditCardNumber = cardnumber; + + return m; + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/shop/Address.java b/prototype/src/main/java/org/hso/ecommerce/entities/shop/Address.java index 32d057b..2f76083 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/shop/Address.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/shop/Address.java @@ -4,7 +4,24 @@ import javax.persistence.Embeddable; @Embeddable public class Address { - public String name; - public String addressString; + public String name = ""; + public String addressString = ""; public String country = "DE"; + + @Override + public String toString() { + return name + "\n" + addressString; + } + + public static Address fromString(String addr) { + Address a = new Address(); + + String[] arr = addr.split("\n", 2); + a.name = arr[0]; + if (arr.length > 1) { + a.addressString = arr[1]; + } + + return a; + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/shop/Article.java b/prototype/src/main/java/org/hso/ecommerce/entities/shop/Article.java index 6cd198e..a26cb39 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/shop/Article.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/shop/Article.java @@ -32,9 +32,18 @@ public class Article { public String description; @OneToOne(optional = true) + @Basic(fetch = FetchType.LAZY) public Image image; @ManyToMany @JoinTable(name = "article_categories_bindings") public Set<Category> categories = new HashSet<>(); + + public int getVat() { + return (shopPricePerUnitNetCent * related.vatPercent) / 100; + } + + public int getPriceGross() { + return shopPricePerUnitNetCent + getVat(); + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/shop/CustomerOrder.java b/prototype/src/main/java/org/hso/ecommerce/entities/shop/CustomerOrder.java index dd7d4f8..1b20bb2 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/shop/CustomerOrder.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/shop/CustomerOrder.java @@ -24,7 +24,7 @@ public class CustomerOrder { @OneToMany( targetEntity = CustomerOrderPosition.class, - mappedBy = "order" + mappedBy = "order", cascade = CascadeType.ALL ) public List<CustomerOrderPosition> positions = new ArrayList<>(); diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/shop/Image.java b/prototype/src/main/java/org/hso/ecommerce/entities/shop/Image.java index 33eebdb..6609853 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/shop/Image.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/shop/Image.java @@ -11,7 +11,5 @@ public class Image { @Basic public long id; - @Lob - @Column(name = "data", columnDefinition = "BLOB", nullable = false) - private byte[] data; -} + public String path; +} \ No newline at end of file diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/shop/ShoppingCart.java b/prototype/src/main/java/org/hso/ecommerce/entities/shop/ShoppingCart.java new file mode 100644 index 0000000..d059f15 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/entities/shop/ShoppingCart.java @@ -0,0 +1,103 @@ +package org.hso.ecommerce.entities.shop; + +import java.util.ArrayList; +import java.util.List; + +// Not a db entity. Just for session storage +public class ShoppingCart { + + private final static int MAX_ITEMS = 10; + + private int revision; + private ArrayList<ShoppingCartItem> items; + + public ShoppingCart() { + clear(); + } + + public void clear() { + items = new ArrayList<>(); + revision = (int) Math.round(Math.random() * 0xFFFF); + } + + public List<ShoppingCartItem> getItems() { + return items; + } + + public int getItemCount() { + int count = 0; + + for (ShoppingCartItem i : items) { + count += i.getAmount(); + } + + return count; + } + + public int getRevision() { + return revision; + } + + public void addArticle(Article article, int quantity) { + this.revision++; + + for (ShoppingCartItem i : items) { + if (i.getArticleId() == article.id) { + i.addAmount(quantity); + return; + } + } + + items.add(new ShoppingCartItem(quantity, article)); + } + + public void setArticleCount(Article article, Integer quantity) { + this.revision++; + + boolean found = false; + for (ShoppingCartItem i : items) { + if (i.getArticleId() == article.id) { + i.setAmount(quantity); + found = true; + break; + } + } + if (!found) { + items.add(new ShoppingCartItem(quantity, article)); + } + + items.removeIf(i -> i.getAmount() <= 0); + } + + public static class ShoppingCartItem { + private int amount; + private final long articleId; + + public ShoppingCartItem(int amount, Article article) { + this.amount = amount; + this.articleId = article.id; + } + + public int getAmount() { + return amount; + } + + public void addAmount(int amount) { + this.amount += amount; + if (this.amount > MAX_ITEMS) { + this.amount = MAX_ITEMS; + } + } + + public long getArticleId() { + return articleId; + } + + public void setAmount(Integer amount) { + this.amount = amount; + if (this.amount > MAX_ITEMS) { + this.amount = MAX_ITEMS; + } + } + } +} diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/supplier/ArticleOffer.java b/prototype/src/main/java/org/hso/ecommerce/entities/supplier/ArticleOffer.java index 005b0b9..ae86659 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/supplier/ArticleOffer.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/supplier/ArticleOffer.java @@ -19,4 +19,6 @@ public class ArticleOffer { public String articleNumber; public int vatPercent; + + public boolean shouldBeAdvertised; } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/user/User.java b/prototype/src/main/java/org/hso/ecommerce/entities/user/User.java index 9b07996..784d89a 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/user/User.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/user/User.java @@ -30,10 +30,10 @@ public class User { public boolean isEmployee; @Embedded - private Address defaultDeliveryAddress; + public Address defaultDeliveryAddress; @Embedded - private PaymentMethod defaultPayment; + public PaymentMethod defaultPayment; public long getId() { return id; diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/Slot.java b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/Slot.java new file mode 100644 index 0000000..6a56541 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/Slot.java @@ -0,0 +1,17 @@ +package org.hso.ecommerce.entities.warehouse; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; + +@Entity +@Table(name = "warehouse_slots") +public class Slot { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Basic + public long id; + + @NotNull + @Column(unique = true) + public int slotNum; +} diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBooking.java b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBooking.java index 97a2805..08eaa40 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBooking.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBooking.java @@ -1,5 +1,7 @@ package org.hso.ecommerce.entities.warehouse; +import org.hso.ecommerce.entities.booking.BookingReason; + import javax.persistence.*; import javax.validation.constraints.NotNull; import java.util.ArrayList; @@ -21,7 +23,11 @@ public class WarehouseBooking { public boolean isDone; @OneToMany( - mappedBy = "booking" + mappedBy = "booking", cascade = CascadeType.ALL ) public List<WarehouseBookingPosition> positions = new ArrayList<>(); + + // TODO FIX ME + @OneToOne(optional = false, cascade = CascadeType.ALL) + public BookingReason reason; } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPosition.java b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPosition.java index 88ceee3..e3a147a 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPosition.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPosition.java @@ -23,10 +23,6 @@ public class WarehouseBookingPosition { public int amount; // positive or negative - @ManyToOne(optional = true) - public WarehouseBookingPositionSlotEntry source; - - @ManyToOne(optional = true) - public WarehouseBookingPositionSlotEntry destination; - + @ManyToOne(optional = true, cascade = CascadeType.ALL) + public WarehouseBookingPositionSlotEntry slotEntry; } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPositionSlotEntry.java b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPositionSlotEntry.java index 7529e4f..444b921 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPositionSlotEntry.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingPositionSlotEntry.java @@ -3,6 +3,7 @@ package org.hso.ecommerce.entities.warehouse; import org.hso.ecommerce.entities.shop.Article; import javax.persistence.*; +import javax.validation.constraints.NotNull; @Entity @Table(name = "warehouse_booking_position_entries") @@ -13,11 +14,28 @@ public class WarehouseBookingPositionSlotEntry { @Basic public long id; + @NotNull @ManyToOne public Article article; - public int newSumArticles; - public int newSumWarehousePosition; + @NotNull + public int newSumSlot; - public int slot; + @NotNull + @ManyToOne + public Slot slot; + + public WarehouseBookingPositionSlotEntry copyAddAmount(int amount) { + WarehouseBookingPositionSlotEntry e = new WarehouseBookingPositionSlotEntry(); + + e.article = article; + e.slot = slot; + + e.newSumSlot = newSumSlot + amount; + + assert e.article.warehouseUnitsPerSlot >= e.newSumSlot; + assert e.newSumSlot >= 0; + + return e; + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingReason.java b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingReason.java index ec60c4d..e287141 100644 --- a/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingReason.java +++ b/prototype/src/main/java/org/hso/ecommerce/entities/warehouse/WarehouseBookingReason.java @@ -23,4 +23,12 @@ public class WarehouseBookingReason { public CustomerOrder customerOrder; public boolean isManuel; + + // Default Constructor is needed for construction by ORM + public WarehouseBookingReason() { + } + + public WarehouseBookingReason(CustomerOrder order) { + this.customerOrder = order; + } } diff --git a/prototype/src/main/java/org/hso/ecommerce/repos/booking/BookingAccountEntryRepository.java b/prototype/src/main/java/org/hso/ecommerce/repos/booking/BookingAccountEntryRepository.java new file mode 100644 index 0000000..d842a3a --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/repos/booking/BookingAccountEntryRepository.java @@ -0,0 +1,25 @@ +package org.hso.ecommerce.repos.booking; + +import org.hso.ecommerce.entities.booking.BookingAccountEntry; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface BookingAccountEntryRepository extends JpaRepository<BookingAccountEntry, Long> { + + @Query(value = "SELECT * FROM booking_account_entries as e WHERE e.user_account_id = :user ORDER BY e.id DESC LIMIT 1", nativeQuery = true) + Optional<BookingAccountEntry> getByUser(Long user); + + @Query(value = "SELECT * FROM booking_account_entries as e WHERE e.is_main_account = 1 ORDER BY e.id DESC LIMIT 1", nativeQuery = true) + Optional<BookingAccountEntry> getByMain(); + + @Query(value = "SELECT * FROM booking_account_entries as e WHERE e.isvataccount = 1 ORDER BY e.id DESC LIMIT 1", nativeQuery = true) + Optional<BookingAccountEntry> getByVat(); + + +} + + diff --git a/prototype/src/main/java/org/hso/ecommerce/repos/booking/BookingRepository.java b/prototype/src/main/java/org/hso/ecommerce/repos/booking/BookingRepository.java new file mode 100644 index 0000000..454b3f3 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/repos/booking/BookingRepository.java @@ -0,0 +1,12 @@ +package org.hso.ecommerce.repos.booking; + +import org.hso.ecommerce.entities.booking.Booking; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BookingRepository extends JpaRepository<Booking, Long> { + +} + + diff --git a/prototype/src/main/java/org/hso/ecommerce/repos/shop/ArticleRepository.java b/prototype/src/main/java/org/hso/ecommerce/repos/shop/ArticleRepository.java new file mode 100644 index 0000000..0b108f1 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/repos/shop/ArticleRepository.java @@ -0,0 +1,27 @@ +package org.hso.ecommerce.repos.shop; + +import org.hso.ecommerce.entities.shop.Article; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ArticleRepository extends JpaRepository<Article, Long> { + + @Query("SELECT a FROM Article a WHERE a.id = :articleId") + Article findArticleById(@Param("articleId") long articleId); + + + @Query("SELECT a FROM Article a JOIN a.related ao WHERE ao.shouldBeAdvertised = true") + List<Article> getAdvertisedArticles(); + + + @Query("SELECT a FROM CustomerOrderPosition cop JOIN cop.order co JOIN co.customer c JOIN cop.article a WHERE c.id = :customerId ORDER BY co.id DESC") + List<Article> getOrderedArticles(long customerId); + + +} + diff --git a/prototype/src/main/java/org/hso/ecommerce/repos/shop/CustomerOderRepository.java b/prototype/src/main/java/org/hso/ecommerce/repos/shop/CustomerOderRepository.java new file mode 100644 index 0000000..407f45c --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/repos/shop/CustomerOderRepository.java @@ -0,0 +1,11 @@ +package org.hso.ecommerce.repos.shop; + +import org.hso.ecommerce.entities.shop.CustomerOrder; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CustomerOderRepository extends JpaRepository<CustomerOrder, Long> { + +} + diff --git a/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/SlotRepository.java b/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/SlotRepository.java new file mode 100644 index 0000000..d0f00c0 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/SlotRepository.java @@ -0,0 +1,17 @@ +package org.hso.ecommerce.repos.warehouse; + +import org.hso.ecommerce.entities.warehouse.Slot; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface SlotRepository extends JpaRepository<Slot, Long> { + + @Query("SELECT s FROM Slot s WHERE s.slotNum = :slotNum") + Optional<Slot> findBySlotNum(int slotNum); + + +} diff --git a/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/WarehouseBookingPositionSlotEntryRepository.java b/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/WarehouseBookingPositionSlotEntryRepository.java new file mode 100644 index 0000000..c3eab4b --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/WarehouseBookingPositionSlotEntryRepository.java @@ -0,0 +1,17 @@ +package org.hso.ecommerce.repos.warehouse; + +import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface WarehouseBookingPositionSlotEntryRepository extends JpaRepository<WarehouseBookingPositionSlotEntry, Long> { + + @Query(value = "Select e.id, e.article_id, e.new_sum_slot, e.slot_id from warehouse_booking_position_entries as e, warehouse_slots as s where e.slot_id = s.id AND e.article_id = :article GROUP BY s.slot_num HAVING max(e.id)", nativeQuery = true) + List<WarehouseBookingPositionSlotEntry> getByArticle(long article); + +} + diff --git a/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/WarehouseBookingRepository.java b/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/WarehouseBookingRepository.java new file mode 100644 index 0000000..8d01092 --- /dev/null +++ b/prototype/src/main/java/org/hso/ecommerce/repos/warehouse/WarehouseBookingRepository.java @@ -0,0 +1,11 @@ +package org.hso.ecommerce.repos.warehouse; + +import org.hso.ecommerce.entities.warehouse.WarehouseBooking; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface WarehouseBookingRepository extends JpaRepository<WarehouseBooking, Long> { + +} + diff --git a/prototype/src/main/resources/application.properties b/prototype/src/main/resources/application.properties index b62e80f..18ec88c 100644 --- a/prototype/src/main/resources/application.properties +++ b/prototype/src/main/resources/application.properties @@ -3,7 +3,7 @@ spring.resources.cache.cachecontrol.maxAge=P0D # LOGGING logging.level.org.springframework.web=WARN # DATABASE -spring.datasource.url=jdbc:sqlite:./test.db +spring.datasource.url=jdbc:sqlite:./e-commerce.db spring.datasource.driverClassName=org.sqlite.JDBC spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLiteDialect spring.jpa.hibernate.ddl-auto=update diff --git a/prototype/src/main/resources/db/customers.sql b/prototype/src/main/resources/db/customers.sql deleted file mode 100644 index b1a108f..0000000 --- a/prototype/src/main/resources/db/customers.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE "customers" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "lastname" TEXT, - "firstname" TEXT, - "username" TEXT, - "password" TEXT -); diff --git a/prototype/src/main/resources/static/img/error_404_illustatus.svg b/prototype/src/main/resources/static/img/error_404_illustatus.svg index 452b2d5..d58cd65 100644 --- a/prototype/src/main/resources/static/img/error_404_illustatus.svg +++ b/prototype/src/main/resources/static/img/error_404_illustatus.svg @@ -1,277 +1,100 @@ -<svg id="mainImage_create" data-name="mainImage" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 171.2 81.5"> - <style id="svgStyle"> - @import url('https://fonts.googleapis.com/css?family=Merriweather'); - .changeColor { - fill: #4f86ed; - } - - #title { - font-size: 50%; - font-family: 'Merriweather', serif; - } - - .cls-1 { - opacity: 0.3; - } - - .cls-7 { - opacity: 0.8; - } - - .cls-2 { - fill: #fff; - } - - .cls-10, - .cls-11, - .cls-12, - .cls-14, - .cls-16, - .cls-3 { - fill: none; - } - - .cls-3 { - stroke: #5c7690; - } - - .cls-10, - .cls-11, - .cls-12, - .cls-3 { - stroke-miterlimit: 10; - } - - .cls-14, - .cls-15, - .cls-16, - .cls-3 { - stroke-width: 0.5px; - } - - .cls-4 { - fill: #ffe1d9; - } - - .cls-5 { - fill: #ffcfbf; - } - - .cls-6 { - fill: #fecbb6; - } - - .cls-9 { - fill: #fecb02; - } - - .cls-10, - .cls-12 { - stroke: #d26f51; - } - - .cls-10, - .cls-11 { - stroke-width: 0.38px; - } - - .cls-11 { - stroke: #000; - } - - .cls-12 { - stroke-width: 0.19px; - } - - .cls-13 { - opacity: 0.45; - } - - .cls-14, - .cls-15, - .cls-16 { - stroke: #b0bec5; - stroke-linejoin: round; - } - - .cls-15 { - fill: #edf0f2; - } - - .cls-16 { - stroke-linecap: round; - } - - .cls-17 { - font-family: 'PT Sans', sans-serif; - font-size: 49.87px; - font-weight: 700; - } - - .cls-18 { - fill: #fffdbb; - opacity: 0.5; - } - - - /*--------------------------- - SVG Animate - ---------------------------*/ - .earMove { - transition: all ease-in-out 2s; - transform-origin: 50% 50%; - animation: earmove 1.5s linear infinite alternate; - } - - .faceMove { - transition: all ease-in-out 2s; - transform-origin: 50% 50%; - animation: move 1.5s linear infinite alternate; - } - - .neckMove { - transition: all ease-in-out 2s; - transform-origin: 50% 50%; - animation: neck 1.5s linear infinite alternate; - } - - - @keyframes earmove { - 0% { - transform: translateX(-0.3px) translateY(0.6px); - } - 30% { - transform: translateX(-0.3px) translateY(0.6px); - } - - 60% { - transform: translateX(-0.7px) translateY(0px); - } - - 70% { - transform: translateX(-0.7px) translateY(-0.3px); - } - 100% { - transform: translateX(-0.7px) translateY(-0.3px); - } - } - - @keyframes move { - 0% { - transform: translateX(-0.3px) translateY(0.6px); - } - 30% { - transform: translateX(-0.3px) translateY(0.6px); - } - - 60% { - transform: translateX(2px) translateY(0px); - } - - 70% { - transform: translateX(2px) translateY(-0.3px); - } - 100% { - transform: translateX(2px) translateY(-0.3px); - } - } - - @keyframes neck { - 0% { - transform: translateY(0.7px); - } - 50% { - transform: translateY(0.7px); - } - 100% { - transform: translateY(0px); - } - } - </style> - <path id="c-1" class="changeColor cls-1" - d="M46.62,52.5c5.78,4.9,21.14,8.4,39.19,8.4s33.41-3.5,39.19-8.4c-5.78-4.9-21.14-8.4-39.19-8.4S52.41,47.6,46.62,52.5Z" - style="fill: rgb(0, 21, 255);"></path> - <path class="cls-2" - d="M99.73,47.71H68.65a7.13,7.13,0,0,0-7.13,7.13V60a152.58,152.58,0,0,0,24.3,1.83,157.87,157.87,0,0,0,21.05-1.35V54.84A7.13,7.13,0,0,0,99.73,47.71Z"></path> - <path class="cls-3" - d="M123.56,55.81C115,58.94,101.27,61,85.81,61c-26,0-47-5.71-47-12.76,0-3.45,5.05-6.58,13.25-8.88"></path> - <path class="cls-3" d="M55.37,38.47a140,140,0,0,1,30.44-3c26,0,47,5.71,47,12.76,0,2.4-2.44,4.65-6.69,6.57"></path> - <path class="cls-3" d="M53.41,38.95l.94-.24"></path> - <path class="cls-4" - d="M91.68,47.71l-.75-11.2L79.15,43.84l-1.69,3.87H75.79c0,3.36,3.76,6.08,8.4,6.08s8.4-2.72,8.4-6.08Z"></path> - <path class="cls-5 neckMove" - d="M78,46.53a27.19,27.19,0,0,0,6.41.82c3.1,0,7.11-2.19,7.11-2.19l-.42-6.2L79.15,43.84Z"></path> - <polygon class="earMove" points="92.59 32.22 92.59 28.5 76.77 27.71 76.77 32.22 92.59 32.22"></polygon> - <circle class="cls-6 earMove" cx="78.06" cy="34.04" r="2.47"></circle> - <path class="cls-4" - d="M81.74,57.06,60.63,49.72h0A6.72,6.72,0,1,0,57.7,62.49H93.25C93.25,56.78,81.74,57.06,81.74,57.06Z"></path> - <path class="cls-4" - d="M77.46,25H90.92a0,0,0,0,1,0,0V39.38a6.73,6.73,0,0,1-6.73,6.73h0a6.73,6.73,0,0,1-6.73-6.73V25A0,0,0,0,1,77.46,25Z"></path> - <rect id="c-2" class="changeColor cls-7" x="74.82" y="26.48" width="19.14" height="2.45" - transform="translate(1.29 -3.65) rotate(2.49)" style="fill: rgb(0, 21, 255);"></rect> - <path id="c-3" class="changeColor cls-7" - d="M84.36,18.69h.5a7.8,7.8,0,0,1,7.8,7.8v0a0,0,0,0,1,0,0H76.56a0,0,0,0,1,0,0v0A7.8,7.8,0,0,1,84.36,18.69Z" - transform="translate(1.06 -3.66) rotate(2.49)" style="fill: rgb(0, 21, 255);"></path> - <polygon id="c-4" class="changeColor cls-8" - points="82.44 23.89 92.18 24.32 92.59 24.34 92.48 26.84 80.96 26.33 82.44 23.89" - style="fill: rgb(0, 21, 255);"></polygon> - <circle class="cls-9 faceMove" cx="78.72" cy="23.73" r="3.73" - transform="translate(51.58 101.34) rotate(-87.51)"></circle> - <circle class="cls-2 faceMove" cx="78.72" cy="23.73" r="2.36" - transform="translate(51.58 101.34) rotate(-87.51)"></circle> - <circle class="cls-4 earMove" cx="90.92" cy="34.04" r="2.47"></circle> - <path class="cls-4" - d="M112.2,53l-9.87-21.92-3-5.48-11.86-.22,7.42,3.35H91.55l5.82,4.58,2,22.26h0A6.72,6.72,0,1,0,112.2,53Z"></path> - <ellipse class="faceMove" cx="80.09" cy="33.12" rx="0.53" ry="0.59"></ellipse> - <ellipse class="faceMove" cx="86.34" cy="33.12" rx="0.53" ry="0.59"></ellipse> - <polyline class="cls-10 faceMove" points="84.19 31.08 81.74 37.01 84.39 37.01"></polyline> - <path class="cls-10 faceMove" d="M83.06,40.36a4,4,0,0,1,2.75-1"></path> - <line class="cls-11 faceMove" x1="81.07" y1="30.33" x2="78.47" y2="30.58"></line> - <line class="cls-11 faceMove" x1="86.34" y1="30.45" x2="88.15" y2="31.08"></line> - <line class="cls-12" x1="106.86" y1="47.82" x2="110.99" y2="46.11"></line> - <line class="cls-12" x1="107.43" y1="49.9" x2="111.55" y2="48.19"></line> - <line class="cls-12" x1="107.99" y1="51.98" x2="112.11" y2="50.27"></line> - <g class="cls-13"> - <rect class="cls-14" x="85.81" y="2.46" width="10.77" height="3.5"></rect> - <rect class="cls-15" x="96.58" y="2.46" width="10.77" height="3.5"></rect> - <rect class="cls-14" x="92.19" y="5.95" width="10.77" height="3.5"></rect> - <line class="cls-16" x1="107.36" y1="5.95" x2="109.63" y2="5.95"></line> - <line class="cls-16" x1="110.68" y1="5.95" x2="111.57" y2="5.95"></line> - </g> - <g class="cls-13"> - <rect class="cls-16" x="125" y="23.12" width="10.77" height="3.5"></rect> - <rect class="cls-15" x="130.39" y="26.62" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="119.62" y="26.62" width="10.77" height="3.5"></rect> - <line class="cls-16" x1="141.16" y1="26.62" x2="145.73" y2="26.62"></line> - <line class="cls-16" x1="125" y1="23.12" x2="115.4" y2="23.12"></line> - <line class="cls-16" x1="117.95" y1="26.62" x2="115.4" y2="26.62"></line> - </g> - <g class="cls-13"> - <rect class="cls-16" x="39.34" y="16.12" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="39.34" y="23.11" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="50.11" y="23.11" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="50.11" y="16.12" width="10.77" height="3.5"></rect> - <rect class="cls-15" x="44" y="19.61" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="33.23" y="19.61" width="10.77" height="3.5"></rect> - <line class="cls-16" x1="60.89" y1="19.61" x2="65.51" y2="19.61"></line> - <line class="cls-16" x1="39.34" y1="16.12" x2="35.46" y2="16.12"></line> - <line class="cls-16" x1="36.45" y1="26.61" x2="33.23" y2="26.61"></line> - <line class="cls-16" x1="63.2" y1="23.11" x2="65.51" y2="23.11"></line> - </g> - <polyline class="cls-3" points="115.4 58.12 115.4 38.27 120.2 37.01"></polyline> - <polyline class="cls-3" points="129.01 53.21 129.01 43.14 131.74 42.13"></polyline> - <path class="cls-3" d="M115.4,42.13a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,47"></path> - <path class="cls-3" d="M115.4,47.34a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,52.22"></path> - <path class="cls-3" d="M115.4,52.56a53.27,53.27,0,0,1,8,2l1,.42"></path> - <path class="cls-18 faceMove" - d="M78.84,26.09l0-4.71L68.05,18.32a.91.91,0,0,0-.45-.13c-1.17,0-2.11,2.46-2.11,5.5s.95,5.5,2.11,5.5a.9.9,0,0,0,.44-.12Z"></path> - <path class="cls-5" d="M57.7,62.49H93.25A3.67,3.67,0,0,0,92.92,61H53.43A6.69,6.69,0,0,0,57.7,62.49Z"></path> - <path class="cls-12" d="M88.15,60.27s1.7.95,1.7,2.22"></path> - <path class="cls-5" d="M101.81,61a6.68,6.68,0,0,0,8.51,0Z"></path> - <polygon class="cls-5" points="90.92 30.25 77.46 29.69 77.46 28.64 90.92 29.22 90.92 30.25"></polygon> - <text id="title" transform="matrix(1 0 0 1 44.7249 78)">Oops, Page not found</text> -</svg> - - <!-- <h1 id="title">404 Page not found</h1> --> - <!-- <button id="newBtn" class="btn" disabled>→ Back to Home</button> --> - \ No newline at end of file +<svg id="fd59ce54-f850-4dfc-bc34-dd7d379d600e" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1074.392" + height="584.231" viewBox="0 0 1074.392 584.231"> + <title>page not found</title> + <ellipse cx="540.64346" cy="549.3094" rx="527.5" ry="34.9216" fill="#f2f2f2"/> + <path d="M583.47969,324.89424c-85.94407,0-147.651,55.13938-147.651,183.79791,0,145.813,61.70691,184.41057,147.651,184.41057s151.327-42.27352,151.327-184.41057C734.80664,356.75255,669.42376,324.89424,583.47969,324.89424Zm.56495,319.80837c-59.52686,0-90.62592-34.92288-90.62592-135.9163,0-89.11185,32.37209-136.10461,91.899-136.10461s91.899,30.86774,91.899,136.10461C677.21663,607.23367,643.5715,644.70261,584.04464,644.70261Z" + transform="translate(-63.054 -157.8845)" fill="#2f2e41"/> + <path d="M384.36531,591.40121H348.831V486.76183A20.95585,20.95585,0,0,0,327.87517,465.806h-8.32638a20.95585,20.95585,0,0,0-20.95586,20.95585V591.40121H198.36285a11.96327,11.96327,0,0,1-10.57763-17.552l106.0824-200.78034A20.95585,20.95585,0,0,0,284.28724,344.33l-6.26231-2.9572a20.95585,20.95585,0,0,0-27.4293,9.07005L121.21416,592.4754a28.41578,28.41578,0,0,0-3.35584,13.39612v0a28.41583,28.41583,0,0,0,28.41584,28.41583H298.59293v66.16727a25.119,25.119,0,0,0,25.119,25.119h.00005a25.119,25.119,0,0,0,25.119-25.119V634.28739h35.53428a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,384.36531,591.40121Z" + transform="translate(-63.054 -157.8845)" fill="#16a085"/> + <path d="M1042.36183,591.40121h-35.53428V486.76183A20.95585,20.95585,0,0,0,985.87169,465.806h-8.32638a20.95585,20.95585,0,0,0-20.95586,20.95585V591.40121H856.35937a11.96326,11.96326,0,0,1-10.57763-17.552L951.86413,373.06891A20.95586,20.95586,0,0,0,942.28376,344.33l-6.26231-2.9572a20.95586,20.95586,0,0,0-27.42931,9.07005L779.21068,592.4754a28.41578,28.41578,0,0,0-3.35584,13.39612v0a28.41583,28.41583,0,0,0,28.41583,28.41583H956.58945v66.16727a25.119,25.119,0,0,0,25.119,25.119h0a25.119,25.119,0,0,0,25.119-25.119V634.28739h35.53428a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,1042.36183,591.40121Z" + transform="translate(-63.054 -157.8845)" fill="#16a085"/> + <path d="M394.16787,579.148H358.63358V474.50864a20.95585,20.95585,0,0,0-20.95585-20.95586h-8.32638a20.95586,20.95586,0,0,0-20.95586,20.95586V579.148H208.16541a11.96327,11.96327,0,0,1-10.57763-17.552L303.67017,360.81572a20.95586,20.95586,0,0,0-9.58037-28.73893l-6.26231-2.9572a20.95586,20.95586,0,0,0-27.42931,9.07L131.01672,580.2222a28.41582,28.41582,0,0,0-3.35584,13.39613v0a28.41583,28.41583,0,0,0,28.41583,28.41583H308.39549v66.16727a25.119,25.119,0,0,0,25.119,25.119h.00005a25.119,25.119,0,0,0,25.119-25.119V622.0342h35.53429a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,394.16787,579.148Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M1060.74162,579.148h-35.53428V474.50864a20.95586,20.95586,0,0,0-20.95586-20.95586H995.9251a20.95586,20.95586,0,0,0-20.95586,20.95586V579.148H874.73916a11.96327,11.96327,0,0,1-10.57763-17.552L970.24392,360.81572a20.95586,20.95586,0,0,0-9.58037-28.73893l-6.26231-2.9572a20.95586,20.95586,0,0,0-27.42931,9.07L797.59047,580.2222a28.41582,28.41582,0,0,0-3.35584,13.39613v0a28.41583,28.41583,0,0,0,28.41583,28.41583H974.96924v66.16727a25.119,25.119,0,0,0,25.119,25.119h0a25.119,25.119,0,0,0,25.119-25.119V622.0342h35.53428a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,1060.74162,579.148Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M603.0848,313.86637c-85.94407,0-147.651,55.13937-147.651,183.79791,0,145.813,61.70691,184.41057,147.651,184.41057s151.327-42.27352,151.327-184.41057C754.41175,345.72467,689.02887,313.86637,603.0848,313.86637Zm.565,319.80836c-59.52686,0-90.62592-34.92287-90.62592-135.91629,0-89.11185,32.37209-136.10461,91.899-136.10461s91.899,30.86774,91.899,136.10461C696.82174,596.20579,663.17661,633.67473,603.64975,633.67473Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <circle cx="471.14108" cy="18.25044" r="12.90118" fill="#2f2e41"/> + <ellipse cx="502.60736" cy="46.88476" rx="36.18622" ry="46.88476" fill="#2f2e41"/> + <path d="M565.66136,237.49419c-18.1276,0-33.1413-17.27052-35.77576-39.80484a60.9759,60.9759,0,0,0-.41046,7.07991c0,25.89373,16.20114,46.88476,36.18622,46.88476s36.18623-20.991,36.18623-46.88476a60.9759,60.9759,0,0,0-.41046-7.07991C598.80267,220.22367,583.789,237.49419,565.66136,237.49419Z" + transform="translate(-63.054 -157.8845)" opacity="0.1"/> + <path d="M639.29619,342.07326c-.77711,3.19345-4.12792,5.751-7.83881,7.53791-7.80188,3.75682-17.4253,4.87788-26.7597,5.25418a45.17622,45.17622,0,0,1-7.1445-.132,20.5371,20.5371,0,0,1-12.25052-5.63141,1.68086,1.68086,0,0,1,.04371-2.84388c4.9694-5.45888,13.2622-8.80605,21.61613-11.21609,6.3344-1.82743,17.3813-6.56089,24.29013-5.9221C637.94444,329.73864,640.2774,338.04112,639.29619,342.07326Z" + transform="translate(-63.054 -157.8845)" fill="#3f3d56"/> + <path d="M639.29619,342.07326c-.77711,3.19345-4.12792,5.751-7.83881,7.53791-7.80188,3.75682-17.4253,4.87788-26.7597,5.25418a45.17622,45.17622,0,0,1-7.1445-.132,20.5371,20.5371,0,0,1-12.25052-5.63141,1.68086,1.68086,0,0,1,.04371-2.84388c4.9694-5.45888,13.2622-8.80605,21.61613-11.21609,6.3344-1.82743,17.3813-6.56089,24.29013-5.9221C637.94444,329.73864,640.2774,338.04112,639.29619,342.07326Z" + transform="translate(-63.054 -157.8845)" opacity="0.1"/> + <path d="M540.09786,318.2059a19.76967,19.76967,0,0,0-1.1987,15.07476,26.33914,26.33914,0,0,0,8.82921,12.49683c10.09467,8.09163,23.98784,9.20512,36.92477,9.09278a284.6495,284.6495,0,0,0,33.90525-2.32384,40.53788,40.53788,0,0,0,11.00143-2.55442c4.22242-1.82679,7.93282-5.17756,9.436-9.5257s.43625-9.67246-3.13383-12.57428c-3.13686-2.54969-7.46265-2.9004-11.49775-3.14289l-23.08764-1.38745c2.281-2.30839,5.31816-3.614,8.09586-5.29216,3.68523-2.22642,6.13358-5.96455,8.81312-9.33471a129.00143,129.00143,0,0,1,13.4386-13.817c.75138,4.31038,3.4782,7.8499,6.68733,10.824s6.90841,5.36845,10.2439,8.20013c8.0786,6.85838,13.89583,16.1669,22.39215,22.50043a43.82885,43.82885,0,0,0,16.04862-8.0122l-3.30209-5.98141a3.94,3.94,0,0,0-1.24459-1.55282c-.93465-.575-2.13975-.27872-3.225-.44144-2.90082-.435-4.16771-3.784-5.306-6.48737-3.12491-7.42173-9.108-13.17993-14.21783-19.40381a98.00854,98.00854,0,0,1-9.99577-14.72284c-1.71652-3.10162-3.288-6.33107-5.61746-9.00321s-5.59358-4.773-9.1385-4.78051c-3.13222-.00662-6.02122,1.58355-8.71422,3.18308a230.47679,230.47679,0,0,0-23.63018,16.09894c-3.94376,3.0617-7.86306,6.29645-12.48933,8.17393-1.94748.79035-4.00044,1.33052-5.86924,2.29223-3.27313,1.6844-5.75721,4.53435-8.43128,7.06415C566.27712,311.89225,553.219,317.73841,540.09786,318.2059Z" + transform="translate(-63.054 -157.8845)" fill="#3f3d56"/> + <path d="M588.3737,253.98251a23.77444,23.77444,0,0,1-1.73379,8.03335,10.04492,10.04492,0,0,1-5.76772,5.57269,12.37513,12.37513,0,0,1-5.62306.18249,10.88232,10.88232,0,0,1-4.58151-1.56071c-2.16484-1.48837-3.24415-4.14413-3.63748-6.74325-.39333-2.596-.21714-5.24857-.46885-7.86342a42.94439,42.94439,0,0,0-1.202-6.25549c-.16993-.68282-.343-1.36248-.51294-2.04216-.16674-.67967-.33037-1.35935-.48141-2.039-.13847-.63878-.26745-1.28068-.37761-1.92574-.09123-.54436-.173-1.09189-.23285-1.64255a18.42329,18.42329,0,0,0-.80867-4.81118,14.60727,14.60727,0,0,0-1.68659-2.854c-.28635-.40906-.56326-.81811-.81815-1.24292a5.88984,5.88984,0,0,1-.97226-3.74763,3.286,3.286,0,0,1,.14788-.601c.02516-.07552.05347-.151.085-.2234A1.80187,1.80187,0,0,0,560.932,223.07a3.43341,3.43341,0,0,0-.14788-1.77783,11.31808,11.31808,0,0,0-.95974-2.28761c-.2643-.47829-1.16108-1.34046-1.16738-1.888-.0126-1.10132,2.13972-1.98867,3.01134-2.42291a16.79623,16.79623,0,0,1,8.59657-1.74323c1.90369.129,3.9679.71428,5.0189,2.30962.944,1.438.81807,3.30081,1.22085,4.97169a1.47068,1.47068,0,0,0,.29892.66393,1.34135,1.34135,0,0,0,.73948.33982,4.54948,4.54948,0,0,0,1.416.05666h.00315a2.93138,2.93138,0,0,0,.37128-.05351,4.957,4.957,0,0,0,2.03271-.8779q.58531-.15576,1.18-.25488a.25112.25112,0,0,0,.04725-.00945c1.57646,4.97482,1.781,10.30836,3.07111,15.37444.63874,2.52044,1.55442,5.00943,1.6834,7.60225.00945.11327.0126.2297.01575.34612.0189.83386-.04717,1.674-.0126,2.50472a6.981,6.981,0,0,0,.12591,1.1139,15.61121,15.61121,0,0,0,.52546,1.74325l.00945.02831c.05977.18251.11643.36817.16363.55381.03457.1353.06607.26747.09127.40277l.00311.00943A14.93754,14.93754,0,0,1,588.3737,253.98251Z" + transform="translate(-63.054 -157.8845)" fill="#fbbebe"/> + <circle cx="503.23669" cy="44.99678" r="18.56511" fill="#fbbebe"/> + <path d="M684.15711,304.03278a30.445,30.445,0,0,0-5.236-14.10317q.72216,4.29513,1.44748,8.58714a3.214,3.214,0,0,1-3.36688-1.03523,10.33663,10.33663,0,0,1-1.76529-3.27565,67.46571,67.46571,0,0,0-8.2095-14.73567c-11.81876-.98489-23.50223-5.88418-33.89555-11.59532-10.39643-5.708-20.12582-12.5519-30.38382-18.50217a43.57346,43.57346,0,0,0-5.54436-2.832c-3.20954-1.287-6.81242-1.95406-9.85526-3.46759-.2045-.1007-.409-.20767-.61043-.31781a12.57834,12.57834,0,0,1-1.94459-1.30584,10.34363,10.34363,0,0,1-.93139-.8559,20.35115,20.35115,0,0,1-3.55886-5.95341c-1.63308-3.61232-2.21524-7.97041-3.84517-11.58274a11.20292,11.20292,0,0,1,2.50156-1.76525h.00315c.13213-.06924.2643-.13532.39962-.19824a11.9404,11.9404,0,0,1,2.00437-.73317q.58531-.15576,1.18-.25488a.25112.25112,0,0,0,.04725-.00945,11.56564,11.56564,0,0,1,5.49085.43424c2.58652.87477,4.76711,2.62115,6.94148,4.27313a114.02006,114.02006,0,0,1,10.14787,8.04908c1.79357,1.718,3.4298,3.606,5.35868,5.16676a42.14393,42.14393,0,0,0,5.05662,3.35116q15.65613,9.32658,31.31525,18.65005c3.53365,2.1051,7.07046,4.21019,10.52553,6.438,5.24855,3.38578,10.30828,7.05474,15.36493,10.72057q4.46978,3.23787,8.93647,6.47889a9.72771,9.72771,0,0,1,2.533,2.3411,8.4724,8.4724,0,0,1,1.12337,3.433A31.3874,31.3874,0,0,1,684.15711,304.03278Z" + transform="translate(-63.054 -157.8845)" fill="#fbbebe"/> + <path d="M592.97726,267.9441c-1.25235,5.61674-6.92888,9.012-9.89617,13.94586-3.68784,6.12335-2.18378,13.241-.79922,20.25484q-3.79485,3.27095-7.59285,6.54186c-1.39708,1.19886-2.79417,2.404-4.29827,3.46444a57.35064,57.35064,0,0,1-6.85966,3.93956q-3.3606,1.72752-6.72119,3.45814a32.1282,32.1282,0,0,1-6.57961,2.78793c-4.41473,1.13278-9.10318.33982-13.4707-.97232a6.08761,6.08761,0,0,1-1.47264-.601,2.39351,2.39351,0,0,1-.69854-.63248,3.91067,3.91067,0,0,1-.44365-2.53933c.44365-7.35052,2.24036-14.54686,4.03081-21.68971a85.2598,85.2598,0,0,1,3.84832-12.57708,85.0766,85.0766,0,0,1,5.41538-10.151,68.36751,68.36751,0,0,1,7.92948-11.51353,18.47881,18.47881,0,0,0,3.67525-4.73882c1.11706-2.54876.686-5.472.91252-8.24732a17.14844,17.14844,0,0,1,1.63312-6.0069v-.00315a17.09326,17.09326,0,0,1,1.74321-2.88232q.45788,1.06671.91568,2.13027.30209.69855.59783,1.394.38706.89679.7678,1.78728,1.09973,2.55823,2.19637,5.11327a21.58968,21.58968,0,0,0,3.33538,5.944,6.49923,6.49923,0,0,0,11.12337-.85275,21.26125,21.26125,0,0,0,2.27185-6.0132,19.21547,19.21547,0,0,0,.25175-7.83509c-.75835-5.00945-2.88862-10.12585-4.43678-14.77972a14.94511,14.94511,0,0,1-1.07927-4.871,3.35144,3.35144,0,0,1,.05662-.56011c.00945-.04719.0189-.09754.02834-.14473a11.9404,11.9404,0,0,1,2.00437-.73317q.58531-.15576,1.18-.25488,2.04378,11.06355,4.09377,22.12709c.0315.17307.0661.34613.09756.52234.19509,1.05726.39333,2.11454.61358,3.16865.19828.95657.41223,1.91.65137,2.85715l.00945.02831c.08182.321.16678.63877.2549.95658l.00311.00943c.2423.86848.5129,1.73065.81811,2.58024C590.93825,257.47528,594.16355,262.62946,592.97726,267.9441Z" + transform="translate(-63.054 -157.8845)" fill="#16a085"/> + <path d="M668.32144,346.87707a6.58269,6.58269,0,0,0,.61,3.14328c1.16192,2.12353,3.94981,2.60625,6.36228,2.80484a188.37688,188.37688,0,0,0,42.2657-1.28774,4.88565,4.88565,0,0,0,2.15136-.66766c1.98985-1.39509.76329-4.7951-1.40951-5.88355s-4.75126-.82614-7.1353-1.29748a22.47912,22.47912,0,0,1-6.67794-2.89617q-7.25234-4.16669-14.293-8.68808c-2.79453-1.79464-6.09272-3.70993-9.23987-2.64587C672.43,332.34264,668.26533,337.68065,668.32144,346.87707Z" + transform="translate(-63.054 -157.8845)" fill="#3f3d56"/> + <path d="M564.43732,240.87367v.00315c-.022.13215-.04406.26116-.07237.39018-.0346.214-.07551.43108-.11642.645-.39018,1.99812-.86847,3.98678-1.41913,5.96287-1.5104,5.45939-3.53366,10.83069-5.54121,16.12332q-8.08055,21.28692-16.16423,42.577c-1.35936,3.57457-2.71554,7.15228-4.26054,10.65448-.516,1.16741-1.04782,2.34424-1.57647,3.53368-1.89427,4.25737-3.713,8.65322-4.31716,13.18436a27.44976,27.44976,0,0,0-.19194,9.04027c.60416,2.97042,2.40718,5.8716,5.22969,6.96977,1.37823.53808,3.35113,1.25865,2.97355,2.69037-.2045.78665-1.09817,1.17055-1.90057,1.3027a7.31234,7.31234,0,0,1-5.966-1.718c-1.50725-1.33732-2.66518-3.41725-4.66959-3.64065-1.38767-.151-2.66518.67966-3.93643,1.26178-5.18564,2.36942-11.22719.71114-16.674-.9723.42794-2.20579,2.64318-3.65953,4.84267-4.10006,2.19949-.44367,4.47449-.129,6.718-.18879a3.50958,3.50958,0,0,0,2.04216-.52549,3.70545,3.70545,0,0,0,1.10132-1.88169,78.96356,78.96356,0,0,0,3.21273-13.14661c.7237-4.66645,1.02581-9.40527,2.05787-14.01507.80241-3.59661,2.0422-7.07991,3.10572-10.61044a224.68238,224.68238,0,0,0,5.0598-22.07674,78.02019,78.02019,0,0,0,1.42543-9.36751c.17935-2.6117.09438-5.236.34609-7.83826a60.8877,60.8877,0,0,1,2.11141-9.99683q1.44427-5.34769,2.88547-10.68911c1.42544-5.2706,2.95465-10.74572,6.567-14.84264a13.96159,13.96159,0,0,1,10.02834-4.78915,9.8819,9.8819,0,0,1,2.13027.22969c.11639.02831.23285.05664.34923.0881a8.63447,8.63447,0,0,1,2.17437.89995c1.11388-.708,1.68025-.45942,2.41974.63246a6.97319,6.97319,0,0,1,.88107,3.79485A52.42378,52.42378,0,0,1,564.43732,240.87367Z" + transform="translate(-63.054 -157.8845)" fill="#fbbebe"/> + <path d="M565.66136,245.0461l-.0472.04719-.25486.25488-2.5299,2.52675-1.23976-5.20767-4.25109-17.854a9.8819,9.8819,0,0,1,2.13027.22969,3.286,3.286,0,0,1,.14788-.601l.20135.68911,1.44118,4.90245,2.72811,9.30773.45,1.53241v.00315Z" + transform="translate(-63.054 -157.8845)" fill="#16a085"/> + <path d="M581.71523,188.0873a12.58165,12.58165,0,0,1-3.70049,8.89583,12.31392,12.31392,0,0,1-1.36008,1.17634,12.52812,12.52812,0,0,1-7.53567,2.52415H554.023a12.5902,12.5902,0,0,1,0-25.18037h15.096A12.62919,12.62919,0,0,1,581.71523,188.0873Z" + transform="translate(-63.054 -157.8845)" fill="#2f2e41"/> + <circle cx="532.81499" cy="18.25044" r="12.90118" fill="#2f2e41"/> + <path d="M595.55433,163.23377c-.15825,0-.31505.00628-.472.01193a12.89776,12.89776,0,0,1,0,25.77849c.15694.00565.31374.01193.472.01193a12.90117,12.90117,0,1,0,0-25.80235Z" + transform="translate(-63.054 -157.8845)" opacity="0.1"/> + <path d="M534.19508,163.23377c.15825,0,.31505.00628.472.01193a12.89776,12.89776,0,0,0,0,25.77849c-.157.00565-.31375.01193-.472.01193a12.90118,12.90118,0,0,1,0-25.80235Z" + transform="translate(-63.054 -157.8845)" opacity="0.1"/> + <path d="M576.65466,198.15947a12.52812,12.52812,0,0,1-7.53567,2.52415H554.023a12.52833,12.52833,0,0,1-7.53574-2.52415Z" + transform="translate(-63.054 -157.8845)" opacity="0.1"/> + <path d="M674.13958,291.64042s3.25228,9.37161,6.229,6.87633L677.996,286.26693Z" + transform="translate(-63.054 -157.8845)" fill="#fbbebe"/> + <path d="M1069.91781,577.43414a20.81252,20.81252,0,1,0,2.7716-39.91524l.52093,10.7122-5.06814-9.18045a20.734,20.734,0,0,0-10.68367,11.72261,20.40847,20.40847,0,0,0-1.19713,5.62986A20.80856,20.80856,0,0,0,1069.91781,577.43414Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M1094.99516,701.67756c-1.78906-9.11027,5.9633-17.1868,13.62086-22.43651s16.605-10.40779,19.21775-19.31684c3.755-12.80387-7.43-24.52981-16.13564-34.64176a125.30044,125.30044,0,0,1-16.52359-24.55738c-1.81107-3.5325-3.47558-7.22528-3.95221-11.16626-.68641-5.67546,1.13693-11.32309,2.9739-16.73673q9.17925-27.05169,19.62843-53.65005" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M1070.77493,574.6762a20.81252,20.81252,0,1,0,2.7716-39.91524l.52093,10.7122-5.06815-9.18045a20.734,20.734,0,0,0-10.68366,11.72261,20.40847,20.40847,0,0,0-1.19713,5.62986A20.80855,20.80855,0,0,0,1070.77493,574.6762Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M1092.45136,515.47266a20.78819,20.78819,0,0,1,14.97993-13.19764l1.71361,10.18378,3.177-10.69566a20.81,20.81,0,1,1-19.87057,13.70952Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M1093.59418,511.7954a20.7882,20.7882,0,0,1,14.97993-13.19763l1.71361,10.18378,3.177-10.69567a20.81,20.81,0,1,1-19.87057,13.70952Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M1108.04474,625.48885a20.81,20.81,0,0,0,18.419-37.02267l-2.44121,8.21926-1.73105-10.30382a.36183.36183,0,0,0-.053-.0201,20.81113,20.81113,0,1,0-14.1938,39.12733Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M1109.035,621.76417a20.81,20.81,0,0,0,18.419-37.02267l-2.44121,8.21926-1.73105-10.30382a.3621.3621,0,0,0-.053-.0201,20.81113,20.81113,0,1,0-14.1938,39.12733Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M1086.37782,660.05148a20.80131,20.80131,0,1,0,4.01058-16.29737l9.27267,13.95654-12.66994-7.40768A20.61638,20.61638,0,0,0,1086.37782,660.05148Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M1087.23494,657.29354a20.80131,20.80131,0,1,0,4.01058-16.29737l9.27267,13.95655-12.66994-7.40769A20.61626,20.61626,0,0,0,1087.23494,657.29354Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M72.06146,628.13325a13.67421,13.67421,0,1,0,1.821-26.225l.34227,7.03811-3.32987-6.03172a13.62263,13.62263,0,0,0-7.01936,7.702,13.40883,13.40883,0,0,0-.78654,3.69893A13.6716,13.6716,0,0,0,72.06146,628.13325Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M88.53774,709.76344c-1.17545-5.98561,3.918-11.292,8.94915-14.7412s10.90978-6.8381,12.62642-12.69151c2.46711-8.41238-4.88167-16.11653-10.60142-22.76027A82.32442,82.32442,0,0,1,88.6556,643.43581a22.20962,22.20962,0,0,1-2.59668-7.33643c-.451-3.72888.747-7.43947,1.95391-10.99634q6.03093-17.77346,12.89623-35.24906" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M72.62461,626.32123a13.6742,13.6742,0,1,0,1.821-26.225l.34227,7.03812L71.458,601.10258a13.62262,13.62262,0,0,0-7.01936,7.702,13.40912,13.40912,0,0,0-.78654,3.69892A13.67158,13.67158,0,0,0,72.62461,626.32123Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M86.86641,587.42343a13.65822,13.65822,0,0,1,9.84209-8.67109l1.12587,6.69093,2.08737-7.02725a13.67252,13.67252,0,1,1-13.05533,9.00741Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M87.61727,585.0074a13.65822,13.65822,0,0,1,9.84209-8.67108l1.12587,6.69093L100.6726,576a13.67252,13.67252,0,1,1-13.05533,9.0074Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M97.11155,659.70607a13.67255,13.67255,0,0,0,12.10164-24.32457l-1.60392,5.4002-1.13733-6.76979a.238.238,0,0,0-.0348-.0132,13.67329,13.67329,0,1,0-9.32559,25.70736Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M97.76214,657.25889a13.67255,13.67255,0,0,0,12.10164-24.32457l-1.60392,5.4002-1.13733-6.7698a.238.238,0,0,0-.0348-.0132,13.67329,13.67329,0,1,0-9.32559,25.70737Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M82.876,682.41435a13.66684,13.66684,0,1,0,2.635-10.70767l6.09231,9.16971-8.32438-4.867A13.54535,13.54535,0,0,0,82.876,682.41435Z" + transform="translate(-63.054 -157.8845)" fill="#57b894"/> + <path d="M83.43913,680.60233a13.66684,13.66684,0,1,0,2.635-10.70767l6.09231,9.16971-8.32439-4.867A13.54535,13.54535,0,0,0,83.43913,680.60233Z" + transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <ellipse cx="480.946" cy="319.1155" rx="17" ry="22" fill="#2f2e41"/> + <ellipse cx="573.446" cy="319.6155" rx="17" ry="22" fill="#2f2e41"/> + <path d="M623.5,542.5c0,9.94-13.88,18-31,18s-31-8.06-31-18c0-8.61,10.41-15.81,24.32-17.57a50.10353,50.10353,0,0,1,6.68-.43,50.69869,50.69869,0,0,1,11.13,1.2C615.25,528.29,623.5,534.84,623.5,542.5Z" + transform="translate(-63.054 -157.8845)" fill="#2f2e41"/> + <ellipse cx="484.946" cy="314.1155" rx="17" ry="22" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <ellipse cx="577.446" cy="314.6155" rx="17" ry="22" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <ellipse cx="533.446" cy="379.6155" rx="31" ry="18" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/> + <path d="M604,527.2a4.93658,4.93658,0,0,1-1.32,3.392A4.33873,4.33873,0,0,1,599.5,532h-10a4.66433,4.66433,0,0,1-4.5-4.8,4.90458,4.90458,0,0,1,.82-2.74134A47.02,47.02,0,0,1,592.5,524a47.66454,47.66454,0,0,1,11.13,1.28A5.06656,5.06656,0,0,1,604,527.2Z" + transform="translate(-63.054 -157.8845)" fill="#fff"/> + <circle cx="484.946" cy="308.1155" r="5" fill="#fff"/> + <circle cx="577.946" cy="308.1155" r="5" fill="#fff"/> + <circle cx="582.946" cy="355.1155" r="5" fill="#16a085" opacity="0.3"/> + <circle cx="460.946" cy="355.1155" r="5" fill="#16a085" opacity="0.3"/> +</svg> \ No newline at end of file diff --git a/prototype/src/main/resources/static/img/error_generic_illustatus.svg b/prototype/src/main/resources/static/img/error_generic_illustatus.svg index f94a8d4..20143d7 100644 --- a/prototype/src/main/resources/static/img/error_generic_illustatus.svg +++ b/prototype/src/main/resources/static/img/error_generic_illustatus.svg @@ -1,277 +1,75 @@ -<svg id="mainImage_create" data-name="mainImage" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 171.2 81.5"> - <style id="svgStyle"> - @import url('https://fonts.googleapis.com/css?family=Merriweather'); - .changeColor { - fill: #4f86ed; - } - - #title { - font-size: 50%; - font-family: 'Merriweather', serif; - } - - .cls-1 { - opacity: 0.3; - } - - .cls-7 { - opacity: 0.8; - } - - .cls-2 { - fill: #fff; - } - - .cls-10, - .cls-11, - .cls-12, - .cls-14, - .cls-16, - .cls-3 { - fill: none; - } - - .cls-3 { - stroke: #5c7690; - } - - .cls-10, - .cls-11, - .cls-12, - .cls-3 { - stroke-miterlimit: 10; - } - - .cls-14, - .cls-15, - .cls-16, - .cls-3 { - stroke-width: 0.5px; - } - - .cls-4 { - fill: #ffe1d9; - } - - .cls-5 { - fill: #ffcfbf; - } - - .cls-6 { - fill: #fecbb6; - } - - .cls-9 { - fill: #fecb02; - } - - .cls-10, - .cls-12 { - stroke: #d26f51; - } - - .cls-10, - .cls-11 { - stroke-width: 0.38px; - } - - .cls-11 { - stroke: #000; - } - - .cls-12 { - stroke-width: 0.19px; - } - - .cls-13 { - opacity: 0.45; - } - - .cls-14, - .cls-15, - .cls-16 { - stroke: #b0bec5; - stroke-linejoin: round; - } - - .cls-15 { - fill: #edf0f2; - } - - .cls-16 { - stroke-linecap: round; - } - - .cls-17 { - font-family: 'PT Sans', sans-serif; - font-size: 49.87px; - font-weight: 700; - } - - .cls-18 { - fill: #fffdbb; - opacity: 0.5; - } - - - /*--------------------------- - SVG Animate - ---------------------------*/ - .earMove { - transition: all ease-in-out 2s; - transform-origin: 50% 50%; - animation: earmove 1.5s linear infinite alternate; - } - - .faceMove { - transition: all ease-in-out 2s; - transform-origin: 50% 50%; - animation: move 1.5s linear infinite alternate; - } - - .neckMove { - transition: all ease-in-out 2s; - transform-origin: 50% 50%; - animation: neck 1.5s linear infinite alternate; - } - - - @keyframes earmove { - 0% { - transform: translateX(-0.3px) translateY(0.6px); - } - 30% { - transform: translateX(-0.3px) translateY(0.6px); - } - - 60% { - transform: translateX(-0.7px) translateY(0px); - } - - 70% { - transform: translateX(-0.7px) translateY(-0.3px); - } - 100% { - transform: translateX(-0.7px) translateY(-0.3px); - } - } - - @keyframes move { - 0% { - transform: translateX(-0.3px) translateY(0.6px); - } - 30% { - transform: translateX(-0.3px) translateY(0.6px); - } - - 60% { - transform: translateX(2px) translateY(0px); - } - - 70% { - transform: translateX(2px) translateY(-0.3px); - } - 100% { - transform: translateX(2px) translateY(-0.3px); - } - } - - @keyframes neck { - 0% { - transform: translateY(0.7px); - } - 50% { - transform: translateY(0.7px); - } - 100% { - transform: translateY(0px); - } - } - </style> - <path id="c-1" class="changeColor cls-1" - d="M46.62,52.5c5.78,4.9,21.14,8.4,39.19,8.4s33.41-3.5,39.19-8.4c-5.78-4.9-21.14-8.4-39.19-8.4S52.41,47.6,46.62,52.5Z" - style="fill: rgb(255, 5, 18);"></path> - <path class="cls-2" - d="M99.73,47.71H68.65a7.13,7.13,0,0,0-7.13,7.13V60a152.58,152.58,0,0,0,24.3,1.83,157.87,157.87,0,0,0,21.05-1.35V54.84A7.13,7.13,0,0,0,99.73,47.71Z"></path> - <path class="cls-3" - d="M123.56,55.81C115,58.94,101.27,61,85.81,61c-26,0-47-5.71-47-12.76,0-3.45,5.05-6.58,13.25-8.88"></path> - <path class="cls-3" d="M55.37,38.47a140,140,0,0,1,30.44-3c26,0,47,5.71,47,12.76,0,2.4-2.44,4.65-6.69,6.57"></path> - <path class="cls-3" d="M53.41,38.95l.94-.24"></path> - <path class="cls-4" - d="M91.68,47.71l-.75-11.2L79.15,43.84l-1.69,3.87H75.79c0,3.36,3.76,6.08,8.4,6.08s8.4-2.72,8.4-6.08Z"></path> - <path class="cls-5 neckMove" - d="M78,46.53a27.19,27.19,0,0,0,6.41.82c3.1,0,7.11-2.19,7.11-2.19l-.42-6.2L79.15,43.84Z"></path> - <polygon class="earMove" points="92.59 32.22 92.59 28.5 76.77 27.71 76.77 32.22 92.59 32.22"></polygon> - <circle class="cls-6 earMove" cx="78.06" cy="34.04" r="2.47"></circle> - <path class="cls-4" - d="M81.74,57.06,60.63,49.72h0A6.72,6.72,0,1,0,57.7,62.49H93.25C93.25,56.78,81.74,57.06,81.74,57.06Z"></path> - <path class="cls-4" - d="M77.46,25H90.92a0,0,0,0,1,0,0V39.38a6.73,6.73,0,0,1-6.73,6.73h0a6.73,6.73,0,0,1-6.73-6.73V25A0,0,0,0,1,77.46,25Z"></path> - <rect id="c-2" class="changeColor cls-7" x="74.82" y="26.48" width="19.14" height="2.45" - transform="translate(1.29 -3.65) rotate(2.49)" style="fill: rgb(255, 5, 18);"></rect> - <path id="c-3" class="changeColor cls-7" - d="M84.36,18.69h.5a7.8,7.8,0,0,1,7.8,7.8v0a0,0,0,0,1,0,0H76.56a0,0,0,0,1,0,0v0A7.8,7.8,0,0,1,84.36,18.69Z" - transform="translate(1.06 -3.66) rotate(2.49)" style="fill: rgb(255, 5, 18);"></path> - <polygon id="c-4" class="changeColor cls-8" - points="82.44 23.89 92.18 24.32 92.59 24.34 92.48 26.84 80.96 26.33 82.44 23.89" - style="fill: rgb(255, 5, 18);"></polygon> - <circle class="cls-9 faceMove" cx="78.72" cy="23.73" r="3.73" - transform="translate(51.58 101.34) rotate(-87.51)"></circle> - <circle class="cls-2 faceMove" cx="78.72" cy="23.73" r="2.36" - transform="translate(51.58 101.34) rotate(-87.51)"></circle> - <circle class="cls-4 earMove" cx="90.92" cy="34.04" r="2.47"></circle> - <path class="cls-4" - d="M112.2,53l-9.87-21.92-3-5.48-11.86-.22,7.42,3.35H91.55l5.82,4.58,2,22.26h0A6.72,6.72,0,1,0,112.2,53Z"></path> - <ellipse class="faceMove" cx="80.09" cy="33.12" rx="0.53" ry="0.59"></ellipse> - <ellipse class="faceMove" cx="86.34" cy="33.12" rx="0.53" ry="0.59"></ellipse> - <polyline class="cls-10 faceMove" points="84.19 31.08 81.74 37.01 84.39 37.01"></polyline> - <path class="cls-10 faceMove" d="M83.06,40.36a4,4,0,0,1,2.75-1"></path> - <line class="cls-11 faceMove" x1="81.07" y1="30.33" x2="78.47" y2="30.58"></line> - <line class="cls-11 faceMove" x1="86.34" y1="30.45" x2="88.15" y2="31.08"></line> - <line class="cls-12" x1="106.86" y1="47.82" x2="110.99" y2="46.11"></line> - <line class="cls-12" x1="107.43" y1="49.9" x2="111.55" y2="48.19"></line> - <line class="cls-12" x1="107.99" y1="51.98" x2="112.11" y2="50.27"></line> - <g class="cls-13"> - <rect class="cls-14" x="85.81" y="2.46" width="10.77" height="3.5"></rect> - <rect class="cls-15" x="96.58" y="2.46" width="10.77" height="3.5"></rect> - <rect class="cls-14" x="92.19" y="5.95" width="10.77" height="3.5"></rect> - <line class="cls-16" x1="107.36" y1="5.95" x2="109.63" y2="5.95"></line> - <line class="cls-16" x1="110.68" y1="5.95" x2="111.57" y2="5.95"></line> - </g> - <g class="cls-13"> - <rect class="cls-16" x="125" y="23.12" width="10.77" height="3.5"></rect> - <rect class="cls-15" x="130.39" y="26.62" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="119.62" y="26.62" width="10.77" height="3.5"></rect> - <line class="cls-16" x1="141.16" y1="26.62" x2="145.73" y2="26.62"></line> - <line class="cls-16" x1="125" y1="23.12" x2="115.4" y2="23.12"></line> - <line class="cls-16" x1="117.95" y1="26.62" x2="115.4" y2="26.62"></line> - </g> - <g class="cls-13"> - <rect class="cls-16" x="39.34" y="16.12" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="39.34" y="23.11" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="50.11" y="23.11" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="50.11" y="16.12" width="10.77" height="3.5"></rect> - <rect class="cls-15" x="44" y="19.61" width="10.77" height="3.5"></rect> - <rect class="cls-16" x="33.23" y="19.61" width="10.77" height="3.5"></rect> - <line class="cls-16" x1="60.89" y1="19.61" x2="65.51" y2="19.61"></line> - <line class="cls-16" x1="39.34" y1="16.12" x2="35.46" y2="16.12"></line> - <line class="cls-16" x1="36.45" y1="26.61" x2="33.23" y2="26.61"></line> - <line class="cls-16" x1="63.2" y1="23.11" x2="65.51" y2="23.11"></line> - </g> - <polyline class="cls-3" points="115.4 58.12 115.4 38.27 120.2 37.01"></polyline> - <polyline class="cls-3" points="129.01 53.21 129.01 43.14 131.74 42.13"></polyline> - <path class="cls-3" d="M115.4,42.13a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,47"></path> - <path class="cls-3" d="M115.4,47.34a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,52.22"></path> - <path class="cls-3" d="M115.4,52.56a53.27,53.27,0,0,1,8,2l1,.42"></path> - <path class="cls-18 faceMove" - d="M78.84,26.09l0-4.71L68.05,18.32a.91.91,0,0,0-.45-.13c-1.17,0-2.11,2.46-2.11,5.5s.95,5.5,2.11,5.5a.9.9,0,0,0,.44-.12Z"></path> - <path class="cls-5" d="M57.7,62.49H93.25A3.67,3.67,0,0,0,92.92,61H53.43A6.69,6.69,0,0,0,57.7,62.49Z"></path> - <path class="cls-12" d="M88.15,60.27s1.7.95,1.7,2.22"></path> - <path class="cls-5" d="M101.81,61a6.68,6.68,0,0,0,8.51,0Z"></path> - <polygon class="cls-5" points="90.92 30.25 77.46 29.69 77.46 28.64 90.92 29.22 90.92 30.25"></polygon> - <text id="title" transform="matrix(1 0 0 1 44.7249 78)">Oops, something went wrong</text> -</svg> - - <!-- <h1 id="title">404 Page not found</h1> --> - <!-- <button id="newBtn" class="btn" disabled>→ Back to Home</button> --> - \ No newline at end of file +<svg id="aa03ddf9-f8f2-4819-a4ce-be9b0a220741" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1119.60911" + height="699" viewBox="0 0 1119.60911 699"> + <title>server down</title> + <circle cx="292.60911" cy="213" r="213" fill="#f2f2f2"/> + <path d="M31.39089,151.64237c0,77.49789,48.6181,140.20819,108.70073,140.20819" + transform="translate(-31.39089 -100.5)" fill="#2f2e41"/> + <path d="M140.09162,291.85056c0-78.36865,54.255-141.78356,121.30372-141.78356" + transform="translate(-31.39089 -100.5)" fill="#16a085"/> + <path d="M70.77521,158.66768c0,73.61476,31.00285,133.18288,69.31641,133.18288" + transform="translate(-31.39089 -100.5)" fill="#16a085"/> + <path d="M140.09162,291.85056c0-100.13772,62.7103-181.16788,140.20819-181.16788" + transform="translate(-31.39089 -100.5)" fill="#2f2e41"/> + <path d="M117.22379,292.83905s15.41555-.47479,20.06141-3.783,23.713-7.2585,24.86553-1.95278,23.16671,26.38821,5.76263,26.5286-40.43935-2.711-45.07627-5.53549S117.22379,292.83905,117.22379,292.83905Z" + transform="translate(-31.39089 -100.5)" fill="#a8a8a8"/> + <path d="M168.224,311.78489c-17.40408.14042-40.43933-2.71094-45.07626-5.53548-3.53126-2.151-4.93843-9.86945-5.40926-13.43043-.32607.014-.51463.02-.51463.02s.97638,12.43276,5.61331,15.2573,27.67217,5.67589,45.07626,5.53547c5.02386-.04052,6.7592-1.82793,6.66391-4.47526C173.87935,310.756,171.96329,311.75474,168.224,311.78489Z" + transform="translate(-31.39089 -100.5)" opacity="0.2"/> + <ellipse cx="198.60911" cy="424.5" rx="187" ry="25.43993" fill="#3f3d56"/> + <ellipse cx="198.60911" cy="424.5" rx="157" ry="21.35866" opacity="0.1"/> + <ellipse cx="836.60911" cy="660.5" rx="283" ry="38.5" fill="#3f3d56"/> + <ellipse cx="310.60911" cy="645.5" rx="170" ry="23.12721" fill="#3f3d56"/> + <path d="M494,726.5c90,23,263-30,282-90" transform="translate(-31.39089 -100.5)" fill="none" stroke="#2f2e41" + stroke-miterlimit="10" stroke-width="2"/> + <path d="M341,359.5s130-36,138,80-107,149-17,172" transform="translate(-31.39089 -100.5)" fill="none" + stroke="#2f2e41" stroke-miterlimit="10" stroke-width="2"/> + <path d="M215.40233,637.78332s39.0723-10.82,41.47675,24.04449-32.15951,44.78287-5.10946,51.69566" + transform="translate(-31.39089 -100.5)" fill="none" stroke="#2f2e41" stroke-miterlimit="10" stroke-width="2"/> + <path d="M810.09554,663.73988,802.218,714.03505s-38.78182,20.60284-11.51335,21.20881,155.73324,0,155.73324,0,24.84461,0-14.54318-21.81478l-7.87756-52.719Z" + transform="translate(-31.39089 -100.5)" fill="#2f2e41"/> + <path d="M785.21906,734.69812c6.193-5.51039,16.9989-11.252,16.9989-11.252l7.87756-50.2952,113.9216.10717,7.87756,49.582c9.185,5.08711,14.8749,8.987,18.20362,11.97818,5.05882-1.15422,10.58716-5.44353-18.20362-21.38921l-7.87756-52.719-113.9216,3.02983L802.218,714.03506S769.62985,731.34968,785.21906,734.69812Z" + transform="translate(-31.39089 -100.5)" opacity="0.1"/> + <rect x="578.43291" y="212.68859" width="513.25314" height="357.51989" rx="18.04568" fill="#2f2e41"/> + <rect x="595.70294" y="231.77652" width="478.71308" height="267.83694" fill="#3f3d56"/> + <circle cx="835.05948" cy="223.29299" r="3.02983" fill="#f2f2f2"/> + <path d="M1123.07694,621.32226V652.6628a18.04341,18.04341,0,0,1-18.04568,18.04568H627.86949A18.04341,18.04341,0,0,1,609.8238,652.6628V621.32226Z" + transform="translate(-31.39089 -100.5)" fill="#2f2e41"/> + <polygon + points="968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 651.452 645.651 962.312 645.651 968.978 667.466" + fill="#2f2e41"/> + <path d="M1125.828,762.03359c-.59383,2.539-2.83591,5.21743-7.90178,7.75032-18.179,9.08949-55.1429-2.42386-55.1429-2.42386s-28.4804-4.84773-28.4804-17.573a22.72457,22.72457,0,0,1,2.49658-1.48459c7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z" + transform="translate(-31.39089 -100.5)" fill="#2f2e41"/> + <path d="M1125.828,762.03359c-22.251,8.526-42.0843,9.1622-62.43871-4.975-10.26507-7.12617-19.59089-8.88955-26.58979-8.75618,7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z" + transform="translate(-31.39089 -100.5)" opacity="0.1"/> + <ellipse cx="1066.53846" cy="654.13477" rx="7.87756" ry="2.42386" fill="#f2f2f2"/> + <circle cx="835.05948" cy="545.66686" r="11.51335" fill="#f2f2f2"/> + <polygon points="968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 968.978 667.466" + opacity="0.1"/> + <rect x="108.60911" y="159" width="208" height="242" fill="#2f2e41"/> + <rect x="87.60911" y="135" width="250" height="86" fill="#3f3d56"/> + <rect x="87.60911" y="237" width="250" height="86" fill="#3f3d56"/> + <rect x="87.60911" y="339" width="250" height="86" fill="#3f3d56"/> + <rect x="271.60911" y="150" width="16" height="16" fill="#16a085" opacity="0.4"/> + <rect x="294.60911" y="150" width="16" height="16" fill="#16a085" opacity="0.8"/> + <rect x="317.60911" y="150" width="16" height="16" fill="#16a085"/> + <rect x="271.60911" y="251" width="16" height="16" fill="#16a085" opacity="0.4"/> + <rect x="294.60911" y="251" width="16" height="16" fill="#16a085" opacity="0.8"/> + <rect x="317.60911" y="251" width="16" height="16" fill="#16a085"/> + <rect x="271.60911" y="352" width="16" height="16" fill="#16a085" opacity="0.4"/> + <rect x="294.60911" y="352" width="16" height="16" fill="#16a085" opacity="0.8"/> + <rect x="317.60911" y="352" width="16" height="16" fill="#16a085"/> + <circle cx="316.60911" cy="538" r="79" fill="#2f2e41"/> + <rect x="280.60911" y="600" width="24" height="43" fill="#2f2e41"/> + <rect x="328.60911" y="600" width="24" height="43" fill="#2f2e41"/> + <ellipse cx="300.60911" cy="643.5" rx="20" ry="7.5" fill="#2f2e41"/> + <ellipse cx="348.60911" cy="642.5" rx="20" ry="7.5" fill="#2f2e41"/> + <circle cx="318.60911" cy="518" r="27" fill="#fff"/> + <circle cx="318.60911" cy="518" r="9" fill="#3f3d56"/> + <path d="M271.36733,565.03228c-6.37889-28.56758,14.01185-57.43392,45.544-64.47477s62.2651,10.41,68.644,38.9776-14.51861,39.10379-46.05075,46.14464S277.74622,593.59986,271.36733,565.03228Z" + transform="translate(-31.39089 -100.5)" fill="#16a085"/> + <ellipse cx="417.21511" cy="611.34365" rx="39.5" ry="12.40027" + transform="translate(-238.28665 112.98044) rotate(-23.17116)" fill="#2f2e41"/> + <ellipse cx="269.21511" cy="664.34365" rx="39.5" ry="12.40027" + transform="translate(-271.07969 59.02084) rotate(-23.17116)" fill="#2f2e41"/> + <path d="M394,661.5c0,7.732-19.90861,23-42,23s-43-14.268-43-22,20.90861-6,43-6S394,653.768,394,661.5Z" + transform="translate(-31.39089 -100.5)" fill="#fff"/> +</svg> \ No newline at end of file diff --git a/prototype/src/main/resources/templates/error/404.html b/prototype/src/main/resources/templates/error/404.html index 699b4f5..a58016a 100644 --- a/prototype/src/main/resources/templates/error/404.html +++ b/prototype/src/main/resources/templates/error/404.html @@ -9,10 +9,8 @@ <nav th:replace="fragments/header :: header">Header</nav> <main> <div class='content-width'> - <h1>Error 404</h1> + <h1>Ein Fehler ist aufgetreten. Die gewünschte Ressource konnte nicht gefunden werden</h1> <div> - <p>Ein Fehler ist aufgetreten. Die gewünschte Ressource konnte nicht gefunden werden.</p> - <!-- animation from https://github.com/blairlee227/IlluStatus, licensed MIT --> <img th:src="@{/img/error_404_illustatus.svg}"/> </div> </div> diff --git a/prototype/src/main/resources/templates/error/500.html b/prototype/src/main/resources/templates/error/500.html index f14f141..4f9747f 100644 --- a/prototype/src/main/resources/templates/error/500.html +++ b/prototype/src/main/resources/templates/error/500.html @@ -9,10 +9,8 @@ <nav th:replace="fragments/header :: header">Header</nav> <main> <div class='content-width'> - <h1>Error 500</h1> + <h1>Ein Fehler ist aufgetreten. Bitte versuche es später nocheinmal.</h1> <div> - <p>Ein Fehler ist aufgetreten. Bitte versuche es später nocheinmal.</p> - <!-- animation from https://github.com/blairlee227/IlluStatus, licensed MIT --> <img th:src="@{/img/error_generic_illustatus.svg}"/> </div> </div> diff --git a/prototype/src/main/resources/templates/fragments/header.html b/prototype/src/main/resources/templates/fragments/header.html index b0b52bb..7b6ec7c 100644 --- a/prototype/src/main/resources/templates/fragments/header.html +++ b/prototype/src/main/resources/templates/fragments/header.html @@ -26,7 +26,7 @@ </form> </div> </div> - <a class="button" th:href="@{/shop/checkout}">Warenkorb</a> + <a class="button" th:href="@{/shop/checkout}">Warenkorb (<span th:text="${shoppingCart.itemCount}"></span>)</a> </div> <div th:if="${error}" class="error" id="error-msg"> <div class="content-width bar-flex"> diff --git a/prototype/src/main/resources/templates/shop/articles/id.html b/prototype/src/main/resources/templates/shop/articles/id.html index c99ed1d..588b5be 100644 --- a/prototype/src/main/resources/templates/shop/articles/id.html +++ b/prototype/src/main/resources/templates/shop/articles/id.html @@ -19,32 +19,29 @@ <div class="detailgrid"> <div class="s"> - <h1>Tolle Kamera</h1> + <h1 th:text="${article.title}"></h1> <script th:src="@{/js/back.js}"></script> <div class="back" data-group="shop" data-insert="true"></div> - <h2>25.14 EUR</h2> - <p> - Eine TOLLE Kamera <br> - Jaja du denkst jetzt bestimmt: "Bei dem Preis kann sie gar nich sooo TOLL sein". <br> - Aber glaub mir, sie is echt echt TOLL! <br> - Indianerehrenwort! - </p> + <h2><span th:text="${#numbers.formatDecimal(article.getPriceGross() * 0.01, 1, 'POINT', 2, 'COMMA')}"></span><span> EUR</span></h2> + <p th:text="${article.description}"></p> </div> <div class="s"> - <img th:src="@{/img/product-1.jpg}"/> + <img th:src="@{/shop/articles/{id}/image.jpg(id=${article.id})}"/> </div> <div class="s"></div> <form class="s" method="POST"> <div class="detailgrid m"> - <h2>50.28 EUR</h2> + <h2><span th:text="${#numbers.formatDecimal(article.getPriceGross() * 0.01, 1, 'POINT', 2, 'COMMA')}"></span><span> EUR</span></h2> <div> <label class="nolinebreak">Menge:</label> - <select size="1"> - <option>2</option> + <select name="quantity" size="1"> + <option th:each="quantity : ${#numbers.sequence(1,10)}" + th:value="${quantity}" + th:text="${quantity}"></option> </select> </div> - <h3 class="no-margin secondarytext">Auf Lager</h3> + <h3 class="no-margin secondarytext" th:text="${inStock} ? 'AUF LAGER' : 'NICHT AUF LAGER'"></h3> <button class="no-margin secondary" name="fastcheckout" value="false">In den Einkaufswagen </button> <button class="no-margin" name="fastcheckout" value="true">Schneller Checkout</button> @@ -56,26 +53,13 @@ <div class="sidebar-layout"> <div></div> <div> - <h1>Weitere Schnäpchen</h1> + <h1>Weitere Schnäppchen</h1> <div class='grid m base shadow'> - <section><a th:href="@{/shop/articles/1234}" class="section"> - <img th:src="@{/img/product-4.jpg}"> - <h2>Kamera Stativ.</h2> - <p class='price'> 25.14 EUR</p> - <p> - Das Stativ der Zukunft! Jetzt kaufen und verwenden für - wackelfreie Bilder aus der Zukunft!. - </p> - </a> - </section> - - <section><a th:href="@{/shop/articles/1234}" class="section"> - <img th:src="@{/img/product-5.jpg}"> - <h2>Bluetooth Ersatzfernbedinung</h2> - <p class='price'> 10.14 EUR</p> - <p> - Kann alles und jeden ausknippsen. - </p> + <section th:each="article: ${commercialArticles}"><a th:href="@{/shop/articles/{id}(id = ${article.id})}" class="section"> + <img th:src="@{/shop/articles/{id}/image.jpg(id=${article.id})}"/> + <h2 th:text="${article.title}"></h2> + <p class='price'><span th:text="${#numbers.formatDecimal(article.getPriceGross() * 0.01, 1, 'POINT', 2, 'COMMA')}"></span><span> EUR</span></p> + <p th:text="${article.description}"></p> </a> </section> </div> diff --git a/prototype/src/main/resources/templates/shop/checkout.html b/prototype/src/main/resources/templates/shop/checkout.html index e6bb15d..786885f 100644 --- a/prototype/src/main/resources/templates/shop/checkout.html +++ b/prototype/src/main/resources/templates/shop/checkout.html @@ -21,7 +21,17 @@ </div> <nav></nav> </div> -<main class="content-width sidebar-layout" style="min-height: 100vh;"> +<main th:if="${checkoutItems.size() == 0}"> + <div class="detailflex m"> + <h2> Noch keine Artikel im Warenkorb. </h2> + <p> + <img th:src="@{/img/undraw_successful_purchase_secondary.svg}"/> + </p> + <a class="button" th:href="@{/}"> Weiter shoppen </a> + </div> + +</main> +<main th:if="${checkoutItems.size() > 0}" class="content-width sidebar-layout" style="min-height: 100vh;"> <div style="max-width: 45em; width: 100%;"> <table> <tr> @@ -31,91 +41,32 @@ <th>Menge</th> <th></th> </tr> - <tr> - <td><a th:href="@{/shop/articles/4151}"><img th:src="@{/img/product-1.jpg}" class="s"/><a/></td> - <td><a th:href="@{/shop/articles/4151}">Kamera<a/></td> - <td>100,50 EUR</td> - <td> - <select size="1"> - <option value="1" selected>1</option> - <option value="2">2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5">5</option> - </select> - </td> - <td> - <button class="small">Entfernen</button> - </td> - </tr> - <tr> - <td><a th:href="@{/shop/articles/4151}"><img th:src="@{/img/product-2.jpg}" class="s"/><a/></td> - <td><a th:href="@{/shop/articles/4151}">Earbuds<a/></td> - <td>63,95 EUR</td> - <td> - <select size="1"> - <option value="1" selected>1</option> - <option value="2">2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5">5</option> - </select> - </td> - <td> - <button>Entfernen</button> - </td> - </tr> - <tr> - <td><a th:href="@{/shop/articles/4151}"><img th:src="@{/img/product-3.jpg}" class="s"/><a/></td> - <td><a th:href="@{/shop/articles/4151}">USB-Magic Light<a/></td> - <td>11,90 EUR</td> - <td> - <select size="1"> - <option value="1">1</option> - <option value="2" selected>2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5">5</option> - </select> - </td> - <td> - <button>Entfernen</button> - </td> - </tr> - <tr> - <td><a th:href="@{/shop/articles/4151}"><img th:src="@{/img/product-4.jpg}" class="s"/><a/></td> - <td><a th:href="@{/shop/articles/4151}">3D Magic Stativ<a/></td> - <td>15,99 EUR</td> - <td> - <select size="1"> - <option value="1">1</option> - <option value="2">2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5" selected>5</option> - </select> - </td> - <td> - <button>Entfernen</button> - </td> - </tr> - <tr> - <td><a th:href="@{/shop/articles/4151}"><img th:src="@{/img/product-5.jpg}" class="s"/><a/></td> - <td><a th:href="@{/shop/articles/4151}">Ersatzfernbedinung<a/></td> - <td>7,95 EUR</td> - <td> - <select size="1"> - <option value="1" selected>1</option> - <option value="2">2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5">5</option> - </select> - </td> - <td> - <button>Entfernen</button> - </td> - </tr> + <th:block th:each="item : ${checkoutItems}"> + <tr> + <td><a th:href="@{/shop/articles/{id}(id = ${item.article.id})}"><img + th:src="@{/shop/articles/{id}/image.jpg(id=${item.article.id})}" class="s"/></a></td> + + <td><a th:href="@{/shop/articles/{id}(id = ${item.article.id})}" + th:text="${item.article.title}"></a></td> + <td><span + th:text="${#numbers.formatDecimal(item.article.getPriceGross() * 0.01, 1, 'POINT', 2, 'COMMA')}"></span> + EUR <b>x</b></td> + <td> + <form method="POST" th:action="@{/shop/articles/{id}(id = ${item.article.id})}"> + <input type="hidden" name="fastcheckout" value="true"/> + <input type="hidden" name="set_amount" value="true"/> + <select name="quantity" size="1"> + <option th:value="${item.amount}" th:text="${item.amount}" selected></option> + <option value="0">Entfernen</option> + <option th:each="quantity : ${#numbers.sequence(1,10)}" + th:value="${quantity}" + th:text="${quantity}"></option> + </select> + <button class="small">Ändern</button> + </form> + </td> + </tr> + <th:block> </table> </div> <form method="POST" th:action="@{/shop/checkoutFinish}" style="min-width: 30em; max-width: 45em;"> @@ -124,11 +75,16 @@ <h1>Checkout</h1> </div> <div th:if="${user}"> + <input type="hidden" name="shopping_cart_revision" th:value="${shoppingCart.getRevision()}"/> + <input type="hidden" name="expected_total" th:value="${checkoutTotals.total}"/> + <h2>Lieferadresse:</h2> <textarea rows="5" class="full-width" type="text" name="address" - placeholder="Optional: Zusatz Optional: Unternehmen Straße Hausnummer Postleitzeit Ort Land"> + placeholder="Name Optional: Zusatz Optional: Unternehmen Straße Hausnummer Postleitzeit Ort Land" + th:text="${user.defaultDeliveryAddress != null ? user.defaultDeliveryAddress.toString() : ''}" + required> Max Mustermann -Musterstraße 42 +Musterstraße 4 42424 Mustertal </textarea> </div> @@ -138,12 +94,10 @@ Musterstraße 42 <input type="radio" name="type" value="priv" id="payment-card" required checked> <label for="payment-card">Kartenzahlung</label> <br/> <input class="full-width" type="text" id="cardnumber" name="cardnumber" placeholder="Kartennummer" + th:value="${user.defaultPayment != null ? user.defaultPayment.creditCardNumber : ''}" + pattern="[0-9]{6,16}" required/> </fieldset> - <fieldset> - <input class="" type="checkbox" id="bonus" name="bonus" checked/> - <label for="bonus"><h3>10 gesammelte Bonuspunkte verwenden</h3></label> - </fieldset> </div> <div> <h2>Bestellübersicht</h2> @@ -151,23 +105,26 @@ Musterstraße 42 <table> <tr> <th>Artikel (Netto)</th> - <th>200,29 EUR</th> - </tr> - <tr th:if="${user}"> - <th>Bonuspunkte</th> - <th>-5,00 EUR</th> - </tr> - <tr> - <th>Umsatzsteuer (19%)</th> - <th>35,00 EUR</th> - </tr> - <tr> - <th>Umsatzsteuer (7%)</th> - <th>2,50 EUR</th> + <th><span + th:text="${#numbers.formatDecimal(checkoutTotals.net * 0.01, 1, 'POINT', 2, 'COMMA')}"></span> + EUR + </th> </tr> + <th:block th:each="item : ${checkoutTotals.vatAmounts.entrySet()}"> + <tr> + <th>Umsatzsteuer (<span th:text="${item.getKey()}"></span>%)</th> + <th><span + th:text="${#numbers.formatDecimal(item.getValue() * 0.01, 1, 'POINT', 2, 'COMMA')}"></span> + EUR + </th> + </tr> + </th:block> <tr class="secondary"> <th>Gesamt:</th> - <th>240,79 EUR</th> + <th><span + th:text="${#numbers.formatDecimal(checkoutTotals.total * 0.01, 1, 'POINT', 2, 'COMMA')}"></span> + EUR + </th> </tr> <tr th:if="${user}" class="secondary"> <th colspan="2" class=" no-padding"></th> diff --git a/prototype/src/main/resources/templates/shop/index.html b/prototype/src/main/resources/templates/shop/index.html index 68a4a1f..54985ad 100644 --- a/prototype/src/main/resources/templates/shop/index.html +++ b/prototype/src/main/resources/templates/shop/index.html @@ -17,55 +17,17 @@ <h1>Angebote</h1> <script th:src="@{/js/back.js}"></script> <div class="back" data-group="shop" data-name="Zurück zur Startseite." data-insert="false"></div> - <div class='grid m base shadow'> - <section><a th:href="@{/shop/articles/1234}" class="section"> + <div th:if="${commercialArticles.size() == 0}"> + <h1>Momentan gibt es keine Angebote</h1> + </div> + <div class='grid m base shadow' th:if="${commercialArticles.size() != 0}"> + <section th:each="article: ${commercialArticles}"> + <a th:href="@{/shop/articles/{id}(id=${article.id})}" class="section"> - <img th:src="@{/img/product-1.jpg}"/> - <h2>Lorem Ipsum</h2> - <p class='price'> 25.14 EUR</p> - <p> - Als Gregor Samsa eines Morgens aus unruhigen Träumen erwachte. - </p> - </a> - </section> - <section><a th:href="@{/shop/articles/1234}" class="section"> - - <img th:src="@{/img/product-2.jpg}"/> - <h2>Lorem Ipsum</h2> - <p class='price'> 10.14 EUR</p> - <p> - Als Gregor Samsa eines Morgens aus unruhigen Träumen erwachte. - </p> - </a> - </section> - <section><a th:href="@{/shop/articles/1234}" class="section"> - - <img th:src="@{/img/product-3.jpg}"/> - <h2>Lorem Ipsum</h2> - <p class='price'> 25.14 EUR</p> - <p> - Als Gregor Samsa eines Morgens aus unruhigen Träumen erwachte. - </p> - </a> - </section> - <section><a th:href="@{/shop/articles/1234}" class="section"> - - <img th:src="@{/img/product-4.jpg}"/> - <h2>Lorem Ipsum</h2> - <p class='price'> 10.14 EUR</p> - <p> - Als Gregor Samsa eines Morgens aus unruhigen Träumen erwachte. - </p> - </a> - </section> - <section> - <a th:href="@{/shop/articles/1234}" class="section"> - <img th:src="@{/img/product-5.jpg}"/> - <h2>Lorem Ipsum</h2> - <p class='price'> 44.14 EUR</p> - <p> - Als Gregor Samsa eines Morgens aus unruhigen Träumen erwachte. - </p> + <img th:src="@{/shop/articles/{id}/image.jpg(id=${article.id})}"/> + <h2 th:text="${article.title}" /> + <p class='price'><span th:text="${#numbers.formatDecimal(article.getPriceGross() * 0.01, 1, 'POINT', 2, 'COMMA')}"></span><span> EUR</span></p> + <p th:text="${article.description}" /> </a> </section> <section class="spacer"></section> @@ -79,7 +41,9 @@ <div class=''> <div class='content-width'> <h1>Personalisierte Empfehlungen</h1> - <div class="grid l"> + + <!-- if a User is NOT logged in--> + <div class="grid l" th:if="${isLoggedIn == false}"> <img th:src="@{/img/undraw_successful_purchase_secondary.svg}"/> <div> <h2>Werde jetzt Kunde</h2> @@ -90,6 +54,26 @@ </p> </div> </div> + + <!-- If User is logged in but hasn't ordered anything yet--> + <div th:if="${isLoggedIn == true and hasOrders == false}"> + <h1>Jetzt Shoppen und Empfehlungen erhalten!</h1> + </div> + + <!-- If User is logged in and has ordered something before--> + <div th:if="${hasOrders == true}"> + <div class='grid m base shadow'> + <section th:each="article: ${suggestedArticles}"> + <a th:href="@{/shop/articles/{id}(id=${article.id})}" class="section"> + + <img th:src="@{/shop/articles/{id}/image.jpg(id=${article.id})}"/> + <h2 th:text="${article.title}" /> + <p class='price'><span th:text="${#numbers.formatDecimal(article.getPriceGross() * 0.01, 1, 'POINT', 2, 'COMMA')}"></span><span> EUR</span></p> + <p th:text="${article.description}" /> + </a> + </section> + </div> + </div> </div> <div class="vertical-spacer s"></div> </div>