not yet finished manipulations of camp registrations

This commit is contained in:
Jottyfan 2023-03-19 23:36:41 +01:00
parent bc7cd88c2f
commit 502e616c44
8 changed files with 673 additions and 5 deletions

View File

@ -15,6 +15,7 @@ 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.servlet.mvc.support.RedirectAttributes;
import de.jottyfan.camporganizer.module.camplist.CommonController;
import de.jottyfan.camporganizer.module.mail.MailBean;
@ -143,4 +144,64 @@ public class AdminController extends CommonController {
service.deleteLocation(id);
return "redirect:/admin/location";
}
@GetMapping("/admin/camp")
public String getCamplist(Model model, HttpServletRequest request) {
super.setupSession(model, request);
model.addAttribute("camps", service.getAllCamps());
model.addAttribute("locations", service.getLocations());
return "/admin/camp";
}
@GetMapping("/admin/camp/add")
public String prepareAddCamp(Model model, HttpServletRequest request) {
super.setupSession(model, request);
model.addAttribute("bean", new CampBean());
model.addAttribute("documents", service.getCampDocuments());
model.addAttribute("locations", service.getLocations());
model.addAttribute("profiles", service.getProfiles());
return "/admin/camp_edit";
}
@GetMapping("/admin/camp/edit/{id}")
public String prepareEditCamp(@PathVariable Integer id, Model model, HttpServletRequest request) {
super.setupSession(model, request);
model.addAttribute("bean", service.getCamp(id));
model.addAttribute("documents", service.getCampDocuments());
model.addAttribute("locations", service.getLocations());
model.addAttribute("profiles", service.getProfiles());
String error = (String) request.getAttribute("error");
if (error != null) {
model.addAttribute("error", error);
}
return "/admin/camp_edit";
}
@PostMapping("/admin/camp/update")
public String updateDocument(@Valid @ModelAttribute("bean") CampBean bean,
final BindingResult bindingResult, Model model, HttpServletRequest request, RedirectAttributes redirect) {
super.setupSession(model, request);
if (bindingResult.hasErrors()) {
for (ObjectError error : bindingResult.getAllErrors()) {
LOGGER.error("error {}: {}", error.getCode(), error.getDefaultMessage());
}
model.addAttribute("documents", service.getCampDocuments());
model.addAttribute("locations", service.getLocations());
model.addAttribute("profiles", service.getProfiles());
return "/admin/camp_edit";
}
String error = service.upsertCamp(bean);
redirect.addAttribute("error", error);
Integer pk = bean.getPk();
String errorDest = pk == null ? "redirect:/admin/camp/add" : "redirect:/admin/camp/edit/" + bean.getPk();
return error != null ? errorDest : "redirect:/admin/camp";
}
@GetMapping("/admin/camp/delete/{id}")
public String deleteCamp(@PathVariable Integer id, Model model, HttpServletRequest request, RedirectAttributes redirect) {
super.setupSession(model, request);
String error = service.deleteCamp(id);
redirect.addAttribute("error", error);
return error != null ? "redirect:/admin/camp/edit/" + id : "redirect:/admin/camp";
}
}

View File

