integrated module confirmation

This commit is contained in:
Jottyfan 2022-10-08 16:42:21 +02:00
parent fc0bc35e28
commit 0e9088dc1c
19 changed files with 1561 additions and 4 deletions

View File

@ -57,7 +57,7 @@ public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
super.configure(http);
http.authorizeRequests()
// @formatter:off
.antMatchers("/dashboard/**", "/business/**").authenticated()
.antMatchers("/dashboard/**", "/business/**", "/confirmation/**").authenticated()
.anyRequest().permitAll();
// @formatter:on
// http.anonymous().disable();

View File

@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.GetMapping;
/**
*
* @author henkej
* @author jotty
*
*/
@Controller

View File

@ -16,7 +16,7 @@ import de.jottyfan.camporganizer.module.business.privileges.impl.PrivilegesBean;
/**
*
* @author henkej
* @author jotty
*
*/
@Controller

View File

@ -0,0 +1,47 @@
package de.jottyfan.camporganizer.module.confirmation.confirmation;
import java.time.LocalDate;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
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 de.jottyfan.camporganizer.module.confirmation.confirmation.impl.CampOverviewBean;
/**
*
* @author jotty
*
*/
@Controller
public class ConfirmationController {
@Autowired
private HttpServletRequest request;
@Autowired
private IConfirmationService indexService;
@GetMapping("/confirmation")
public String getIndex(Model model) {
String username = indexService.getCurrentUser(request);
List<CampOverviewBean> campoverview = indexService.getCampOverview(request);
CampOverviewBean campoverviewsummary = new CampOverviewBean(LocalDate.now(), "summary");
for (CampOverviewBean bean : campoverview) {
campoverviewsummary.setApproved(bean.getApproved() + campoverviewsummary.getApproved());
campoverviewsummary.setRejected(bean.getRejected() + campoverviewsummary.getRejected());
campoverviewsummary.setUntouched(bean.getUntouched() + campoverviewsummary.getUntouched());
}
model.addAttribute("currentUser", username);
model.addAttribute("campoverview", campoverview);
model.addAttribute("campoverviewsummary", campoverviewsummary);
model.addAttribute("untouched", indexService.getUntouched(request));
model.addAttribute("approved", indexService.getApproved(request));
model.addAttribute("rejected", indexService.getRejected(request));
return "confirmation/confirmation";
}
}

View File

@ -0,0 +1,55 @@
package de.jottyfan.camporganizer.module.confirmation.confirmation;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import de.jottyfan.camporganizer.module.confirmation.confirmation.impl.BookingBean;
import de.jottyfan.camporganizer.module.confirmation.confirmation.impl.CampOverviewBean;
/**
*
* @author jotty
*
*/
public interface IConfirmationService {
/**
* get the user of this session
*
* @param request the request
* @return the username of the current user
*/
public String getCurrentUser(HttpServletRequest request);
/**
* get a list of the camps and its booking status
*
* @param request the request
* @return the camp overview beans
*/
public List<CampOverviewBean> getCampOverview(HttpServletRequest request);
/**
* get a list of bookings that have not yet been worked on
*
* @param request the request
* @return the list of untouched bookings
*/
public List<BookingBean> getUntouched(HttpServletRequest request);
/**
* get a list of approved bookings
*
* @param request the request
* @return the list of approved bookings
*/
public List<BookingBean> getApproved(HttpServletRequest request);
/**
* get a list of rejected bookings
*
* @param request the request
* @return the list of rejected bookings
*/
public List<BookingBean> getRejected(HttpServletRequest request);
}

View File

@ -0,0 +1,113 @@
package de.jottyfan.camporganizer.module.confirmation.confirmation.impl;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
/**
*
* @author jotty
*
*/
public class BookingBean implements Serializable, Comparable<BookingBean> {
private static final long serialVersionUID = 2L;
private final Integer pkPerson;
private final LocalDate date;
private final String camp;
private String fullname;
private String role;
private LocalDateTime registered;
public BookingBean(Integer pkPerson, LocalDate date, String camp) {
this.pkPerson = pkPerson;
this.date = date;
this.camp = camp;
}
@Override
public int compareTo(BookingBean bean) {
return date == null || bean == null || bean.getDate() == null ? 0 : bean.getDate().compareTo(date);
}
/**
* get the German role name
*
* @return the German role name
*/
public String getRolename() {
if (EnumCamprole.student.getLiteral().equals(role)) {
return "Teilnehmer";
} else if (EnumCamprole.teacher.getLiteral().equals(role)) {
return "Mitarbeiter";
} else if (EnumCamprole.director.getLiteral().equals(role)) {
return "Leiter";
} else if (EnumCamprole.feeder.getLiteral().equals(role)) {
return "Küchenteam";
} else {
return role;
}
}
/**
* @return the fullname
*/
public String getFullname() {
return fullname;
}
/**
* @param fullname the fullname to set
*/
public void setFullname(String fullname) {
this.fullname = fullname;
}
/**
* @return the role
*/
public String getRole() {
return role;
}
/**
* @param role the role to set
*/
public void setRole(String role) {
this.role = role;
}
/**
* @return the registered
*/
public LocalDateTime getRegistered() {
return registered;
}
/**
* @param registered the registered to set
*/
public void setRegistered(LocalDateTime registered) {
this.registered = registered;
}
/**
* @return the date
*/
public LocalDate getDate() {
return date;
}
/**
* @return the camp
*/
public String getCamp() {
return camp;
}
public Integer getPkPerson() {
return pkPerson;
}
}

