/* * Copyright (C) 2023 Simon Bruder * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package catering.inventory; import org.salespointframework.catalog.Product; import org.salespointframework.inventory.UniqueInventory; import org.salespointframework.inventory.UniqueInventoryItem; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.Assert; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import catering.catalog.CateringCatalog; import catering.catalog.Consumable; import catering.catalog.Rentable; import jakarta.validation.Valid; /* * TODO TL;DR: Use HandlerMethodArgumentResolver * * This class currently has many confusing methods that do a variety of things, * but mostly work around polymorphic restrictions of Spring. * Ideally, spring would allow a form to be polymorphic * based on its attributes, if they are unique, * or based on another parameter. * From what I could find, * this is not possible without implementing a custom HandlerMethodArgumentResolver. * If there is time, the controller should be changed to use this, * which should vastly simplify it. * * Adding will always require a type parameter, * as the type is not known at first. * However, currently there are two (or even three) POST handlers, * as there are two different form types it must support * (and an infinite amount of invalid types that it should ignore). * * Editing should not require passing any additional type parameter around * as the type can be inferred from the product. * Changing the type of a product should not be possible. */ @Controller @PreAuthorize("hasRole('ADMIN')") class InventoryController { private final UniqueInventory inventory; private final CateringCatalog cateringCatalog; InventoryController(UniqueInventory inventory, CateringCatalog cateringCatalog) { Assert.notNull(inventory, "Inventory must not be null!"); Assert.notNull(inventory, "CateringCatalog must not be null!"); this.inventory = inventory; this.cateringCatalog = cateringCatalog; } @GetMapping("/inventory") String list(Model model) { model.addAttribute("inventory", inventory.findAll()); return "inventory"; } @GetMapping("/inventory/edit/{pid}") String edit(Model model, @PathVariable Product pid) { UniqueInventoryItem item = inventory.findByProduct(pid).get(); final InventoryMutateForm form = InventoryMutateForm.of(pid, item); return edit(model, form); } String edit(Model model, InventoryMutateForm form) { model.addAttribute("actionIsAdd", false); model.addAttribute("form", form); return "inventory-mutate"; } @PostMapping(path = "/inventory/edit/{pid}", params = "type=Consumable") String editConsumable(@Valid @ModelAttribute("form") ConsumableMutateForm form, Errors result, @PathVariable Consumable pid, Model model) { return edit(form, result, pid, model); } @PostMapping(path = "/inventory/edit/{pid}", params = "type=Rentable") String editRentable(@Valid @ModelAttribute("form") RentableMutateForm form, Errors result, @PathVariable Rentable pid, Model model) { return edit(form, result, pid, model); } String edit(InventoryMutateForm form, Errors result, Product product, Model model) { if (result.hasErrors()) { return edit(model, form); } form.modifyProduct(product); product = cateringCatalog.save(product); UniqueInventoryItem item = inventory.findByProduct(product).get(); // no setQuantity in enterprise java // (though returing a modified object is actually nice) inventory.save(item.increaseQuantity(form.getQuantity().subtract(item.getQuantity()))); return "redirect:/inventory"; } @GetMapping(path = "/inventory/add") String add(Model model, @RequestParam String type) { switch (type) { case "Consumable": return add(model, new ConsumableMutateForm()); case "Rentable": return add(model, new RentableMutateForm()); default: // TODO better error handling return "redirect:/inventory"; } } String add(Model model, InventoryMutateForm form) { model.addAttribute("actionIsAdd", true); model.addAttribute("form", form); return "inventory-mutate"; } @PostMapping(path = "/inventory/add", params = "type=Consumable") String addConsumable(@Valid @ModelAttribute("form") ConsumableMutateForm form, Errors result, Model model) { return add(form, result, model); } @PostMapping(path = "/inventory/add", params = "type=Rentable") String addRentable(@Valid @ModelAttribute("form") ConsumableMutateForm form, Errors result, Model model) { return add(form, result, model); } String add(@Valid InventoryMutateForm form, Errors result, Model model) { if (result.hasErrors()) { return add(model, form); } inventory.save(new UniqueInventoryItem(cateringCatalog.save(form.toProduct()), form.getQuantity())); return "redirect:/inventory"; } @GetMapping("/inventory/delete/{pid}") String delete(@PathVariable Product pid) { UniqueInventoryItem item = inventory.findByProduct(pid).get(); inventory.delete(item); cateringCatalog.delete(pid); return "redirect:/inventory"; } }