@ -4,6 +4,8 @@ import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_DOCUMENT;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_DOCUMENTROLE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_LOCATION;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import java.util.ArrayList;
import java.util.Arrays;
@ -42,6 +44,8 @@ import de.jottyfan.camporganizer.db.jooq.tables.records.TCampRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TDocumentRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TDocumentroleRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TLocationRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord;
import de.jottyfan.camporganizer.module.camplist.LambdaResultWrapper;
/**
@ -81,7 +85,7 @@ public class AdminRepository {
// @formatter:on
LOGGER.debug(sql.toString());
Record5<Integer, String, EnumDocument, EnumFiletype, EnumCamprole[]> r = sql.fetchOne();
if (r != null ) {
if (r != null) {
DocumentBean bean = new DocumentBean();
bean.setPk(r.get(T_DOCUMENT.PK));
bean.setName(r.get(T_DOCUMENT.NAME));
@ -234,8 +238,7 @@ public class AdminRepository {
/**
* delete entry from t_document where pk = ?
*
* @param pk
* to be used as reference
* @param pk to be used as reference
* @return number of affected database lines
* @throws DataAccessException
*/
@ -353,4 +356,83 @@ public class AdminRepository {
});
return lrw.getCounter();
}
/**
* get all camps from the database
*
* @return a list of camps; an empty list at least
*/
public List<CampBean> getAllCamps() {
SelectWhereStep<TCampRecord> sql = jooq.selectFrom(T_CAMP);
LOGGER.debug(sql.toString());
List<CampBean> list = new ArrayList<>();
for (TCampRecord r : sql.fetch()) {
list.add(CampBean.of(r));
}
return list;
}
/**
* get the camp of id
*
* @param id the ID of the camp
* @return the camp or null
*/
public CampBean getCamp(Integer id) {
SelectConditionStep<TCampRecord> sql = jooq.selectFrom(T_CAMP).where(T_CAMP.PK.eq(id));
LOGGER.debug(sql.toString());
return CampBean.of(sql.fetchOne());
}
/**
* delete the camp and all of its dependencies
*
* @param id the ID of the camp
* @return error message
*/
public String deleteCamp(Integer id) {
LambdaResultWrapper lrw = new LambdaResultWrapper();
jooq.transaction(t -> {
SelectConditionStep<TPersonRecord> sql1 = DSL.using(t).selectFrom(T_PERSON).where(T_PERSON.FK_CAMP.eq(id));
LOGGER.debug(sql1.toString());
Integer registrations = sql1.fetch().size();
if (registrations < 1) {
DeleteConditionStep<TCampRecord> sql2 = DSL.using(t).deleteFrom(T_CAMP).where(T_CAMP.PK.eq(id));
LOGGER.debug(sql2.toString());
sql2.execute();
} else {
lrw.putString("error", String.format("Es gibt bereits %d Anmeldungen. Die Freizeit kann daher nicht gelöscht werden.", registrations));
}
});
return lrw.getString("error");
}
/**
* upsert the camp
*
* @param bean the bean
*/
public String upsertCamp(@Valid CampBean bean) {
if (bean.getPk() == null) {
// TODO: insert
} else {
// TODO: update
}
return "not yet implemented";
}
/**
* get all profiles from the db
*
* @return the profiles
*/
public List<ProfileBean> getProfiles() {
SelectWhereStep<TProfileRecord> sql = jooq.selectFrom(T_PROFILE);
LOGGER.debug(sql.toString());
List<ProfileBean> list = new ArrayList<>();
for (TProfileRecord r : sql.fetch()) {
list.add(ProfileBean.of(r));
}
return list;
}
}

View File

@ -9,7 +9,6 @@ import javax.validation.Valid;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.exception.DataAccessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -115,7 +114,7 @@ public class AdminService {
*/
public void deleteLocation(Integer id) {
// TODO: if a location is still in use by a camp, forbid deleting it
adminRepository.deleteLocation(id);
adminRepository.deleteLocation(id);
}
/**
@ -126,4 +125,61 @@ public class AdminService {
public List<DocumentBean> getLocationDocuments() {
return adminRepository.getAllDocumentsWith(T_DOCUMENT.DOCTYPE.eq(EnumDocument.location));
}
/**
* get all documents that fit to the camp definitions
*
* @return the camp documents
*/
public List<DocumentBean> getCampDocuments() {
return adminRepository.getAllDocumentsWith(T_DOCUMENT.DOCTYPE.eq(EnumDocument.camp));
}
/**
* get all camp beans from the database
*
* @return all camp beans; an empty list at least
*/
public List<CampBean> getAllCamps() {
return adminRepository.getAllCamps();
}
/**
* get all profiles from the db
*
* @return the profiles
*/
public List<ProfileBean> getProfiles() {
return adminRepository.getProfiles();
}
/**
* get the camp of id
*
* @param id the ID of the camp
* @return the camp or null
*/
public CampBean getCamp(Integer id) {
return adminRepository.getCamp(id);
}
/**
* upsert the camp
*
* @param bean the bean
* @return the error message, if any
*/
public String upsertCamp(@Valid CampBean bean) {
return adminRepository.upsertCamp(bean);
}
/**
* delete the camp and all of its dependencies
*
* @param id the ID of the camp
* @return the error message, if any
*/
public String deleteCamp(Integer id) {
return adminRepository.deleteCamp(id);
}
}