View File

@ -0,0 +1,88 @@
package de.jottyfan.camporganizer.module.confirmation.confirmation.impl;
import java.io.Serializable;
import java.time.LocalDate;
/**
*
* @author jotty
*
*/
public class CampOverviewBean implements Serializable, Comparable<CampOverviewBean> {
private static final long serialVersionUID = 1L;
private final LocalDate date;
private final String camp;
private Integer approved;
private Integer rejected;
private Integer untouched;
public CampOverviewBean(LocalDate date, String camp) {
this.date = date;
this.camp = camp;
this.approved = 0;
this.rejected = 0;
this.untouched = 0;
}
@Override
public int compareTo(CampOverviewBean bean) {
return date == null || bean == null || bean.getDate() == null ? 0 : bean.getDate().compareTo(date);
}
/**
* @return the camp
*/
public String getCamp() {
return camp;
}
/**
* @return the approved
*/
public Integer getApproved() {
return approved;
}
/**
* @param approved the approved to set
*/
public void setApproved(Integer approved) {
this.approved = approved;
}
/**
* @return the rejected
*/
public Integer getRejected() {
return rejected;
}
/**
* @param rejected the rejected to set
*/
public void setRejected(Integer rejected) {
this.rejected = rejected;
}
/**
* @return the untouched
*/
public Integer getUntouched() {
return untouched;
}
/**
* @param untouched the untouched to set
*/
public void setUntouched(Integer untouched) {
this.untouched = untouched;
}
/**
* @return the date
*/
public LocalDate getDate() {
return date;
}
}

View File

