// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-FileCopyrightText: 2023 swt23w23 package catering.order; import catering.catalog.CateringCatalog; import catering.catalog.Consumable; import catering.catalog.Rentable; import catering.staff.Employee; import catering.staff.StaffManagement; import org.salespointframework.catalog.Product; import org.salespointframework.inventory.UniqueInventory; import org.salespointframework.inventory.UniqueInventoryItem; import org.salespointframework.order.*; import org.salespointframework.quantity.Quantity; import org.salespointframework.useraccount.Role; import org.salespointframework.useraccount.UserAccount; import org.salespointframework.useraccount.web.LoggedIn; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.time.LocalDate; import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @Controller @PreAuthorize("isAuthenticated()") @SessionAttributes("event") public class OrderController { private final OrderManagement orderManagement; private final CustomOrderRepository customOrderRepository; private final UniqueInventory inventory; private final CateringCatalog catalog; private final StaffManagement staffManagement; public OrderController(OrderManagement orderManagement, CustomOrderRepository customOrderRepository, UniqueInventory inventory, CateringCatalog catalog, StaffManagement staffManagement) { this.orderManagement = orderManagement; this.customOrderRepository = customOrderRepository; this.catalog = catalog; this.inventory = inventory; this.staffManagement = staffManagement; } @GetMapping("/myOrders") @PreAuthorize("hasRole('CUSTOMER')") public String orders(Model model, @LoggedIn Optional userAccount) { List myOrders = orderManagement.findBy(userAccount.get()).stream().collect(Collectors.toList()); model.addAttribute("orders", myOrders); model.addAttribute("total", myOrders.size()); model.addAttribute("cancelled", OrderStatus.CANCELED); model.addAttribute("completed", OrderStatus.COMPLETED); return "orders"; } @GetMapping("/allOrders") @PreAuthorize("hasRole('ADMIN')") public String orders(Model model) { List myOrders = orderManagement.findAll(Pageable.unpaged()).stream().collect(Collectors.toList()); model.addAttribute("orders", myOrders); model.addAttribute("total", myOrders.size()); model.addAttribute("cancelled", OrderStatus.CANCELED); model.addAttribute("completed", OrderStatus.COMPLETED); return "orders"; } // For Theo: filters orders by day @GetMapping("/allOrders/{day}") @PreAuthorize("hasRole('ADMIN')") public String orders(@PathVariable String day, Model model) { // Obtains an instance of LocalDate from a text string such as 2007-12-03. LocalDate date = LocalDate.parse(day); List myOrders = customOrderRepository.findOrdersByInterval(date.atStartOfDay(), date.atStartOfDay().plusHours(23).withMinute(59).withSecond(59)) .stream().toList(); model.addAttribute("orders", myOrders); model.addAttribute("total", myOrders.size()); return "orders"; } @ModelAttribute("event") CustomCart initializeCart() { return new CustomCart(OrderType.EVENT_CATERING, LocalDateTime.now().plusDays(7), LocalDateTime.now().plusDays(7).plusHours(1)); } @GetMapping("/event") @PreAuthorize("hasRole('CUSTOMER')") public String event(Model model, @ModelAttribute("event") CustomCart cart) { model.addAttribute("cartRentables", cart.stream().filter(item -> item.getProduct() instanceof Rentable).toList()); model.addAttribute("cartConsumables", cart.stream().filter(item -> item.getProduct() instanceof Consumable).toList()); model.addAttribute("totalPrice", cart.getPrice()); Map invConsumables = new HashMap<>(); catalog.findConsumablesByCategoriesContains(cart.getOrderType().toString()).forEach(consumable -> invConsumables.put( consumable, inventory.findByProduct(consumable).get().getQuantity()) ); model.addAttribute("invConsumables", invConsumables); model.addAttribute( "invRentables", catalog.findRentablesByCategoriesContains(cart.getOrderType().toString()) .stream() .collect(Collectors.toMap(rentable -> rentable, rentable -> findFreeAmountInInterval( rentable, cart.getStart(), cart.getFinish(), inventory, customOrderRepository)))); model.addAttribute("allStaff", staffManagement.findAll().stream().toList()); model.addAttribute("duration", cart.getDurationInHours()); model.addAttribute("minDate", LocalDate.now().plusDays(7)); return "event"; } @PostMapping("/event/addEmployee") @PreAuthorize("hasRole('CUSTOMER')") String addEmployeeToCart(@RequestParam("sid") long employeeId, @ModelAttribute("event") CustomCart cart) { Employee employee = staffManagement.findById(employeeId).get(); if (cart.getStaff().contains(employee)) { return "redirect:/event"; } cart.addEmployee(employee); return "redirect:/event"; } @PostMapping("/event/removeEmployee") @PreAuthorize("hasRole('CUSTOMER')") String removeEmployeeFromCart(@RequestParam("sid") long employeeId, @ModelAttribute("event") CustomCart cart) { Employee employee = staffManagement.findById(employeeId).get(); cart.removeEmployee(employee); return "redirect:/event"; } @PostMapping("/allOrders/remove") @PreAuthorize("hasRole('ADMIN')") public String removeOrder(@RequestParam Order.OrderIdentifier orderID, @LoggedIn Optional userAccount) { return userAccount.map(account -> { if (account.hasRole(Role.of("ADMIN"))) { CustomOrder myOrder = orderManagement.get(orderID).get(); // FIXME orderManagement.cancelOrder(myOrder, "I have my own reasons."); return "redirect:/allOrders"; } return "redirect:/"; }).orElse("redirect:/"); } @PostMapping("/event/addProduct") @PreAuthorize("hasRole('CUSTOMER')") public String addProduct(@RequestParam("pid") Product product, @RequestParam("number") int number, @ModelAttribute("event") CustomCart cart) { Quantity amount = Quantity.of(number > 0 ? number : 1); Quantity cartQuantity = cart.getQuantity(product); Quantity available; if (product instanceof Rentable rentable) { available = findFreeAmountInInterval( rentable, cart.getStart(), cart.getFinish(), inventory, customOrderRepository); } else { available = inventory.findByProduct(product).get().getQuantity(); } // check for possible miss-inputs if (amount.add(cartQuantity).isGreaterThan(available)) { cart.addOrUpdateItem(product, cartQuantity.negate().add(available)); } else { cart.addOrUpdateItem(product, amount); } return "redirect:/event"; } @PostMapping("/event/removeProduct") @PreAuthorize("hasRole('CUSTOMER')") public String removeProduct(@RequestParam("itemId") String itemId, @ModelAttribute("event") CustomCart cart) { cart.removeItem(itemId); return "redirect:/event"; } @PostMapping("/event/changeDate") @PreAuthorize("hasRole('CUSTOMER')") public String changeDate(@RequestParam("startDate") LocalDate start, @RequestParam("startHour") Optional startHour, @RequestParam("finishDate") LocalDate finish, @RequestParam("finishHour") Optional finishHour, @ModelAttribute("event") CustomCart cart) { int unwrappedStartHour = startHour.orElse(cart.getStart().getHour()); int unwrappedFinishHour = finishHour.orElse(cart.getFinish().getHour()); finish = finish.isBefore(start) ? start : finish; LocalDateTime startTime = LocalDateTime.of(start, LocalTime.of(unwrappedStartHour, 0)); LocalDateTime finishTime = LocalDateTime.of(finish, LocalTime.of(unwrappedFinishHour <= unwrappedStartHour && !finish.isAfter(start) ? (unwrappedStartHour+1) % 24 : unwrappedFinishHour, 0)); cart.setStart(startTime); cart.setFinish(finishTime); return "redirect:/event"; } @PostMapping("/event/checkout") @PreAuthorize("hasRole('CUSTOMER')") public String checkout(@ModelAttribute("event") CustomCart cart, @LoggedIn Optional userAccount) { if (cart.isEmpty() && cart.getStaff().isEmpty()) { return "redirect:/event"; } return userAccount.map(account -> { CustomOrder myOrder = new CustomOrder(account.getId(), cart); cart.addItemsTo(myOrder); cart.addStaffTo(myOrder); cart.addRentablesToOrder(myOrder, inventory); orderManagement.payOrder(myOrder); orderManagement.completeOrder(myOrder); cart.clear(); return "redirect:/myOrders"; }).orElse("redirect:/event"); } @PostMapping("/event/changeOrderType") @PreAuthorize("hasRole('CUSTOMER')") public String changeOrderType(@RequestParam(name = "type") String orderType, @ModelAttribute("event") CustomCart cart) { try { cart.setOrderType(OrderType.valueOf(orderType)); } catch (IllegalArgumentException e) { cart.setOrderType(OrderType.EVENT_CATERING); } return "redirect:/event"; } public static Quantity findFreeAmountInInterval(Rentable product, LocalDateTime start, LocalDateTime finish, UniqueInventory inventory, CustomOrderRepository customOrderRepository) { return inventory.findByProduct(product) .map(item -> item.getQuantity().subtract( customOrderRepository.findOrdersByInterval(start, finish) .flatMap(order -> order.getOrderLines(product).stream()) .map(OrderLine::getQuantity).stream() .reduce(Quantity.NONE, Quantity::add))) .orElse(Quantity.NONE); } @GetMapping("/orders/calender") public String calender(Model model) { ArrayList> datesOfMonth = new ArrayList>(28); LocalDateTime startDateTime = LocalDateTime.now(); LocalDateTime endDateTime = startDateTime.plusDays(27).withHour(23).withMinute(59).withSecond(59); // FIXME: set me to end of day LocalDate startDate = startDateTime.toLocalDate(); LocalDate endDate = endDateTime.toLocalDate(); // create all dates of the calendar for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { ArrayList x = new ArrayList(2); x.add(Integer.toString(date.getDayOfMonth())); x.add(date.toString()); datesOfMonth.add(x); } // add each order overlapping with the calendar to the days it overlaps with for (CustomOrder order : customOrderRepository.findOrdersByInterval(startDateTime, endDateTime)) { int start_index_inclusive = Math.max((int) startDate.until(order.getStart().toLocalDate(), ChronoUnit.DAYS),0); int end_index_exclusive = Math.min((int) startDate.until(order.getFinish().toLocalDate(), ChronoUnit.DAYS), 27) + 1; String order_id = Objects.requireNonNull(order.getId()).toString(); for (int i = start_index_inclusive; i < end_index_exclusive; i++) { // FIXME: exchange order ids for a short name or a color datesOfMonth.get(i).add(order_id); } } // get names of weekdays for table header starting with the current day LocalDate endOfWeekDate = startDate.plusDays(6); ArrayList dayNames = new ArrayList(7); for (LocalDate date = startDate; !date.isAfter(endOfWeekDate); date = date.plusDays(1)) { dayNames.add(date.getDayOfWeek().toString()); } // FIXME: Get rid of the following paragraph of code by change of order_calender.html // put data of datesOfMonth inside current structure used in order_calender.html ArrayList>> weeksOfMonth = new ArrayList>>(); for (int i = 0; i < 4; i++) { weeksOfMonth.add(new ArrayList>(7)); for (int j = 0; j < 7; j++) { weeksOfMonth.get(i).add(new ArrayList()); weeksOfMonth.get(i).get(j).addAll(datesOfMonth.get( i*7+j ) ); } } model.addAttribute("weeksOfMonth", weeksOfMonth); model.addAttribute("dayNames", dayNames); return "orders_calender"; } @GetMapping("/myOrders/{order}/invoice") @PreAuthorize("isAuthenticated()") public String invoice(Model model, @LoggedIn UserAccount userAccount, @PathVariable Order order){ if(!order.getUserAccountIdentifier().equals(userAccount.getId()) && !userAccount.hasRole(Role.of("ADMIN"))){ return "redirect:/unauthorized"; } model.addAttribute("order", order); model.addAttribute("staffCharges", order.getChargeLines().stream() .collect(Collectors.toMap(Function.identity(), cl -> staffManagement.findById(Long.parseLong(cl.getDescription())).get()))); return "invoice"; } }