View File

@ -0,0 +1,234 @@
package de.jottyfan.camporganizer.module.admin;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampRecord;
/**
*
* @author jotty
*
*/
public class CampBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pk;
@NotBlank
private String name;
@NotNull
private Integer fkDocument;
@NotNull
private Integer fkLocation;
@NotNull
private Integer fkProfile;
private Boolean lockSales;
@NotNull
private Integer maxAge;
@NotNull
private Integer minAge;
@NotNull
private LocalDateTime arrive;
@NotNull
private LocalDateTime depart;
private String countries;
@NotNull
private String price;
/**
* generate a camp bean out of r
*
* @param r the record
* @return the camp bean
*/
public static final CampBean of(TCampRecord r) {
if (r == null) {
return null;
}
CampBean bean = new CampBean();
bean.setArrive(r.getArrive());
bean.setCountries(r.getCountries());
bean.setDepart(r.getDepart());
bean.setFkDocument(r.getFkDocument());
bean.setFkLocation(r.getFkLocation());
bean.setFkProfile(r.getFkProfile());
bean.setLockSales(r.getLockSales());
bean.setMaxAge(r.getMaxAge());
bean.setMinAge(r.getMinAge());
bean.setName(r.getName());
bean.setPk(r.getPk());
bean.setPrice(r.getPrice());
return bean;
}
/**
* @return the pk
*/
public Integer getPk() {
return pk;
}
/**
* @param pk the pk to set
*/
public void setPk(Integer pk) {
this.pk = pk;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the fkDocument
*/
public Integer getFkDocument() {
return fkDocument;
}
/**
* @param fkDocument the fkDocument to set
*/
public void setFkDocument(Integer fkDocument) {
this.fkDocument = fkDocument;
}
/**
* @return the fkLocation
*/
public Integer getFkLocation() {
return fkLocation;
}
/**
* @param fkLocation the fkLocation to set
*/
public void setFkLocation(Integer fkLocation) {
this.fkLocation = fkLocation;
}
/**
* @return the fkProfile
*/
public Integer getFkProfile() {
return fkProfile;
}
/**
* @param fkProfile the fkProfile to set
*/
public void setFkProfile(Integer fkProfile) {
this.fkProfile = fkProfile;
}
/**
* @return the lockSales
*/
public Boolean getLockSales() {
return lockSales;
}
/**
* @param lockSales the lockSales to set
*/
public void setLockSales(Boolean lockSales) {
this.lockSales = lockSales;
}
/**
* @return the maxAge
*/
public Integer getMaxAge() {
return maxAge;
}
/**
* @param maxAge the maxAge to set
*/
public void setMaxAge(Integer maxAge) {
this.maxAge = maxAge;
}
/**
* @return the minAge
*/
public Integer getMinAge() {
return minAge;
}
/**
* @param minAge the minAge to set
*/
public void setMinAge(Integer minAge) {
this.minAge = minAge;
}
/**
* @return the arrive
*/
public LocalDateTime getArrive() {
return arrive;
}
/**
* @param arrive the arrive to set
*/
public void setArrive(LocalDateTime arrive) {
this.arrive = arrive;
}
/**
* @return the depart
*/
public LocalDateTime getDepart() {
return depart;
}
/**
* @param depart the depart to set
*/
public void setDepart(LocalDateTime depart) {
this.depart = depart;
}
/**
* @return the countries
*/
public String getCountries() {
return countries;
}
/**
* @param countries the countries to set
*/
public void setCountries(String countries) {
this.countries = countries;
}
/**
* @return the price
*/
public String getPrice() {
return price;
}
/**
* @param price the price to set
*/
public void setPrice(String price) {
this.price = price;
}
}

View File