@ -0,0 +1,162 @@
package de.jottyfan.camporganizer.module.confirmation.confirmation.impl;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMPPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.Name;
import org.jooq.Record4;
import org.jooq.Record7;
import org.jooq.SelectHavingStep;
import org.jooq.SelectSeekStep1;
import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumModule;
/**
*
* @author jotty
*
*/
@Repository
@Transactional(transactionManager = "transactionManager")
public class ConfirmationGateway {
private static final Logger LOGGER = LogManager.getLogger(ConfirmationGateway.class);
@Autowired
private DSLContext jooq;
/**
* get an overview of camps
*
* @param currentUser the current user to check for its privileges
* @return a list of camp overview beans
*/
public List<CampOverviewBean> getCampOverviewBeans(String currentUser) {
Name COUNT = DSL.name("count");
SelectHavingStep<Record4<Integer, Boolean, String, LocalDateTime>> sql = jooq
// @formatter:off
.select(DSL.count(T_PERSON.PK).as(COUNT),
T_PERSON.ACCEPT,
T_CAMP.NAME,
T_CAMP.ARRIVE)
.from(T_PERSON)
.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
.where(T_CAMP.ARRIVE.isNotNull())
.and(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
.and(T_PROFILE.USERNAME.eq(currentUser))
.groupBy(T_CAMP.NAME, T_CAMP.ARRIVE, T_PERSON.ACCEPT);
// @formatter:on
LOGGER.debug(sql.toString());
Map<LocalDateTime, CampOverviewBean> map = new HashMap<>();
for (Record4<Integer, Boolean, String, LocalDateTime> r : sql.fetch()) {
Integer count = r.get(COUNT, Integer.class);
Boolean accept = r.get(T_PERSON.ACCEPT);
String campname = r.get(T_CAMP.NAME);
LocalDateTime arrive = r.get(T_CAMP.ARRIVE);
CampOverviewBean bean = map.get(arrive);
if (bean == null) {
bean = new CampOverviewBean(arrive.toLocalDate(), campname);
map.put(arrive, bean);
}
if (accept == null) {
bean.setUntouched(count);
} else if (accept) {
bean.setApproved(count);
} else {
bean.setRejected(count);
}
}
List<CampOverviewBean> list = new ArrayList<>(map.values());
Collections.sort(list);
return list;
}
/**
* get all bookings with condition that this user can manage
*
* @param currentUser the current user
* @param condition the condition e.g. T_PERSON.ACCEPT.isNull()
* @return a list of booking beans; an empty one at least
*/
private List<BookingBean> getListWithCondition(String currentUser, Condition condition) {
SelectSeekStep1<Record7<Integer, String, String, String, LocalDateTime, EnumCamprole, LocalDateTime>, LocalDateTime> sql = jooq
// @formatter:off
.select(T_PERSON.PK, T_PERSON.FORENAME, T_PERSON.SURNAME, T_CAMP.NAME, T_CAMP.ARRIVE, T_PERSON.CAMPROLE, T_PERSON.CREATED)
.from(T_PERSON)
.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
.where(condition)
.and(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
.and(T_PROFILE.USERNAME.eq(currentUser))
.orderBy(T_PERSON.CREATED.desc());
// @formatter:on
LOGGER.debug(sql.toString());
List<BookingBean> list = new ArrayList<>();
for (Record7<Integer, String, String, String, LocalDateTime, EnumCamprole, LocalDateTime> r : sql.fetch()) {
String forename = r.get(T_PERSON.FORENAME);
String surname = r.get(T_PERSON.SURNAME);
String campname = r.get(T_CAMP.NAME);
LocalDateTime arrive = r.get(T_CAMP.ARRIVE);
EnumCamprole role = r.get(T_PERSON.CAMPROLE);
LocalDateTime created = r.get(T_PERSON.CREATED);
Integer pkPerson = r.get(T_PERSON.PK);
BookingBean bean = new BookingBean(pkPerson, arrive.toLocalDate(), campname);
bean.setRole(role == null ? null : role.getLiteral());
bean.setRegistered(created);
bean.setFullname(new StringBuilder().append(forename).append(" ").append(surname).toString());
list.add(bean);
}
return list;
}
/**
* get all untouched bookings that this user can manage
*
* @param currentUser the current user
* @return a list of booking beans; an empty one at least
*/
public List<BookingBean> getUntouched(String currentUser) {
return getListWithCondition(currentUser, T_PERSON.ACCEPT.isNull());
}
/**
* get all approved bookings that this user can manage
*
* @param currentUser the current user
* @return a list of booking beans; an empty one at least
*/
public List<BookingBean> getApproved(String currentUser) {
return getListWithCondition(currentUser, T_PERSON.ACCEPT.isTrue());
}
/**
* get all rejected bookings that this user can manage
*
* @param currentUser the current user
* @return a list of booking beans; an empty one at least
*/
public List<BookingBean> getRejected(String currentUser) {
return getListWithCondition(currentUser, T_PERSON.ACCEPT.isFalse());
}
}

View File

@ -0,0 +1,53 @@
package de.jottyfan.camporganizer.module.confirmation.confirmation.impl;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.jooq.exception.DataAccessException;
import org.keycloak.KeycloakSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.module.confirmation.confirmation.IConfirmationService;
/**
*
* @author jotty
*
*/
@Service
public class ConfirmationService implements IConfirmationService {
@Autowired
private ConfirmationGateway gateway;
@Override
public String getCurrentUser(HttpServletRequest request) {
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request
.getAttribute(KeycloakSecurityContext.class.getName());
if (ksc == null) {
throw new DataAccessException("no keycloak user in session");
}
return ksc.getIdToken().getPreferredUsername();
}
@Override
public List<CampOverviewBean> getCampOverview(HttpServletRequest request) {
return gateway.getCampOverviewBeans(getCurrentUser(request));
}
@Override
public List<BookingBean> getUntouched(HttpServletRequest request) {
return gateway.getUntouched(getCurrentUser(request));
}
@Override
public List<BookingBean> getApproved(HttpServletRequest request) {
return gateway.getApproved(getCurrentUser(request));
}
@Override
public List<BookingBean> getRejected(HttpServletRequest request) {
return gateway.getRejected(getCurrentUser(request));
}
}

View File

@ -0,0 +1,47 @@
package de.jottyfan.camporganizer.module.confirmation.person;
import java.util.List;
import de.jottyfan.camporganizer.module.confirmation.person.impl.CampBean;
import de.jottyfan.camporganizer.module.confirmation.person.impl.PersonBean;
/**
*
* @author jotty
*
*/
public interface IPersonService {
/**
* get person bean from db if username can read it
*
* @param username the user to read from the database
* @param pk the ID of the person to be read
* @return the person or null
*/
public PersonBean getPerson(String username, Integer pk);
/**
* update bean in the database
*
* @param bean the bean
* @return number of affected database rows
*/
public Integer updatePerson(PersonBean bean);
/**
* get all camps from the database that the user has access to
*
* @param username the user
* @return a list of camp beans; an empty one at least
*/
public List<CampBean> getCamps(String username);
/**
* get several annotations as a String
*
* @param pk the ID of the person
* @return the annotations; may be an empty string
*/
public String getAnnotations(Integer pk);
}

View File

@ -0,0 +1,48 @@
package de.jottyfan.camporganizer.module.confirmation.person;
import javax.servlet.http.HttpServletRequest;
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.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import de.jottyfan.camporganizer.module.confirmation.confirmation.IConfirmationService;
import de.jottyfan.camporganizer.module.confirmation.person.impl.PersonBean;
/**
*
* @author jotty
*
*/
@Controller
public class PersonController {
@Autowired
private HttpServletRequest request;
@Autowired
private IConfirmationService confirmationService;
@Autowired
private IPersonService personService;
@GetMapping("/confirmation/person/{pk}")
public String getIndex(Model model, @PathVariable Integer pk) {
String username = confirmationService.getCurrentUser(request);
model.addAttribute("currentUser", username);
model.addAttribute("person", personService.getPerson(username, pk));
model.addAttribute("camps", personService.getCamps(username));
model.addAttribute("annotations", personService.getAnnotations(pk));
return "confirmation/person";
}
@PostMapping("/confirmation/person/update")
public String doUpdate(@ModelAttribute PersonBean bean, Model model) {
personService.updatePerson(bean);
return "redirect:/confirmation";
}
}

View File

@ -0,0 +1,75 @@
package de.jottyfan.camporganizer.module.confirmation.person.impl;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
*
* @author jotty
*
*/
public class CampBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pk;
private String name;
private LocalDateTime arrive;
private String location;
/**
* @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 arrive
*/
public LocalDateTime getArrive() {
return arrive;
}
/**
* @param arrive the arrive to set
*/
public void setArrive(LocalDateTime arrive) {
this.arrive = arrive;
}
/**
* @return the location
*/
public String getLocation() {
return location;
}
/**
* @param location the location to set
*/
public void setLocation(String location) {
this.location = location;
}
}

View File

@ -0,0 +1,276 @@
package de.jottyfan.camporganizer.module.confirmation.person.impl;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumSex;
/**
*
* @author jotty
*
*/
public class PersonBean implements Serializable {
private static final long serialVersionUID = 2L;
private Integer pk;
private String forename;
private String surname;
private String street;
private String zip;
private String city;
private String phone;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthdate;
private EnumCamprole camprole;
private String email;
private Integer fkCamp;
private Integer fkProfile;
private Boolean accept;
private LocalDateTime created;
private EnumSex sex;
private Integer fkRegistrator;
private String comment;
/**
* @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;
}
/**
* @return the street
*/
public String getStreet() {
return street;
}
/**
* @param street the street to set
*/
public void setStreet(String street) {
this.street = street;
}
/**
* @return the zip
*/
public String getZip() {
return zip;
}
/**
* @param zip the zip to set
*/
public void setZip(String zip) {
this.zip = zip;
}
/**
* @return the city
*/
public String getCity() {
return city;
}
/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}
/**
* @return the phone
*/
public String getPhone() {
return phone;
}
/**
* @param phone the phone to set
*/
public void setPhone(String phone) {
this.phone = phone;
}
/**
* @return the birthdate
*/
public LocalDate getBirthdate() {
return birthdate;
}
/**
* @param birthdate the birthdate to set
*/
public void setBirthdate(LocalDate birthdate) {
this.birthdate = birthdate;
}
/**
* @return the camprole
*/
public EnumCamprole getCamprole() {
return camprole;
}
/**
* @param camprole the camprole to set
*/
public void setCamprole(EnumCamprole camprole) {
this.camprole = camprole;
}
/**
* @return the email
*/
public String getEmail() {
return email;
}
/**
* @param email the email to set
*/
public void setEmail(String email) {
this.email = email;
}
/**
* @return the fkCamp
*/
public Integer getFkCamp() {
return fkCamp;
}
/**
* @param fkCamp the fkCamp to set
*/
public void setFkCamp(Integer fkCamp) {
this.fkCamp = fkCamp;
}
/**
* @return the fkProfile
*/
public Integer getFkProfile() {
return fkProfile;
}
/**
* @param fkProfile the fkProfile to set
*/
public void setFkProfile(Integer fkProfile) {
this.fkProfile = fkProfile;
}
/**
* @return the accept
*/
public Boolean getAccept() {
return accept;
}
/**
* @param accept the accept to set
*/
public void setAccept(Boolean accept) {
this.accept = accept;
}
/**
* @return the created
*/
public LocalDateTime getCreated() {
return created;
}
/**
* @param created the created to set
*/
public void setCreated(LocalDateTime created) {
this.created = created;
}
/**
* @return the sex
*/
public EnumSex getSex() {
return sex;
}
/**
* @param sex the sex to set
*/
public void setSex(EnumSex sex) {
this.sex = sex;
}
/**
* @return the fkRegistrator
*/
public Integer getFkRegistrator() {
return fkRegistrator;
}
/**
* @param fkRegistrator the fkRegistrator to set
*/
public void setFkRegistrator(Integer fkRegistrator) {
this.fkRegistrator = fkRegistrator;
}
/**
* @return the comment
*/
public String getComment() {
return comment;
}
/**
* @param comment the comment to set
*/
public void setComment(String comment) {
this.comment = comment;
}
/**
* @return the pk
*/
public Integer getPk() {
return pk;
}
/**
* @param pk the pk to set
*/
public void setPk(Integer pk) {
this.pk = pk;
}
}

