Add staff form and bind it to model

Closes #51
This commit is contained in:
Denis Natusch 2023-11-28 09:48:06 +01:00
parent a46d2ad169
commit 0119b1cfa0
No known key found for this signature in database
GPG key ID: 5E57BD8EDACFA985
8 changed files with 169 additions and 38 deletions

View file

@ -300,6 +300,7 @@ image:models/design/staff.svg[class design diagram - Staff]
|StaffController |A Spring MVC Controller for handling staff-related operations such as adding, removing, and updating staff data. It interacts with StaffManagement. |StaffController |A Spring MVC Controller for handling staff-related operations such as adding, removing, and updating staff data. It interacts with StaffManagement.
|StaffManagement |A class that manages interactions and logic with StaffRepository. It provides methods to find, save/add, list, and delete staff members. |StaffManagement |A class that manages interactions and logic with StaffRepository. It provides methods to find, save/add, list, and delete staff members.
|StaffRepository |An extension of 'CrudRepository' that provides standard methods to perform CRUD operations on staff objects. |StaffRepository |An extension of 'CrudRepository' that provides standard methods to perform CRUD operations on staff objects.
|StaffForm |A Form to cache a user input that was made during registration or updating an Employee.
|=== |===
=== Link between analysis and design === Link between analysis and design

View file