@ -0,0 +1,71 @@
package de.jottyfan.camporganizer.module.admin;
import java.io.Serializable;
import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord;
/**
*
* @author jotty
*
*/
public class ProfileBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pk;
private String forename;
private String surname;
public static final ProfileBean of(TProfileRecord r) {
if (r == null) {
return null;
}
ProfileBean bean = new ProfileBean();
bean.setPk(r.getPk());
bean.setForename(r.getForename());
bean.setSurname(r.getSurname());
return bean;
}
/**
* @return the pk
*/
public Integer getPk() {
return pk;
}
/**
* @param pk the pk to set
*/
public void setPk(Integer pk) {
this.pk = pk;
}
/**
* @return the forename
*/
public String getForename() {
return forename;
}
/**
* @param forename the forename to set
*/
public void setForename(String forename) {
this.forename = forename;
}
/**
* @return the surname
*/
public String getSurname() {
return surname;
}
/**
* @param surname the surname to set
*/
public void setSurname(String surname) {
this.surname = surname;
}
}

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="tablebox" sec:authorize="hasRole('admin')">
<table id="docs" class="table table-striped" style="width: 100% !important">
<thead>
<tr>
<td>Name</td>
<td>Ort</td>
<td>Zeitraum</td>
<td>Bestätigung</td>
</tr>
</thead>
<tbody>
<tr th:each="c : ${camps}">
<td><a th:href="@{/admin/camp/edit/{id}(id=${c.pk})}"><span th:text="${c.name}"></span></a></td>
<td><th:block th:each="l : ${locations}">
<span th:if="${l.pk == c.fkLocation}" th:text="${l.name}"></span>
</th:block></td>
<td><span th:text="${#temporals.format(c.arrive, 'dd.MM.')}"></span>&nbsp;-&nbsp;<span th:text="${#temporals.format(c.depart, 'dd.MM.yyyy')}"></span></td>
<td><a th:href="@{/document/{id}(id=${c.fkDocument})}"><i class="fas fa-download"></i></a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="6" style="text-align: center"><a th:href="@{/admin/camp/add}" class="btn btn-outline-primary">neue Freizeit anlegen</a></td>
</tr>
</tfoot>
</table>
<script>
$(document).ready(function() {
$("#docs").DataTable({
language : locale_de
});
});
</script>
</div>
</th:block>
</body>
</html>