View File

@ -0,0 +1,205 @@
package de.jottyfan.camporganizer.module.confirmation.person.impl;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMPPROFILE;
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.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Record11;
import org.jooq.Record4;
import org.jooq.SelectConditionStep;
import org.jooq.SelectSeekStep1;
import org.jooq.UpdateConditionStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumModule;
import de.jottyfan.camporganizer.db.jooq.tables.TProfile;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
/**
*
* @author jotty
*
*/
@Repository
@Transactional(transactionManager = "transactionManager")
public class PersonGateway {
private static final Logger LOGGER = LogManager.getLogger(PersonGateway.class);
@Autowired
private DSLContext jooq;
/**
* get all camps from the database if username is allowed to maintain it
*
* @param username the username of the requesting person
* @return all camps
*/
public List<CampBean> getCamps(String username) {
SelectSeekStep1<Record4<Integer, String, LocalDateTime, String>, LocalDateTime> sql = jooq
// @formatter:off
.select(T_CAMP.PK, T_CAMP.NAME, T_CAMP.ARRIVE, T_LOCATION.NAME)
.from(T_CAMP)
.leftJoin(T_LOCATION).on(T_LOCATION.PK.eq(T_CAMP.FK_LOCATION))
.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_CAMP.PK))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
.where(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
.and(T_PROFILE.USERNAME.eq(username))
.orderBy(T_CAMP.ARRIVE.desc());
// @formatter:on
LOGGER.debug(sql.toString());
List<CampBean> list = new ArrayList<>();
for (Record r : sql.fetch()) {
CampBean bean = new CampBean();
bean.setPk(r.get(T_CAMP.PK));
bean.setName(r.get(T_CAMP.NAME));
bean.setArrive(r.get(T_CAMP.ARRIVE));
bean.setLocation(r.get(T_LOCATION.NAME));
list.add(bean);
}
return list;
}
/**
* get the person referenced by pk if username is allowed to read it
*
* @param username the username of the requesting person
* @param pk the ID of the person
* @return the person bean if found; null otherwise
*/
public PersonBean getPerson(String username, Integer pk) {
SelectConditionStep<Record> sql = jooq
// @formatter:off
.select(T_PERSON.fields())
.from(T_PERSON)
.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
.leftJoin(T_CAMPPROFILE).on(T_CAMPPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
.where(T_PERSON.PK.eq(pk))
.and(T_CAMPPROFILE.MODULE.eq(EnumModule.registration))
.and(T_PROFILE.USERNAME.eq(username));
// @formatter:on
LOGGER.debug(sql.toString());
for (Record r : sql.fetch()) {
PersonBean bean = new PersonBean();
bean.setPk(pk);
bean.setAccept(r.get(T_PERSON.ACCEPT));
bean.setBirthdate(r.get(T_PERSON.BIRTHDATE));
bean.setCamprole(r.get(T_PERSON.CAMPROLE));
bean.setCity(r.get(T_PERSON.CITY));
bean.setComment(r.get(T_PERSON.COMMENT));
bean.setCreated(r.get(T_PERSON.CREATED));
bean.setEmail(r.get(T_PERSON.EMAIL));
bean.setFkCamp(r.get(T_PERSON.FK_CAMP));
bean.setFkProfile(r.get(T_PERSON.FK_PROFILE));
bean.setFkRegistrator(r.get(T_PERSON.FK_REGISTRATOR));
bean.setForename(r.get(T_PERSON.FORENAME));
bean.setSurname(r.get(T_PERSON.SURNAME));
bean.setPhone(r.get(T_PERSON.PHONE));
bean.setSex(r.get(T_PERSON.SEX));
bean.setStreet(r.get(T_PERSON.STREET));
bean.setZip(r.get(T_PERSON.ZIP));
return bean;
}
return null;
}
/**
* update that bean in the database
*
* @param bean the bean
* @return the number of affected database rows
*/
public Integer updatePerson(PersonBean bean) {
UpdateConditionStep<TPersonRecord> sql = jooq
// @formatter:off
.update(T_PERSON)
.set(T_PERSON.FORENAME, bean.getForename())
.set(T_PERSON.SURNAME, bean.getSurname())
.set(T_PERSON.STREET, bean.getStreet())
.set(T_PERSON.ZIP, bean.getZip())
.set(T_PERSON.CITY, bean.getCity())
.set(T_PERSON.BIRTHDATE, bean.getBirthdate())
.set(T_PERSON.SEX, bean.getSex())
.set(T_PERSON.PHONE, bean.getPhone())
.set(T_PERSON.EMAIL, bean.getEmail())
.set(T_PERSON.COMMENT, bean.getComment())
.set(T_PERSON.ACCEPT, bean.getAccept())
.where(T_PERSON.PK.eq(bean.getPk()));
// @formatter:on
LOGGER.debug(sql.toString());
return sql.execute();
}
/**
* get annotations of person
*
* @param pk the ID of the person
* @return annotations if found; an empty string if not
*/
public String getAnnotations(Integer pk) {
TProfile REGISTRATOR = T_PROFILE.as("registrator");
SelectConditionStep<Record11<LocalDate, LocalDateTime, EnumCamprole, LocalDateTime, LocalDateTime, Integer, Integer, String, String, String, String>> sql = jooq
// @formatter:off
.select(T_PERSON.BIRTHDATE, T_PERSON.CREATED, T_PERSON.CAMPROLE,
T_CAMP.ARRIVE, T_CAMP.DEPART,
T_CAMP.MIN_AGE, T_CAMP.MAX_AGE,
T_PROFILE.FORENAME, T_PROFILE.SURNAME,
REGISTRATOR.FORENAME, REGISTRATOR.SURNAME)
.from(T_PERSON)
.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_PERSON.FK_PROFILE))
.leftJoin(REGISTRATOR).on(REGISTRATOR.PK.eq(T_PERSON.FK_REGISTRATOR))
.where(T_PERSON.PK.eq(pk));
// @formatter:on
LOGGER.debug(sql.toString());
StringBuilder buf = new StringBuilder();
for (Record r : sql.fetch()) {
LocalDate birthdate = r.get(T_PERSON.BIRTHDATE);
LocalDateTime arrive = r.get(T_CAMP.ARRIVE);
LocalDateTime depart = r.get(T_CAMP.DEPART);
Integer minAge = r.get(T_CAMP.MIN_AGE);
Integer maxAge = r.get(T_CAMP.MAX_AGE);
String bookerForename = r.get(T_PROFILE.FORENAME);
String bookerSurname = r.get(T_PROFILE.SURNAME);
String registratorForename = r.get(REGISTRATOR.FORENAME);
String registratorSurname = r.get(REGISTRATOR.SURNAME);
LocalDateTime created = r.get(T_PERSON.CREATED);
EnumCamprole role = r.get(T_PERSON.CAMPROLE);
String createdString = created.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
buf.append(String.format("angemeldet von %s %s am %s\n", bookerForename, bookerSurname, createdString));
if (registratorForename != null || registratorSurname != null) {
buf.append(String.format("bearbeitet von %s %s\n", registratorForename, registratorSurname));
}
if (birthdate == null) {
buf.append("Geburtstag wurde nicht angegeben\n");
} else if (arrive.isBefore(birthdate.atStartOfDay()) && depart.isAfter(birthdate.atStartOfDay())) {
buf.append("hat während der Freizeit Geburtstag\n");
}
if (birthdate == null || arrive == null) {
buf.append("Alter konnte wegen fehlendem Geburtstag nicht ermittelt werden\n");
} else {
long age = ChronoUnit.YEARS.between(birthdate, arrive);
if (EnumCamprole.student.equals(role) && (minAge > age || maxAge < age)) {
buf.append("passt vom Alter her nicht in die Freizeit");
}
}
}
return buf.toString();
}
}