@ -6,6 +6,8 @@ skinparam groupInheritance 2
package Spring { package Spring {
class CrudRepository class CrudRepository
class Streamable class Streamable
class Errors
class Model
} }
package catering.staff { package catering.staff {
@ -32,13 +34,18 @@ package catering.staff {
class StaffController { class StaffController {
+ StaffController(staffRepository: StaffRepository) + StaffController(staffRepository: StaffRepository)
+ getStaff(model: Model): String + getStaff(model: Model): String
+ getStaff(model: Model, form: StaffForm): String
+ removeEmployee(employee: Employee, model: Model): String + removeEmployee(employee: Employee, model: Model): String
+ addEmployee(name: String, job: JobType): String + addEmployee(form:StaffForm, result:Errors, model: Model): String
+ editEmployee(employee: Employee, model Model): String + editEmployee(employee: Employee, model Model): String
+ updateEmployee(employee: Employee, name: String, job: JobType): String + editEmployee(model Model, form:StaffForm): String
+ updateEmployee(employee: Employee, form:StaffForm): String
} }
StaffController --> StaffManagement : -staffManagement StaffController --> StaffManagement : -staffManagement
StaffController ..> Employee StaffController ..> Employee
StaffController ..> StaffForm
StaffController ..> Model
StaffController ..> Errors
class StaffManagement { class StaffManagement {
+ StaffManagement(staffRepository: StaffRepository) + StaffManagement(staffRepository: StaffRepository)
@ -56,6 +63,16 @@ package catering.staff {
StaffRepository ..|> Streamable StaffRepository ..|> Streamable
StaffRepository o-- Employee StaffRepository o-- Employee
class StaffForm {
- name: String
+ StaffForm(): void
+ getName(): String
+ getJob(): JobType
+ setName(name:String): void
+ setJob(job:JobType): void
+ validate(e:Errors): void
}
StaffForm ..> JobType : -job
} }
@enduml @enduml

BIN
src/main/asciidoc/models/design/staff.svg (Stored with Git LFS)

Binary file not shown.

View file

@ -3,11 +3,15 @@ package catering.staff;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; 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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import jakarta.validation.Valid;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.Errors;
@Controller @Controller
public class StaffController { public class StaffController {
@ -21,10 +25,26 @@ public class StaffController {
@GetMapping("/staff") @GetMapping("/staff")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public String getStaff(Model model) { public String getStaff(Model model) {
return getStaff(model, new StaffForm());
}
public String getStaff(Model model, @Valid StaffForm form) {
model.addAttribute("staff", staffManagement.findAll()); model.addAttribute("staff", staffManagement.findAll());
model.addAttribute("form", form);
return "staff"; return "staff";
} }
@PostMapping("/staff/add")
@PreAuthorize("hasRole('ADMIN')")
public String addEmployee(@Valid @ModelAttribute("form") StaffForm form, Errors result, Model model) {
form.validate(result);
if (result.hasErrors()) {
return getStaff(model, form);
}
staffManagement.save(new Employee(form.getName(), form.getJob()));
return "redirect:/staff";
}
@PostMapping("/staff/remove") @PostMapping("/staff/remove")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public String removeEmployee(@RequestParam("id") Employee employee, Model model) { public String removeEmployee(@RequestParam("id") Employee employee, Model model) {
@ -32,25 +52,30 @@ public class StaffController {
return "redirect:/staff"; return "redirect:/staff";
} }
@PostMapping("/staff/add")
@PreAuthorize("hasRole('ADMIN')")
public String addEmployee(@RequestParam String name, @RequestParam JobType job) {
staffManagement.save(new Employee(name, job));
return "redirect:/staff";
}
@GetMapping("/staff/edit/{id}") @GetMapping("/staff/edit/{id}")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public String editEmployee(@PathVariable("id") Employee employee, Model model) { public String editEmployee(@PathVariable("id") Employee employee, Model model) {
model.addAttribute("employee", employee); StaffForm form = new StaffForm();
form.setJob(employee.getJob());
form.setName(employee.getName());
return editEmployee(model, form);
}
public String editEmployee(Model model, @Valid StaffForm form) {
model.addAttribute("form", form);
return "edit-staff"; return "edit-staff";
} }
@PostMapping("/staff/edit/{id}") @PostMapping("/staff/edit/{id}")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public String updateEmployee(@PathVariable("id") Employee employee, @RequestParam String name, @RequestParam JobType job) { public String updateEmployee(@PathVariable("id") Employee employee, @Valid @ModelAttribute("form") StaffForm form,
employee.setJob(job); Errors result, Model model) {
employee.setName(name); form.validate(result);
if (result.hasErrors()) {
return editEmployee(model, form);
}
employee.setJob(form.getJob());
employee.setName(form.getName());
staffManagement.save(employee); staffManagement.save(employee);
return "redirect:/staff"; return "redirect:/staff";
} }

View file

@ -0,0 +1,38 @@
package catering.staff;
import org.springframework.validation.Errors;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
public class StaffForm {
private @NotEmpty String name;
private @NotNull JobType job;
public StaffForm() {
}
public String getName() {
return name;
}
public JobType getJob() {
return job;
}
public void setName(String name) {
this.name = name;
}
public void setJob(JobType job) {
this.job = job;
}
public void validate(Errors e) {
if (job == null) {
e.rejectValue("job", "job is not a job type");
}
}
}

View file

@ -7,17 +7,18 @@
</head> </head>
<body> <body>
<div layout:fragment="content"> <div layout:fragment="content">
<form th:object="${employee}" method="post"> <form th:object="${form}" method="post">
<input type="hidden" th:field="*{id}" />
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="name">Name:</label> <label class="form-label" for="name">Name:</label>
<input class="form-control" type="text" th:field="*{name}" required /> <input class="form-control" type="text" th:field="*{name}" th:errorclass="is-invalid" required/>
<div th:if="${#fields.hasErrors('name')}" class="invalid-feedback">Ungültiger Name</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="job">Beruf:</label> <label class="form-label" for="job">Beruf:</label>
<select name="job" class="form-select"> <select th:field="*{job}" class="form-select" th:errorclass="is-invalid">
<option th:each="type : ${T(catering.staff.JobType).values()}" th:value="${type}" th:selected="${type.name() == employee.job.name()}" th:text="${type}" required></option> <option th:each="type : ${T(catering.staff.JobType).values()}" th:value="${type}" th:text="${type}" required></option>
</select> </select>
<div th:if="${#fields.hasErrors('job')}" class="invalid-feedback">Ungültiger Job</div>
</div> </div>
<button class="btn btn-primary" type="submit">Speichern</button> <button class="btn btn-primary" type="submit">Speichern</button>
<a th:href="@{/staff}"><button type="button" class="btn btn-danger">Abbrechen</button></a> <a th:href="@{/staff}"><button type="button" class="btn btn-danger">Abbrechen</button></a>

View file

@ -37,16 +37,18 @@
</div> </div>
<div> <div>
<h2>Personal Hinzufügen</h2> <h2>Personal Hinzufügen</h2>
<form action="/staff/add" method="post"> <form th:object="${form}" th:action="@{/staff/add}" method="post">
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="name">Name</label> <label class="form-label" for="name">Name</label>
<input class="form-control" type="text" name="name" required /> <input class="form-control" type="text" th:field="*{name}" th:errorclass="is-invalid" required>
<div th:if="${#fields.hasErrors('name')}" class="invalid-feedback">Ungültiger Name</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="job">Beruf:</label> <label class="form-label" for="job">Beruf:</label>
<select name="job" class="form-select"> <select th:field="*{job}" class="form-select" th:errorclass="is-invalid">
<option th:each="type : ${T(catering.staff.JobType).values()}" th:value="${type}" th:text="${type}" required></option> <option th:each="type : ${T(catering.staff.JobType).values()}" th:value="${type}" th:text="${type}" required></option>
</select> </select>
<div th:if="${#fields.hasErrors('job')}" class="invalid-feedback">Ungültiger Job</div>
</div> </div>
<button class="btn btn-primary" type="submit">Hinzufügen</button> <button class="btn btn-primary" type="submit">Hinzufügen</button>
</form> </form>

View file

@ -29,8 +29,8 @@ class StaffControllerIntegrationTests {
MockMvc mvc; MockMvc mvc;
MockHttpServletRequestBuilder createStaff = post("/staff/add") MockHttpServletRequestBuilder createStaff = post("/staff/add")
.param("name", "Karl Baum") .param("name", "Karl Baum")
.param("job", "COOK"); .param("job", "COOK");
@Autowired @Autowired
private StaffManagement staffManagement; private StaffManagement staffManagement;
@ -55,16 +55,16 @@ class StaffControllerIntegrationTests {
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN") @WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
void viewStaff() throws Exception { void viewStaff() throws Exception {
mvc.perform(get("/staff")) mvc.perform(get("/staff"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(containsString(defaultEmployee.getName()))); .andExpect(content().string(containsString(defaultEmployee.getName())));
} }
@Test @Test
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN") @WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
void viewStaffEditPage() throws Exception { void viewStaffEditPage() throws Exception {
mvc.perform(get("/staff/edit/" + defaultEmployeeId.toString())) mvc.perform(get("/staff/edit/" + defaultEmployeeId.toString()))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(containsString(defaultEmployee.getName()))); .andExpect(content().string(containsString(defaultEmployee.getName())));
} }
@Test @Test
@ -83,12 +83,12 @@ class StaffControllerIntegrationTests {
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN") @WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
void modifyStaff() throws Exception { void modifyStaff() throws Exception {
mvc.perform(post("/staff/edit/" + defaultEmployeeId.toString()) mvc.perform(post("/staff/edit/" + defaultEmployeeId.toString())
.param("name", "Dieter Bäume") .param("name", "Dieter Bäume")
.param("job", "SERVICE")).andExpect(redirectedUrl("/staff")) .param("job", "SERVICE")).andExpect(redirectedUrl("/staff"))
.andExpect(redirectedUrl("/staff")); .andExpect(redirectedUrl("/staff"));
assertThat(staffManagement.findById(defaultEmployeeId).get()) assertThat(staffManagement.findById(defaultEmployeeId).get())
.extracting("name", "job") .extracting("name", "job")
.containsExactly("Dieter Bäume", JobType.SERVICE); .containsExactly("Dieter Bäume", JobType.SERVICE);
} }
@Test @Test
@ -103,6 +103,53 @@ class StaffControllerIntegrationTests {
.doesNotContain("Dieter Baum"); .doesNotContain("Dieter Baum");
} }
@Test
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
void addStaffWrong() throws Exception {
mvc.perform(post("/staff/add"))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültiger Job")));
}
@Test
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
void editStaffWrong() throws Exception {
mvc.perform(post("/staff/edit/" + defaultEmployeeId.toString()))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültiger Job")));
assertThat(staffManagement.findById(defaultEmployeeId).get())
.extracting("name", "job")
.containsExactly("Dieter Baum", JobType.COOK);
}
@Test
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
void addStaffWithWrongJobType() throws Exception {
assertThat(staffManagement.findAll().stream())
.extracting("name")
.doesNotContain("Paul Kunst");
mvc.perform(post("/staff/add")
.param("name", "Paul Kunst")
.param("job", "notAJob"))
.andExpect(content().string(containsString("Ungültiger Job")));
assertThat(staffManagement.findAll().stream())
.extracting("name")
.doesNotContain("Paul Kunst");
}
@Test
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
void editStaffWithWrongJobType() throws Exception {
mvc.perform(
post("/staff/edit/" + defaultEmployeeId.toString())
.param("name", defaultEmployee.getName())
.param("job", "notAJob"))
.andExpect(content().string(containsString("Ungültiger Job")));
assertThat(staffManagement.findById(defaultEmployeeId).get())
.extracting("name", "job")
.containsExactly("Dieter Baum", JobType.COOK);
}
@Test @Test
@WithMockUser(username = "dieter", password = "123", roles = "CUSTOMER") @WithMockUser(username = "dieter", password = "123", roles = "CUSTOMER")
void refuseCustomer() throws Exception { void refuseCustomer() throws Exception {
@ -114,10 +161,10 @@ class StaffControllerIntegrationTests {
@WithAnonymousUser @WithAnonymousUser
void refuseAnonymous() throws Exception { void refuseAnonymous() throws Exception {
mvc.perform(get("/staff")) mvc.perform(get("/staff"))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andExpect(header().string(HttpHeaders.LOCATION, endsWith("/login"))); .andExpect(header().string(HttpHeaders.LOCATION, endsWith("/login")));
mvc.perform(get("/staff/edit/" + defaultEmployeeId)) mvc.perform(get("/staff/edit/" + defaultEmployeeId))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andExpect(header().string(HttpHeaders.LOCATION, endsWith("/login"))); .andExpect(header().string(HttpHeaders.LOCATION, endsWith("/login")));
} }
} }