View File

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div sec:authorize="hasRole('admin')">
<form th:action="@{/admin/camp/update}" th:object="${bean}" method="post" enctype="multipart/form-data">
<div class="tablebox">
<div class="container">
<input type="hidden" th:field="*{pk}" />
<div class="row mb-2">
<div class="col-sm-12">
<div class="alert alter-danger" th:if="${error}" th:text="${error}"></div>
</div>
</div>
<div class="row mb-2">
<label for="inputName" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<span class="error" th:each="error : ${#fields.errors('name')}">[[${error}]]<br /></span> <input id="inputName" type="text" th:field="*{name}"
th:class="${'form-control ' + (#fields.hasErrors('name') ? 'inputerror' : '')}">
</div>
</div>
<div class="row mb-2">
<label for="inputLocation" class="col-sm-2 col-form-label">Ort</label>
<div class="col-sm-10">
<span class="error" th:each="error : ${#fields.errors('fkLocation')}">[[${error}]]<br /></span>
<select id="inputLocation" th:field="*{fkLocation}" th:class="${'form-select ' + (#fields.hasErrors('fkLocation') ? 'inputerror' : '')}">
<option value="">--- bitte wählen ---</option>
<option th:each="l : ${locations}" th:value="${l.pk}" th:text="${l.name}"></option>
</select>
</div>
</div>
<div class="row mb-2">
<label for="inputArrive" class="col-sm-2 col-form-label">von</label>
<div class="col-sm-4">
<span class="error" th:each="error : ${#fields.errors('arrive')}">[[${error}]]<br /></span> <input id="inputArrive" type="date" th:field="*{arrive}"
th:class="${'form-control ' + (#fields.hasErrors('arrive') ? 'inputerror' : '')}">
</div>
<label for="inputDepart" class="col-sm-2 col-form-label">bis</label>
<div class="col-sm-4">
<span class="error" th:each="error : ${#fields.errors('depart')}">[[${error}]]<br /></span> <input id="inputDepart" type="date" th:field="*{depart}"
th:class="${'form-control ' + (#fields.hasErrors('depart') ? 'inputerror' : '')}">
</div>
</div>
<div class="row mb-2">
<label for="inputMinAge" class="col-sm-2 col-form-label">Mindestalter</label>
<div class="col-sm-4">
<span class="error" th:each="error : ${#fields.errors('minAge')}">[[${error}]]<br /></span> <input id="inputMinAge" type="number" th:field="*{minAge}"
th:class="${'form-control ' + (#fields.hasErrors('minAge') ? 'inputerror' : '')}">
</div>
<label for="inputMaxAge" class="col-sm-2 col-form-label">Maximalalter</label>
<div class="col-sm-4">
<span class="error" th:each="error : ${#fields.errors('maxAge')}">[[${error}]]<br /></span> <input id="inputMaxAge" type="number" th:field="*{maxAge}"
th:class="${'form-control ' + (#fields.hasErrors('maxAge') ? 'inputerror' : '')}">
</div>
</div>
<div class="row mb-2">
<label for="inputPrice" class="col-sm-2 col-form-label">Preis</label>
<div class="col-sm-10">
<span class="error" th:each="error : ${#fields.errors('price')}">[[${error}]]<br /></span> <textarea id="inputPrice" type="text" th:field="*{price}"
th:class="${'form-control ' + (#fields.hasErrors('price') ? 'inputerror' : '')}"></textarea>
</div>
</div>
<div class="row mb-2">
<label for="inputCountries" class="col-sm-2 col-form-label">Ferien in</label>
<div class="col-sm-10"><!-- TODO: input helper for finding Bundesland by typing -->
<span class="error" th:each="error : ${#fields.errors('countries')}">[[${error}]]<br /></span> <textarea id="inputCountries" type="text" th:field="*{countries}"
th:class="${'form-control ' + (#fields.hasErrors('countries') ? 'inputerror' : '')}"></textarea>
</div>
</div>
<div class="row mb-2">
<label for="inputDoc" class="col-sm-2 col-form-label">Bestätigung</label>
<div class="col-sm-10">
<span class="error" th:each="error : ${#fields.errors('fkDocument')}">[[${error}]]<br /></span> <select id="inputDoc" th:field="*{fkDocument}"
th:class="${'form-select ' + (#fields.hasErrors('fkDocument') ? 'inputerror' : '')}">
<option value="">--- bitte wählen ---</option>
<option th:each="d : ${documents}" th:value="${d.pk}" th:text="${d.name}"></option>
</select>
<script type="text/javascript">
$("#inputDoc").select2({
theme : 'bootstrap-5',
});
</script>
</div>
</div>
<div class="row mb-2">
<label for="inputProfile" class="col-sm-2 col-form-label">Verantwortlicher</label>
<div class="col-sm-10">
<span class="error" th:each="error : ${#fields.errors('fkProfile')}">[[${error}]]<br /></span> <select id="inputProfile" th:field="*{fkProfile}"
th:class="${'form-select ' + (#fields.hasErrors('fkProfile') ? 'inputerror' : '')}">
<option value="">--- bitte wählen ---</option>
<option th:each="p : ${profiles}" th:value="${p.pk}" th:text="${p.forename} + ' ' + ${p.surname}"></option>
</select>
<script type="text/javascript">
$("#inputProfile").select2({
theme : 'bootstrap-5',
});
</script>
</div>
</div>
<!-- TODO: lock sales - boolean - Kassenschluss -->
<div class="row mb-2">
<div class="col-sm-2"></div>
<div class="col-sm-10">
<input type="submit" class="btn btn-success" value="Ok" />
<a th:href="@{/admin/camp}" class="btn btn-outline-secondary">Abbrechen</a>
<div class="dropdown" style="display: inline" th:if="${bean.pk}">
<button class="btn btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-trash-alt"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" th:href="@{/admin/camp/delete/{id}(id=${bean.pk})}">Freizeit endgültig löschen</a>
</ul>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</th:block>
</body>
</html>

View File

@ -123,6 +123,7 @@
<li><a th:href="@{/admin/mail}" class="dropdown-item menufont">Testmail</a></li>
<li><a th:href="@{/admin/document}" class="dropdown-item menufont">Dokumente</a></li>
<li><a th:href="@{/admin/location}" class="dropdown-item menufont">Freizeitheime</a></li>
<li><a th:href="@{/admin/camp}" class="dropdown-item menufont">Freizeiten</a>
</ul>
</div>
</li>