View File

@ -0,0 +1,40 @@
package de.jottyfan.camporganizer.module.confirmation.person.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.module.confirmation.person.IPersonService;
/**
*
* @author jotty
*
*/
@Service
public class PersonService implements IPersonService {
@Autowired
private PersonGateway gateway;
@Override
public PersonBean getPerson(String username, Integer pk) {
return gateway.getPerson(username, pk);
}
@Override
public Integer updatePerson(PersonBean bean) {
return gateway.updatePerson(bean);
}
@Override
public List<CampBean> getCamps(String username) {
return gateway.getCamps(username);
}
@Override
public String getAnnotations(Integer pk) {
return gateway.getAnnotations(pk);
}
}

View File

@ -87,6 +87,49 @@ body {
color: darkcyan;
}
.badgetodo {
border-radius: 8px;
border: 1px solid black;
color: white;
font-weight: bolder;
background-image: linear-gradient(to right bottom, rgb(153, 193, 241), rgb(26, 95, 180));
padding: 2px 4px 2px 4px;
margin: 0px 2px 0px 2px;
}
.badgewarn {
border-radius: 8px;
border: 1px solid black;
color: white;
font-weight: bolder;
background-image: linear-gradient(to right bottom, #fa0, #a70);
padding: 2px 4px 2px 4px;
margin: 0px 2px 0px 2px;
}
.badgeinfo {
border-radius: 8px;
border: 1px solid black;
color: white;
font-weight: bolder;
background-image: linear-gradient(to right bottom, rgb(143, 240, 164), rgb(38, 162, 105));
padding: 2px 4px 2px 4px;
margin: 0px 2px 0px 2px;
}
.dist8 {
margin: 8px;
}
.error {
color: red;
}
.locked {
background-color: rgba(255, 255, 255, 0.2) !important;
cursor: not-allowed;
}
.mytoggle_collapsed {
display: none;
}

View File

@ -0,0 +1,169 @@
<!DOCTYPE html>
<html th:replace="~{template :: layout(~{::title}, ~{::libs}, ~{::header}, ~{::content})}" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<title>Camp Organizer Confirmation</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<libs></libs>
</head>
<body>
<header>
<a th:href="@{/}" class="btn btn-secondary btn-icon-silent" title="zur Hauptseite"><i class="fas fa-home"></i></a>
<a th:href="@{/confirmation}" class="btn btn-secondary btn-icon-silent" title="aktualisieren"><i class="fas fa-sync"></i></a>
</header>
<content>
<div class="mainpage">
<div class="accordion" id="mainacc" sec:authorize="hasRole('registrator')">
<div class="accordion-item">
<h2 class="accordion-header" id="untouchedpanel">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#untoucheddiv" aria-expanded="true" aria-control="untoucheddiv">unbearbeitete Anmeldungen
</button>
</h2>
<div id="untoucheddiv" class="accordion-collapse collapse show dist8" aria-labelled="untouchedpanel" data-bs-parent="#mainacc">
<table id="untouched">
<thead>
<tr>
<th>Freizeit</th>
<th>Name</th>
<th>Rolle</th>
<th>Anmeldedatum</th>
</tr>
</thead>
<tbody>
<tr th:each="u : ${untouched}">
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.camp} + ' ' + ${#temporals.format(u.date, 'yyyy')}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.fullname}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.rolename}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${#temporals.format(u.registered, 'dd.MM.yyyy')}"></a></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(document).ready(function() {
$("#untouched").DataTable({
language : locale_de,
pageLength : 5,
lengthMenu : [ [ 5, 25, 50, -1 ], [ 5, 25, 50, "Alle" ] ]
});
});
</script>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="approvedpanel">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#approveddiv" aria-expanded="true" aria-control="approveddiv">kürzlich bestätigte
Anmeldungen</button>
</h2>
<div id="approveddiv" class="accordion-collapse collapse dist8" aria-labelled="approvedpanel" data-bs-parent="#mainacc">
<table id="approved">
<thead>
<tr>
<th>Freizeit</th>
<th>Name</th>
<th>Rolle</th>
<th>Anmeldedatum</th>
</tr>
</thead>
<tbody>
<tr th:each="u : ${approved}">
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.camp} + ' ' + ${#temporals.format(u.date, 'yyyy')}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.fullname}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.rolename}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${#temporals.format(u.registered, 'dd.MM.yyyy')}"></a></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(document).ready(function() {
$("#approved").DataTable({
language : locale_de,
pageLength : 5,
lengthMenu : [ [ 5, 25, 50, -1 ], [ 5, 25, 50, "Alle" ] ]
});
});
</script>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="rejectedpanel">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#rejecteddiv" aria-expanded="true" aria-control="rejecteddiv">kürzlich abgelehnte
Anmeldungen</button>
</h2>
<div id="rejecteddiv" class="accordion-collapse collapse dist8" aria-labelled="rejectedpanel" data-bs-parent="#mainacc">
<table id="rejected">
<thead>
<tr>
<th>Freizeit</th>
<th>Name</th>
<th>Rolle</th>
<th>Anmeldedatum</th>
</tr>
</thead>
<tbody>
<tr th:each="u : ${rejected}">
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.camp} + ' ' + ${#temporals.format(u.date, 'yyyy')}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.fullname}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${u.rolename}"></a></td>
<td><a th:href="@{'/confirmation/person/' + ${u.pkPerson}}" th:text="${#temporals.format(u.registered, 'dd.MM.yyyy')}"></a></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(document).ready(function() {
$("#rejected").DataTable({
language : locale_de,
pageLength : 5,
lengthMenu : [ [ 5, 25, 50, -1 ], [ 5, 25, 50, "Alle" ] ]
});
});
</script>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="overviewpanel">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#overviewdiv" aria-expanded="true" aria-control="overviewdiv">Freizeitenübersicht</button>
</h2>
<div id="overviewdiv" class="accordion-collapse collapse dist8" aria-labelled="overviewpanel" data-bs-parent="#mainacc">
<table id="campoverview">
<thead>
<tr>
<th>Freizeit</th>
<th style="min-width: 64px">u/a/b</th>
</tr>
</thead>
<tbody>
<tr th:each="o : ${campoverview}">
<td th:text="${o.camp} + ' ' + ${#temporals.format(o.date, 'yyyy')}"></td>
<td><span th:text="${o.untouched}" class="badgetodo"></span> / <span th:text="${o.rejected}" class="badgewarn"></span> / <span th:text="${o.approved}" class="badgeinfo"></span></td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Zusammenfassung</td>
<td><span th:text="${campoverviewsummary.untouched}" class="badgetodo"></span> / <span th:text="${campoverviewsummary.rejected}" class="badgewarn"></span> / <span
th:text="${campoverviewsummary.approved}" class="badgeinfo"></span></td>
</tfoot>
</table>
<script type="text/javascript">
$(document).ready(function() {
$("#campoverview").DataTable({
language : locale_de,
pageLength : 5,
lengthMenu : [ [ 5, 25, 50, -1 ], [ 5, 25, 50, "Alle" ] ]
});
});
</script>
<span>Legende:</span><span class="badgetodo">unbearbeitet</span><span class="badgewarn">abgelehnt</span><span class="badgeinfo">bestätigt</span>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="searchpanel">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#searchdiv" aria-expanded="true" aria-control="searchdiv">Suchmaske</button>
</h2>
<div id="searchdiv" class="accordion-collapse collapse dist8" aria-labelled="searchpanel" data-bs-parent="#mainacc">TODO: add an ajax based search field for persons to directly edit
them</div>
</div>
</div>
</div>
</content>
</body>
</html>

View File

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html th:replace="~{template :: layout(~{::title}, ~{::libs}, ~{::header}, ~{::content})}" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<title>Camp Organizer Confirmation</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<libs></libs>
</head>
<body>
<header>
<a th:href="@{/}" class="btn btn-secondary btn-icon-silent" title="zur Hauptseite"><i class="fas fa-home"></i></a>
<a th:href="@{/confirmation}" class="btn btn-secondary btn-icon-silent" title="Anmeldungen"><i class="fas fa-user-check"></i></a>
<a th:href="@{/confirmation/person/{pk}(pk=${person.pk})}" class="btn btn-secondary btn-icon-silent" title="aktualisieren" th:if="${person != null}"><i class="fas fa-sync"></i></a>
</header>
<content>
<div class="mainpage">
<div class="container" style="max-width: 100%" sec:authorize="hasRole('registrator')">
<form action="#" th:action="@{/confirmation/person/update}" th:object="${person}" method="post" th:if="${person != null}">
<div class="row mb-2">
<label for="outputPk" class="col-sm-2 col-form-label">ID</label>
<div class="col-sm-10">
<input type="text" th:field="*{pk}" class="outputPk form-control locked"></span>
</div>
</div>
<div class="row mb-2">
<label for="inputForename" class="col-sm-2 col-form-label">Vorname</label>
<div class="col-sm-10">
<input type="text" th:field="*{forename}" class="inputForename form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputSurname" class="col-sm-2 col-form-label">Nachname</label>
<div class="col-sm-10">
<input type="text" th:field="*{surname}" class="inputSurname form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputStreet" class="col-sm-2 col-form-label">Straße</label>
<div class="col-sm-10">
<input type="text" th:field="*{street}" class="inputStreet form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputZip" class="col-sm-2 col-form-label">PLZ</label>
<div class="col-sm-10">
<input type="text" th:field="*{zip}" class="inputZip form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputCity" class="col-sm-2 col-form-label">Ort</label>
<div class="col-sm-10">
<input type="text" th:field="*{city}" class="inputCity form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputBirthdate" class="col-sm-2 col-form-label">Geburtstag (TODO)</label>
<div class="col-sm-10">
<input type="date" th:field="*{birthdate}" class="inputBirthdate form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputSex" class="col-sm-2 col-form-label">Geschlecht</label>
<div class="col-sm-10">
<select class="form-select" th:field="*{sex}">
<option value="male">männlich</option>
<option value="female">weiblich</option>
</select>
</div>
</div>
<div class="row mb-2">
<label for="inputPhone" class="col-sm-2 col-form-label">Telefon</label>
<div class="col-sm-10">
<input type="text" th:field="*{phone}" class="inputPhone form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputEmail" class="col-sm-2 col-form-label">E-Mail</label>
<div class="col-sm-10">
<input type="email" th:field="*{email}" class="inputEmail form-control" />
</div>
</div>
<div class="row mb-2">
<label for="outputCamp" class="col-sm-2 col-form-label">Freizeit</label>
<div class="col-sm-10">
<select class="form-select locked" th:field="*{fkCamp}" disabled="disabled">
<option th:each="c : ${camps}" th:value="${c.pk}" th:text="${c.name} + ' ' + ${#temporals.format(c.arrive, 'yyyy')} + ' in ' + ${c.location}"></option>
</select>
</div>
</div>
<div class="row mb-2">
<label for="outputCamprole" class="col-sm-2 col-form-label">Rolle</label>
<div class="col-sm-10">
<select class="outputCamprole form-select locked" th:field="*{camprole}" disabled="disabled">
<option value="student">Teilnehmer</option>
<option value="teacher">Mitarbeiter</option>
<option value="director">Leiter</option>
<option value="feeder">Küchenteam</option>
</select>
</div>
</div>
<div class="row mb-2">
<label for="inputComment" class="col-sm-2 col-form-label">Kommentar</label>
<div class="col-sm-10">
<textarea th:field="*{comment}" class="inputComment form-control"></textarea>
</div>
</div>
<div class="row mb-2">
<label for="outputAnno" class="col-sm-2 col-form-label">Anmerkungen</label>
<div class="col-sm-10">
<pre class="form-control locked" th:text="${annotations}"></pre>
</div>
</div>
<div class="row mb-2">
<label for="inputAccept" class="col-sm-2 col-form-label">Status</label>
<div class="col-sm-10">
<select class="form-select" th:field="*{accept}">
<option th:value="null">offen</option>
<option th:value="true">bestätigt</option>
<option th:value="false">abgelehnt</option>
</select>
</div>
</div>
<div class="row mb-2">
<label for="inputAccept" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Ok</button>
<a th:href="@{/confirmation/}" class="btn btn-secondary">Abbrechen</a>
</div>
</div>
</form>
<div th:if="${person == null}" class="error">In der Datenbank wurde keine Person mit entsprechender ID gefunden.</div>
</div>
</div>
</content>
</body>
</html>

View File

@ -8,7 +8,8 @@
<body>
<header>
<a th:href="@{/}" class="btn btn-secondary btn-icon-silent" title="aktualisieren"><i class="fas fa-sync"></i></a>
<a th:href="@{/business}" class="btn btn-secondary btn-icon-silent" title="Abrechnungen"><i class="far fa-money-bill-alt"></i></a>
<a th:href="@{/business}" class="btn btn-secondary btn-icon-silent" title="Abrechnungen" sec:authorize="hasRole('business')"><i class="far fa-money-bill-alt"></i></a>
<a th:href="@{/confirmation}" class="btn btn-secondary btn-icon-silent" title="Anmeldungen" sec:authorize="hasRole('registrator')"><i class="fas fa-user-check"></i></a>
<a href="https://www.onkelwernerfreizeiten.de/nextcloud" class="btn btn-secondary btn-icon-silent" title="Nextcloud" target="_blank"><img th:src="@{images/nextcloud.png}" width="20px"/></a>
</header>
<content>