integrated the business module

This commit is contained in:
Jottyfan 2022-10-08 15:36:13 +02:00
parent 8e8fc13efa
commit 6b6b37f3ea
42 changed files with 2468 additions and 91 deletions

View File

@ -12,6 +12,13 @@
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer">

View File

@ -1,13 +1,2 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=false
show.console.view=false
show.executions.view=false

View File

@ -1,8 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="camporganizer2">
<property name="context-root" value="camporganizer2"/>
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="CampOrganizer2">
<property name="context-root" value="CampOrganizer2"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/resources"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
</wb-module>
</project-modules>

View File

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

View File

@ -0,0 +1,62 @@
package de.jottyfan.camporganizer.module.business.bookings;
import javax.annotation.security.RolesAllowed;
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.business.bookings.impl.BookerBean;
import de.jottyfan.camporganizer.module.business.business.IBusinessService;
import de.jottyfan.camporganizer.module.business.bookings.impl.AddPaymentBean;
/**
*
* @author jotty
*
*/
@Controller
public class BookingsController {
@Autowired
private HttpServletRequest request;
@Autowired
private IBusinessService indexService;
@Autowired
private IBookingsService bookingsService;
@GetMapping("/business/bookings")
@RolesAllowed({"business_booking"})
public String getBookings(Model model) {
String username = indexService.getCurrentUser(request);
model.addAttribute("currentUser", username);
model.addAttribute("bookers", bookingsService.getBookers(username));
return "business/bookings";
}
@GetMapping("/business/bookings/{id}")
@RolesAllowed({"business_booking"})
public String getBooking(Model model, @PathVariable Integer id) {
String username = indexService.getCurrentUser(request);
model.addAttribute("currentUser", username);
BookerBean bean = bookingsService.getBooker(id, username);
model.addAttribute("booker", bean);
model.addAttribute("addBean", new AddPaymentBean());
return bean == null ? getBookings(model) : "business/booker";
}
@PostMapping("/business/bookings/payment/{id}")
@RolesAllowed({"business_booking"})
public String addBooking(Model model, @ModelAttribute AddPaymentBean bean, @PathVariable Integer id) {
Double payment = bean.getPayment();
bookingsService.addPayment(id, payment);
return getBooking(model, id);
}
}

View File

@ -0,0 +1,38 @@
package de.jottyfan.camporganizer.module.business.bookings;
import java.util.List;
import de.jottyfan.camporganizer.module.business.bookings.impl.BookerBean;
/**
*
* @author jotty
*
*/
public interface IBookingsService {
/**
* get the bookers information
*
* @param username the name of the user in this session
* @return the list of bookers; an empty one at least
*/
public List<BookerBean> getBookers(String username);
/**
* get the booker referenced by id
*
* @param id the ID of the booker
* @param username the name of the user in this session
* @return the booker if found; null otherwise
*/
public BookerBean getBooker(Integer id, String username);
/**
* add payment to the paid values of user with id
*
* @param id the ID of the booker
* @param payment the payment (additional value)
* @return number of affected database rows, should be 1
*/
public Integer addPayment(Integer id, Double payment);
}

View File

@ -0,0 +1,21 @@
package de.jottyfan.camporganizer.module.business.bookings.impl;
import java.io.Serializable;
/**
*
* @author jotty
*
*/
public class AddPaymentBean implements Serializable {
private static final long serialVersionUID = 1L;
private Double payment;
public Double getPayment() {
return payment;
}
public void setPayment(Double payment) {
this.payment = payment;
}
}

View File

@ -0,0 +1,120 @@
package de.jottyfan.camporganizer.module.business.bookings.impl;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
*
* @author jotty
*
*/
public class BookerBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pk;
private String name;
private String role;
private String sex;
private Boolean accept;
private LocalDateTime bookingDate;
private BigDecimal paid;
private String camp;
private String price;
/**
* @return the accept
*/
public Boolean getAccept() {
return accept;
}
/**
* @param accept the accept to set
*/
public void setAccept(Boolean accept) {
this.accept = accept;
}
/**
* @return the paid
*/
public BigDecimal getPaid() {
return paid;
}
/**
* @param paid the paid to set
*/
public void setPaid(BigDecimal paid) {
this.paid = paid;
}
/**
* @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;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public LocalDateTime getBookingDate() {
return bookingDate;
}
public void setBookingDate(LocalDateTime bookingDate) {
this.bookingDate = bookingDate;
}
public String getCamp() {
return camp;
}
public void setCamp(String camp) {
this.camp = camp;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}

View File

@ -0,0 +1,146 @@
package de.jottyfan.camporganizer.module.business.bookings.impl;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_SALESPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.V_CAMP;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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.Record10;
import org.jooq.Record11;
import org.jooq.SelectConditionStep;
import org.jooq.SelectSeekStep4;
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.EnumSex;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
/**
*
* @author jotty
*
*/
@Repository
@Transactional(transactionManager = "transactionManager")
public class BookingsGateway {
private static final Logger LOGGER = LogManager.getLogger(BookingsGateway.class);
@Autowired
private DSLContext jooq;
/**
* get a list of all registered persons that have booked the camp
*
* @param username the name of the current user in this session
*
* @return a list of bookings; an empty one at least
*/
public List<BookerBean> getBookings(String username) {
SelectSeekStep4<Record10<Integer, Boolean, BigDecimal, String, String, EnumCamprole, EnumSex, LocalDateTime, String, Double>, EnumCamprole, EnumSex, String, String> sql = jooq
// @formatter:off
.select(T_PERSON.PK, T_PERSON.ACCEPT, T_PERSON.PAID, T_PERSON.FORENAME, T_PERSON.SURNAME, T_PERSON.CAMPROLE, T_PERSON.SEX, T_PERSON.CREATED, V_CAMP.NAME, V_CAMP.YEAR)
.from(T_PERSON)
.leftJoin(V_CAMP).on(V_CAMP.PK.eq(T_PERSON.FK_CAMP))
.leftJoin(T_SALESPROFILE).on(T_SALESPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_SALESPROFILE.FK_PROFILE))
.where(T_PROFILE.USERNAME.eq(username))
.orderBy(T_PERSON.CAMPROLE, T_PERSON.SEX, T_PERSON.SURNAME, T_PERSON.FORENAME);
// @formatter:on
LOGGER.debug(sql.toString());
List<BookerBean> list = new ArrayList<>();
for (Record r : sql.fetch()) {
String forename = r.get(T_PERSON.FORENAME);
String surname = r.get(T_PERSON.SURNAME);
EnumCamprole role = r.get(T_PERSON.CAMPROLE);
EnumSex sex = r.get(T_PERSON.SEX);
String campName = r.get(V_CAMP.NAME);
Double campYear = r.get(V_CAMP.YEAR);
BookerBean bean = new BookerBean();
bean.setPk(r.get(T_PERSON.PK));
bean.setName(String.format("%s %s", forename, surname));
bean.setRole(EnumCamprole.student.equals(role) ? "Teilnehmer" : "Mitarbeiter");
bean.setSex(EnumSex.female.equals(sex) ? "weiblich" : "männlich");
bean.setBookingDate(r.get(T_PERSON.CREATED));
bean.setAccept(r.get(T_PERSON.ACCEPT));
bean.setPaid(r.get(T_PERSON.PAID));
bean.setCamp(String.format("%s %4.0f", campName, campYear));
list.add(bean);
}
return list;
}
/**
* get the booking payment information of the user referenced by id
*
* @param id the id of the user
* @param username the name of the current's session user
* @return the booker bean or null
*/
public BookerBean getBooking(Integer id, String username) {
SelectConditionStep<Record11<Integer, Boolean, BigDecimal, String, String, EnumCamprole, EnumSex, LocalDateTime, String, Double, String>> sql = jooq
// @formatter:off
.select(T_PERSON.PK, T_PERSON.ACCEPT, T_PERSON.PAID, T_PERSON.FORENAME, T_PERSON.SURNAME, T_PERSON.CAMPROLE, T_PERSON.SEX, T_PERSON.CREATED, V_CAMP.NAME, V_CAMP.YEAR, V_CAMP.PRICE)
.from(T_PERSON)
.leftJoin(V_CAMP).on(V_CAMP.PK.eq(T_PERSON.FK_CAMP))
.leftJoin(T_SALESPROFILE).on(T_SALESPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_SALESPROFILE.FK_PROFILE))
.where(T_PROFILE.USERNAME.eq(username))
.and(T_PERSON.PK.eq(id));
// @formatter:on
LOGGER.debug(sql.toString());
Record r = sql.fetchOne();
if (r == null) {
return null;
} else {
String forename = r.get(T_PERSON.FORENAME);
String surname = r.get(T_PERSON.SURNAME);
EnumCamprole role = r.get(T_PERSON.CAMPROLE);
EnumSex sex = r.get(T_PERSON.SEX);
String campName = r.get(V_CAMP.NAME);
Double campYear = r.get(V_CAMP.YEAR);
BookerBean bean = new BookerBean();
bean.setPk(r.get(T_PERSON.PK));
bean.setName(String.format("%s %s", forename, surname));
bean.setRole(EnumCamprole.student.equals(role) ? "Teilnehmer" : "Mitarbeiter");
bean.setSex(EnumSex.female.equals(sex) ? "weiblich" : "männlich");
bean.setBookingDate(r.get(T_PERSON.CREATED));
bean.setAccept(r.get(T_PERSON.ACCEPT));
bean.setPaid(r.get(T_PERSON.PAID));
bean.setCamp(String.format("%s %4.0f", campName, campYear));
bean.setPrice(r.get(V_CAMP.PRICE));
return bean;
}
}
/**
* add payment for user referenced by pk
*
* @param pk the ID of the user
* @param payment the payment (additional)
* @return the number of affected database rows, should be 1
*/
public Integer addPayment(Integer pk, Double payment) {
BigDecimal oldValue = jooq.select(T_PERSON.PAID).from(T_PERSON).where(T_PERSON.PK.eq(pk)).fetchOne().get(T_PERSON.PAID);
BigDecimal value = oldValue == null ? BigDecimal.valueOf(payment) : oldValue.add(BigDecimal.valueOf(payment));
UpdateConditionStep<TPersonRecord> sql = jooq
// @formatter:off
.update(T_PERSON)
.set(T_PERSON.PAID, value)
.where(T_PERSON.PK.eq(pk));
// @formatter:on
LOGGER.debug(sql.toString());
return sql.execute();
}
}

View File

@ -0,0 +1,36 @@
package de.jottyfan.camporganizer.module.business.bookings.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.module.business.bookings.IBookingsService;
/**
*
* @author jotty
*
*/
@Service
public class BookingsService implements IBookingsService {
@Autowired
private BookingsGateway bookingsGateway;
@Override
public List<BookerBean> getBookers(String username) {
return bookingsGateway.getBookings(username);
}
@Override
public BookerBean getBooker(Integer id, String username) {
return bookingsGateway.getBooking(id, username);
}
@Override
public Integer addPayment(Integer id, Double payment) {
return bookingsGateway.addPayment(id, payment);
}
}

View File

@ -0,0 +1,31 @@
package de.jottyfan.camporganizer.module.business.business;
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;
/**
*
* @author henkej
*
*/
@Controller
public class BusinessController {
@Autowired
private HttpServletRequest request;
@Autowired
private IBusinessService indexService;
@GetMapping("/business")
public String getIndex(Model model) {
String username = indexService.getCurrentUser(request);
model.addAttribute("currentUser", username);
model.addAttribute("campBudgets", indexService.getCampBudgets(username));
return "business/business";
}
}

View File

@ -0,0 +1,30 @@
package de.jottyfan.camporganizer.module.business.business;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import de.jottyfan.camporganizer.module.business.business.impl.CampBudgetBean;
/**
*
* @author jotty
*
*/
public interface IBusinessService {
/**
* 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 all camp budgets of all years
*
* @param username the name of the current user in this session
* @return the list; an empty one at least
*/
public List<CampBudgetBean> getCampBudgets(String username);
}

View File

@ -0,0 +1,106 @@
package de.jottyfan.camporganizer.module.business.business.impl;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
*
* @author jotty
*
*/
public class BusinessBean implements Serializable {
private static final long serialVersionUID = 1L;
private final Integer fkPerson;
private final String forename;
private final String surname;
private final String campName;
private final String campPrice;
private final LocalDateTime campArrive;
private final String locationName;
private BigDecimal paid;
private boolean changed;
public BusinessBean(Integer fkPerson, String forename, String surname, String campName, String campPrice,
LocalDateTime campArrive, String locationName, BigDecimal paid) {
super();
this.fkPerson = fkPerson;
this.forename = forename;
this.surname = surname;
this.campName = campName;
this.campPrice = campPrice;
this.campArrive = campArrive;
this.locationName = locationName;
this.paid = paid;
this.changed = false;
}
/**
* @return the paid
*/
public BigDecimal getPaid() {
return paid;
}
/**
* @param paid the paid to set
*/
public void setPaid(BigDecimal paid) {
this.paid = paid;
this.changed = true;
}
/**
* @return the fkPerson
*/
public Integer getFkPerson() {
return fkPerson;
}
/**
* @return the forename
*/
public String getForename() {
return forename;
}
/**
* @return the surname
*/
public String getSurname() {
return surname;
}
/**
* @return the campName
*/
public String getCampName() {
return campName;
}
/**
* @return the campPrice
*/
public String getCampPrice() {
return campPrice;
}
/**
* @return the campArrive
*/
public LocalDateTime getCampArrive() {
return campArrive;
}
/**
* @return the locationName
*/
public String getLocationName() {
return locationName;
}
public boolean isChanged() {
return changed;
}
}

View File

@ -0,0 +1,138 @@
package de.jottyfan.camporganizer.module.business.business.impl;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
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 static de.jottyfan.camporganizer.db.jooq.Tables.T_SALESPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.V_CAMP_BUDGET;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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.Record4;
import org.jooq.Record8;
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.tables.records.TPersonRecord;
/**
*
* @author jotty
*
*/
@Repository
@Transactional(transactionManager = "transactionManager")
public class BusinessGateway {
private static final Logger LOGGER = LogManager.getLogger(BusinessGateway.class);
@Autowired
private DSLContext jooq;
/**
* get a list of all camp budgets of all years
*
* @param username the name of the user for privilege checks
*
* @return the list of all camp budgets
*/
public List<CampBudgetBean> getCampBudgets(String username) {
SelectSeekStep1<Record4<BigDecimal, String, Double, Integer>, LocalDateTime> sql = jooq
// @formatter:off
.select(V_CAMP_BUDGET.BUDGET,
V_CAMP_BUDGET.CAMP_NAME,
V_CAMP_BUDGET.YEAR,
V_CAMP_BUDGET.FK_CAMP)
.from(V_CAMP_BUDGET)
.leftJoin(T_CAMP).on(T_CAMP.PK.eq(V_CAMP_BUDGET.FK_CAMP))
.leftJoin(T_SALESPROFILE).on(T_SALESPROFILE.FK_CAMP.eq(V_CAMP_BUDGET.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_SALESPROFILE.FK_PROFILE))
.where(T_PROFILE.USERNAME.eq(username))
.orderBy(T_CAMP.ARRIVE);
// @formatter:on
LOGGER.debug(sql.toString());
List<CampBudgetBean> list = new ArrayList<>();
for (Record4<BigDecimal, String, Double, Integer> r : sql.fetch()) {
BigDecimal b = r.get(V_CAMP_BUDGET.BUDGET);
String n = r.get(V_CAMP_BUDGET.CAMP_NAME);
Double y = r.get(V_CAMP_BUDGET.YEAR);
Integer c = r.get(V_CAMP_BUDGET.FK_CAMP);
list.add(new CampBudgetBean().withBudget(b).withCampName(n).withCampYear(y).withCampId(c));
}
return list;
}
/**
* get all registrations for the search mask
*
* @param username the name of the user for privilege checks
*
* @return a list of business beans; an empty one at least
*/
public List<BusinessBean> getAllRegistrations(String username) {
SelectConditionStep<Record8<Integer, String, String, String, String, LocalDateTime, String, BigDecimal>> sql = jooq
// @formatter:off
.select(T_PERSON.PK,
T_PERSON.FORENAME,
T_PERSON.SURNAME,
T_CAMP.NAME,
T_CAMP.PRICE,
T_CAMP.ARRIVE,
T_LOCATION.NAME,
T_PERSON.PAID)
.from(T_PERSON)
.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
.leftJoin(T_LOCATION).on(T_LOCATION.PK.eq(T_CAMP.FK_LOCATION))
.leftJoin(T_SALESPROFILE).on(T_SALESPROFILE.FK_CAMP.eq(T_CAMP.PK))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_SALESPROFILE.FK_PROFILE))
.where(T_PROFILE.USERNAME.eq(username));
// @formatter:on
LOGGER.debug(sql.toString());
List<BusinessBean> list = new ArrayList<>();
for (Record8<Integer, String, String, String, String, LocalDateTime, String, BigDecimal> r : sql.fetch()) {
Integer fkPerson = r.get(T_PERSON.PK);
String forename = r.get(T_PERSON.FORENAME);
String surname = r.get(T_PERSON.SURNAME);
String campName = r.get(T_CAMP.NAME);
String campPrice = r.get(T_CAMP.PRICE);
LocalDateTime campArrive = r.get(T_CAMP.ARRIVE);
String locationName = r.get(T_CAMP.NAME);
BigDecimal paid = r.get(T_PERSON.PAID);
list.add(new BusinessBean(fkPerson, forename, surname, campName, campPrice, campArrive, locationName, paid));
}
return list;
}
/**
* update the database by changed records in the list
*
* @param list the list of beans
* @return the number of affected database rows
*/
public int updatePaid(List<BusinessBean> list) {
Integer count = 0;
for (BusinessBean bean : list) {
if (bean.isChanged()) {
UpdateConditionStep<TPersonRecord> sql = jooq
// @formatter:off
.update(T_PERSON)
.set(T_PERSON.PAID, bean.getPaid())
.where(T_PERSON.PK.eq(bean.getFkPerson()));
// @formatter:on
LOGGER.debug(sql.toString());
count += sql.execute();
}
}
return count;
}
}

View File

@ -0,0 +1,38 @@
package de.jottyfan.camporganizer.module.business.business.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.business.business.IBusinessService;
/**
*
* @author jotty
*
*/
@Service
public class BusinessService implements IBusinessService {
@Autowired
private BusinessGateway 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<CampBudgetBean> getCampBudgets(String username) {
return gateway.getCampBudgets(username);
}
}

View File

@ -0,0 +1,88 @@
package de.jottyfan.camporganizer.module.business.business.impl;
import java.io.Serializable;
import java.math.BigDecimal;
/**
*
* @author jotty
*
*/
public class CampBudgetBean implements Serializable {
private static final long serialVersionUID = 1L;
private BigDecimal budget;
private String campName;
private Double campYear;
private Integer campId;
public CampBudgetBean withCampId(Integer campId) {
setCampId(campId);
return this;
}
public CampBudgetBean withCampName(String campName) {
setCampName(campName);
return this;
}
public CampBudgetBean withCampYear(Double campYear) {
setCampYear(campYear);
return this;
}
public CampBudgetBean withBudget(BigDecimal budget) {
setBudget(budget);
return this;
}
/**
* @return the budget
*/
public BigDecimal getBudget() {
return budget;
}
/**
* @param budget the budget to set
*/
public void setBudget(BigDecimal budget) {
this.budget = budget;
}
/**
* @return the campName
*/
public String getCampName() {
return campName;
}
/**
* @param campName the campName to set
*/
public void setCampName(String campName) {
this.campName = campName;
}
/**
* @return the campYear
*/
public Double getCampYear() {
return campYear;
}
/**
* @param campYear the campYear to set
*/
public void setCampYear(Double campYear) {
this.campYear = campYear;
}
public Integer getCampId() {
return campId;
}
public void setCampId(Integer campId) {
this.campId = campId;
}
}

View File

@ -0,0 +1,42 @@
package de.jottyfan.camporganizer.module.business.camp;
import javax.annotation.security.RolesAllowed;
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.PathVariable;
import de.jottyfan.camporganizer.module.business.business.IBusinessService;
/**
*
* @author jotty
*
*/
@Controller
public class CampController {
@Autowired
private HttpServletRequest request;
@Autowired
private IBusinessService indexService;
@Autowired
private ICampService campService;
@GetMapping("/business/camp/{id}")
@RolesAllowed({"business"})
public String getCamp(Model model, @PathVariable Integer id) {
String username = indexService.getCurrentUser(request);
model.addAttribute("currentUser", username);
model.addAttribute("campId", id);
model.addAttribute("camp", campService.getCamp(id, username));
model.addAttribute("booking", campService.getBookings(id, username));
model.addAttribute("bookers", campService.getBookers(id, username));
return "business/camp";
}
}

View File

@ -0,0 +1,43 @@
package de.jottyfan.camporganizer.module.business.camp;
import java.util.List;
import de.jottyfan.camporganizer.module.business.camp.impl.BookingBean;
import de.jottyfan.camporganizer.module.business.camp.impl.CampBean;
import de.jottyfan.camporganizer.module.business.camp.impl.PersonBean;
/**
*
* @author jotty
*
*/
public interface ICampService {
/**
* get the camp bean of id
*
* @param id the id of the camp
* @param username the name of the user in this session
* @return the camp bean if found; null otherwise
*/
public CampBean getCamp(Integer id, String username);
/**
* get the booking information for camp with id
*
* @param id the id of the camp
* @param username the name of the user in this session
* @return a booking bean
*/
public BookingBean getBookings(Integer id, String username);
/**
* get the bookers information for camp with id
*
* @param id the id of the camp
* @param username the name of the user in this session
* @return the list of bookers; an empty one at least
*/
public List<PersonBean> getBookers(Integer id, String username);
}

View File

@ -0,0 +1,108 @@
package de.jottyfan.camporganizer.module.business.camp.impl;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumSex;
/**
*
* @author jotty
*
*/
public class BookerBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pk;
private String name;
private EnumCamprole campRole;
private EnumSex sex;
private LocalDateTime bookingDate;
private BigDecimal paid;
/**
* @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 campRole
*/
public EnumCamprole getCampRole() {
return campRole;
}
/**
* @param campRole the campRole to set
*/
public void setCampRole(EnumCamprole campRole) {
this.campRole = campRole;
}
/**
* @return the sex
*/
public EnumSex getSex() {
return sex;
}
/**
* @param sex the sex to set
*/
public void setSex(EnumSex sex) {
this.sex = sex;
}
/**
* @return the bookingDate
*/
public LocalDateTime getBookingDate() {
return bookingDate;
}
/**
* @param bookingDate the bookingDate to set
*/
public void setBookingDate(LocalDateTime bookingDate) {
this.bookingDate = bookingDate;
}
/**
* @return the paid
*/
public BigDecimal getPaid() {
return paid;
}
/**
* @param paid the paid to set
*/
public void setPaid(BigDecimal paid) {
this.paid = paid;
}
}

View File

@ -0,0 +1,75 @@
package de.jottyfan.camporganizer.module.business.camp.impl;
import java.io.Serializable;
import java.math.BigDecimal;
/**
*
* @author jotty
*
*/
public class BookingBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer approved;
private Integer rejected;
private Integer open;
private BigDecimal paid;
/**
* @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 open
*/
public Integer getOpen() {
return open;
}
/**
* @param open the open to set
*/
public void setOpen(Integer open) {
this.open = open;
}
/**
* @return the paid
*/
public BigDecimal getPaid() {
return paid;
}
/**
* @param paid the paid to set
*/
public void setPaid(BigDecimal paid) {
this.paid = paid;
}
}

View File

@ -0,0 +1,129 @@
package de.jottyfan.camporganizer.module.business.camp.impl;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import de.jottyfan.camporganizer.module.business.privileges.impl.ProfileBean;
/**
*
* @author jotty
*
*/
public class CampBean implements Serializable {
private static final long serialVersionUID = 2L;
private Integer pk;
private String name;
private Double year;
private LocalDateTime arrive;
private LocalDateTime depart;
private String locationName;
private String price;
private List<ProfileBean> profiles;
public CampBean() {
profiles = new ArrayList<>();
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the year
*/
public Double getYear() {
return year;
}
/**
* @param year the year to set
*/
public void setYear(Double year) {
this.year = year;
}
/**
* @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 locationName
*/
public String getLocationName() {
return locationName;
}
/**
* @param locationName the locationName to set
*/
public void setLocationName(String locationName) {
this.locationName = locationName;
}
/**
* @return the price
*/
public String getPrice() {
return price;
}
/**
* @param price the price to set
*/
public void setPrice(String price) {
this.price = price;
}
/**
* @return the profiles
*/
public List<ProfileBean> getProfiles() {
return profiles;
}
public Integer getPk() {
return pk;
}
public void setPk(Integer pk) {
this.pk = pk;
}
}

View File

@ -0,0 +1,110 @@
package de.jottyfan.camporganizer.module.business.camp.impl;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_SALESPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.V_CAMP;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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.Record6;
import org.jooq.Record8;
import org.jooq.SelectConditionStep;
import org.jooq.SelectSeekStep4;
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.EnumSex;
/**
*
* @author jotty
*
*/
@Repository
@Transactional(transactionManager = "transactionManager")
public class CampGateway {
private static final Logger LOGGER = LogManager.getLogger(CampGateway.class);
@Autowired
private DSLContext jooq;
/**
* get the camp referenced by pk
*
* @param pk the ID of the camp
* @param username the name of the current user in this session
*
* @return the camp if found or null
*/
public CampBean getCamp(Integer pk, String username) {
SelectConditionStep<Record6<String, Double, LocalDateTime, LocalDateTime, String, String>> sql = jooq
// @formatter:off
.select(V_CAMP.NAME, V_CAMP.YEAR, V_CAMP.ARRIVE, V_CAMP.DEPART, V_CAMP.LOCATION_NAME, V_CAMP.PRICE)
.from(V_CAMP)
.leftJoin(T_SALESPROFILE).on(T_SALESPROFILE.FK_CAMP.eq(V_CAMP.PK))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_SALESPROFILE.FK_PROFILE))
.where(V_CAMP.PK.eq(pk))
.and(T_PROFILE.USERNAME.eq(username));
// @formatter:on
LOGGER.debug(sql.toString());
CampBean bean = new CampBean();
for (Record6<String, Double, LocalDateTime, LocalDateTime, String, String> r : sql.fetch()) {
bean.setName(r.get(V_CAMP.NAME));
bean.setYear(r.get(V_CAMP.YEAR));
bean.setArrive(r.get(V_CAMP.ARRIVE));
bean.setDepart(r.get(V_CAMP.DEPART));
bean.setLocationName(r.get(V_CAMP.LOCATION_NAME));
bean.setPrice(r.get(V_CAMP.PRICE));
}
return bean;
}
/**
* get a list of all registered persons that have booked the camp
*
* @param pk the ID of the camp
* @param username the name of the current user in this session
*
* @return a list of bookings; an empty one at least
*/
public List<PersonBean> getBookings(Integer pk, String username) {
SelectSeekStep4<Record8<Integer, Boolean, BigDecimal, String, String, EnumCamprole, EnumSex, LocalDateTime>, EnumCamprole, EnumSex, String, String> sql = jooq
// @formatter:off
.select(T_PERSON.PK, T_PERSON.ACCEPT, T_PERSON.PAID, T_PERSON.FORENAME, T_PERSON.SURNAME, T_PERSON.CAMPROLE, T_PERSON.SEX, T_PERSON.CREATED)
.from(T_PERSON)
.leftJoin(T_SALESPROFILE).on(T_SALESPROFILE.FK_CAMP.eq(T_PERSON.FK_CAMP))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_SALESPROFILE.FK_PROFILE))
.where(T_PERSON.FK_CAMP.eq(pk))
.and(T_PROFILE.USERNAME.eq(username))
.orderBy(T_PERSON.CAMPROLE, T_PERSON.SEX, T_PERSON.SURNAME, T_PERSON.FORENAME);
// @formatter:on
LOGGER.debug(sql.toString());
List<PersonBean> list = new ArrayList<>();
for (Record r : sql.fetch()) {
String forename = r.get(T_PERSON.FORENAME);
String surname = r.get(T_PERSON.SURNAME);
EnumCamprole role = r.get(T_PERSON.CAMPROLE);
EnumSex sex = r.get(T_PERSON.SEX);
PersonBean bean = new PersonBean();
bean.setPk(r.get(T_PERSON.PK));
bean.setName(String.format("%s %s", forename, surname));
bean.setRole(EnumCamprole.student.equals(role) ? "Teilnehmer" : "Mitarbeiter");
bean.setSex(EnumSex.female.equals(sex) ? "weiblich" : "männlich");
bean.setBookingDate(r.get(T_PERSON.CREATED));
bean.setAccept(r.get(T_PERSON.ACCEPT));
bean.setPaid(r.get(T_PERSON.PAID));
list.add(bean);
}
return list;
}
}

View File

@ -0,0 +1,57 @@
package de.jottyfan.camporganizer.module.business.camp.impl;
import java.math.BigDecimal;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.module.business.camp.ICampService;
/**
*
* @author jotty
*
*/
@Service
public class CampService implements ICampService {
@Autowired
private CampGateway campGateway;
@Override
public CampBean getCamp(Integer id, String username) {
return campGateway.getCamp(id, username);
}
@Override
public BookingBean getBookings(Integer id, String username) {
Integer approved = 0;
Integer open = 0;
Integer rejected = 0;
BigDecimal paid = new BigDecimal(0);
for (PersonBean p : campGateway.getBookings(id, username)) {
Boolean acceptence = p.getAccept();
if (acceptence == null) {
open += 1;
} else if (acceptence) {
approved +=1;
} else {
rejected +=1;
}
paid = paid.add(p.getPaid() == null ? new BigDecimal(0) : p.getPaid());
}
BookingBean bean = new BookingBean();
bean.setApproved(approved);
bean.setOpen(open);
bean.setRejected(rejected);
bean.setPaid(paid);
return bean;
}
@Override
public List<PersonBean> getBookers(Integer id, String username) {
return campGateway.getBookings(id, username);
}
}

View File

@ -0,0 +1,102 @@
package de.jottyfan.camporganizer.module.business.camp.impl;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
*
* @author jotty
*
*/
public class PersonBean implements Serializable {
private static final long serialVersionUID = 2L;
private Integer pk;
private String name;
private String role;
private String sex;
private Boolean accept;
private LocalDateTime bookingDate;
private BigDecimal paid;
/**
* @return the accept
*/
public Boolean getAccept() {
return accept;
}
/**
* @param accept the accept to set
*/
public void setAccept(Boolean accept) {
this.accept = accept;
}
/**
* @return the paid
*/
public BigDecimal getPaid() {
return paid;
}
/**
* @param paid the paid to set
*/
public void setPaid(BigDecimal paid) {
this.paid = paid;
}
/**
* @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;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public LocalDateTime getBookingDate() {
return bookingDate;
}
public void setBookingDate(LocalDateTime bookingDate) {
this.bookingDate = bookingDate;
}
}

View File

@ -0,0 +1,45 @@
package de.jottyfan.camporganizer.module.business.privileges;
import java.util.List;
import java.util.Map;
import de.jottyfan.camporganizer.module.business.camp.impl.CampBean;
import de.jottyfan.camporganizer.module.business.privileges.impl.PrivilegesBean;
import de.jottyfan.camporganizer.module.business.privileges.impl.ProfileBean;
/**
*
* @author jotty
*
*/
public interface IPrivilegesService {
/**
* get all current privileges for the sales
*
* @return the privileges for sales
*/
public Map<Integer, CampBean> getPrivileges();
/**
* get all profiles
*
* @return the profiles
*/
public List<ProfileBean> getProfiles();
/**
* add a privilege
*
* @param bean the bean
*/
public void add(PrivilegesBean bean);
/**
* remove a privilege
*
* @param bean the bean
* @param currentUser the current user in this sessionö
*/
public void remove(PrivilegesBean bean, String currentUser);
}

View File

@ -0,0 +1,62 @@
package de.jottyfan.camporganizer.module.business.privileges;
import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;
import javax.websocket.server.PathParam;
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.PostMapping;
import de.jottyfan.camporganizer.module.business.business.IBusinessService;
import de.jottyfan.camporganizer.module.business.privileges.impl.PrivilegesBean;
/**
*
* @author henkej
*
*/
@Controller
public class PrivilegesController {
@Autowired
private HttpServletRequest request;
@Autowired
private IPrivilegesService privilegesService;
@Autowired
private IBusinessService indexService;
@GetMapping("/business/privileges")
@RolesAllowed({ "admin" })
public String getIndex(Model model) {
String username = indexService.getCurrentUser(request);
model.addAttribute("currentUser", username);
model.addAttribute("privileges", privilegesService.getPrivileges());
model.addAttribute("profiles", privilegesService.getProfiles());
model.addAttribute("bean", new PrivilegesBean());
return "business/privileges";
}
@PostMapping("/business/privileges/add")
@RolesAllowed({ "admin" })
public String getAdd(@ModelAttribute PrivilegesBean bean, Model model) {
privilegesService.add(bean);
return getIndex(model);
}
@GetMapping("/business/privileges/delete")
@RolesAllowed({ "admin" })
public String getDelete(@PathParam(value = "fkCamp") Integer fkCamp,
@PathParam(value = "fkProfile") Integer fkProfile, Model model) {
PrivilegesBean bean = new PrivilegesBean();
bean.setFkCamp(fkCamp);
bean.setFkProfile(fkProfile);
privilegesService.remove(bean, indexService.getCurrentUser(request));
return getIndex(model);
}
}

View File

@ -0,0 +1,134 @@
package de.jottyfan.camporganizer.module.business.privileges.impl;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
*
* @author jotty
*
*/
public class PrivilegesBean implements Serializable {
private static final long serialVersionUID = 1L;
private String forename;
private String surname;
private String username;
private LocalDateTime duedate;
private Integer fkProfile;
private Integer fkCamp;
private String campName;
private Double campYear;
/**
* @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 fkProfile
*/
public Integer getFkProfile() {
return fkProfile;
}
/**
* @param fkProfile the fkProfile to set
*/
public void setFkProfile(Integer fkProfile) {
this.fkProfile = fkProfile;
}
/**
* @return the fkCamp
*/
public Integer getFkCamp() {
return fkCamp;
}
/**
* @param fkCamp the fkCamp to set
*/
public void setFkCamp(Integer fkCamp) {
this.fkCamp = fkCamp;
}
/**
* @return the campName
*/
public String getCampName() {
return campName;
}
/**
* @param campName the campName to set
*/
public void setCampName(String campName) {
this.campName = campName;
}
/**
* @return the campYear
*/
public Double getCampYear() {
return campYear;
}
/**
* @param campYear the campYear to set
*/
public void setCampYear(Double campYear) {
this.campYear = campYear;
}
/**
* @return the username
*/
public String getUsername() {
return username;
}
/**
* @param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* @return the duedate
*/
public LocalDateTime getDuedate() {
return duedate;
}
/**
* @param duedate the duedate to set
*/
public void setDuedate(LocalDateTime duedate) {
this.duedate = duedate;
}
}

View File

@ -0,0 +1,111 @@
package de.jottyfan.camporganizer.module.business.privileges.impl;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_SALESPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.V_CAMP;
import java.time.LocalDateTime;
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.DeleteConditionStep;
import org.jooq.InsertReturningStep;
import org.jooq.Record5;
import org.jooq.Record8;
import org.jooq.SelectSeekStep3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import de.jottyfan.camporganizer.db.jooq.tables.records.TSalesprofileRecord;
/**
*
* @author jotty
*
*/
@Repository
@Transactional(transactionManager = "transactionManager")
public class PrivilegesGateway {
private static final Logger LOGGER = LogManager.getLogger(PrivilegesGateway.class);
@Autowired
private DSLContext jooq;
public List<PrivilegesBean> getPrivileges() {
SelectSeekStep3<Record8<String, String, LocalDateTime, String, Integer, Integer, String, Double>, LocalDateTime, String, String> sql = jooq
// @formatter:off
.select(T_PROFILE.FORENAME, T_PROFILE.SURNAME, T_PROFILE.DUEDATE, T_PROFILE.USERNAME, T_PROFILE.PK, V_CAMP.PK, V_CAMP.NAME, V_CAMP.YEAR)
.from(V_CAMP)
.leftJoin(T_SALESPROFILE).on(T_SALESPROFILE.FK_CAMP.eq(V_CAMP.PK))
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_SALESPROFILE.FK_PROFILE))
.orderBy(V_CAMP.ARRIVE, T_PROFILE.SURNAME, T_PROFILE.FORENAME);
// @formatter:on
LOGGER.debug(sql.toString());
List<PrivilegesBean> list = new ArrayList<>();
for (Record8<String, String, LocalDateTime, String, Integer, Integer, String, Double> r : sql.fetch()) {
PrivilegesBean bean = new PrivilegesBean();
bean.setCampName(r.get(V_CAMP.NAME));
bean.setCampYear(r.get(V_CAMP.YEAR));
bean.setFkCamp(r.get(V_CAMP.PK));
bean.setFkProfile(r.get(T_PROFILE.PK));
bean.setForename(r.get(T_PROFILE.FORENAME));
bean.setSurname(r.get(T_PROFILE.SURNAME));
bean.setUsername(r.get(T_PROFILE.USERNAME));
bean.setDuedate(r.get(T_PROFILE.DUEDATE));
list.add(bean);
}
return list;
}
public List<ProfileBean> getProfiles() {
SelectSeekStep3<Record5<Integer, String, String, LocalDateTime, String>, String, String, LocalDateTime> sql = jooq
// @formatter:off
.select(T_PROFILE.PK, T_PROFILE.FORENAME, T_PROFILE.SURNAME, T_PROFILE.DUEDATE, T_PROFILE.USERNAME)
.from(T_PROFILE)
.orderBy(T_PROFILE.SURNAME, T_PROFILE.FORENAME, T_PROFILE.DUEDATE);
// @formatter:on
LOGGER.debug(sql.toString());
List<ProfileBean> list = new ArrayList<>();
for (Record5<Integer, String, String, LocalDateTime, String> r : sql.fetch()) {
ProfileBean bean = new ProfileBean();
bean.setPk(r.get(T_PROFILE.PK));
bean.setForename(r.get(T_PROFILE.FORENAME));
bean.setSurname(r.get(T_PROFILE.SURNAME));
bean.setUsername(r.get(T_PROFILE.USERNAME));
bean.setDuedate(r.get(T_PROFILE.DUEDATE));
list.add(bean);
}
return list;
}
public Integer add(PrivilegesBean bean) {
InsertReturningStep<TSalesprofileRecord> sql = jooq
// @formatter:off
.insertInto(T_SALESPROFILE, T_SALESPROFILE.FK_CAMP, T_SALESPROFILE.FK_PROFILE)
.values(bean.getFkCamp(), bean.getFkProfile())
.onConflict(T_SALESPROFILE.FK_CAMP, T_SALESPROFILE.FK_PROFILE)
.doNothing();
// @formatter:on
LOGGER.debug(sql.toString());
return sql.execute();
}
public Integer remove(PrivilegesBean bean, String currentUser) {
DeleteConditionStep<TSalesprofileRecord> sql = jooq
// @formatter:off
.deleteFrom(T_SALESPROFILE)
.where(T_SALESPROFILE.FK_CAMP.eq(bean.getFkCamp()))
.and(T_SALESPROFILE.FK_PROFILE.eq(bean.getFkProfile()))
.and(T_SALESPROFILE.FK_PROFILE.notIn(jooq
.select(T_PROFILE.PK)
.from(T_PROFILE)
.where(T_PROFILE.USERNAME.eq(currentUser))));
// @formatter:on
LOGGER.debug(sql.toString());
return sql.execute();
}
}

View File

@ -0,0 +1,61 @@
package de.jottyfan.camporganizer.module.business.privileges.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.module.business.camp.impl.CampBean;
import de.jottyfan.camporganizer.module.business.privileges.IPrivilegesService;
/**
*
* @author jotty
*
*/
@Service
public class PrivilegesService implements IPrivilegesService {
@Autowired
private PrivilegesGateway gateway;
@Override
public Map<Integer, CampBean> getPrivileges() {
List<PrivilegesBean> list = gateway.getPrivileges();
Map<Integer, CampBean> camps = new HashMap<>();
for (PrivilegesBean bean : list) {
CampBean campBean = camps.get(bean.getFkCamp());
if (campBean == null) {
campBean = new CampBean();
camps.put(bean.getFkCamp(), campBean);
}
campBean.setPk(bean.getFkCamp());
campBean.setName(bean.getCampName());
campBean.setYear(bean.getCampYear());
ProfileBean profileBean = new ProfileBean();
profileBean.setPk(bean.getFkProfile());
profileBean.setForename(bean.getForename());
profileBean.setSurname(bean.getSurname());
profileBean.setUsername(bean.getUsername());
profileBean.setDuedate(bean.getDuedate());
campBean.getProfiles().add(profileBean);
}
return camps;
}
@Override
public List<ProfileBean> getProfiles() {
return gateway.getProfiles();
}
@Override
public void add(PrivilegesBean bean) {
gateway.add(bean);
}
@Override
public void remove(PrivilegesBean bean, String currentUser) {
gateway.remove(bean, currentUser);
}
}

View File

@ -0,0 +1,113 @@
package de.jottyfan.camporganizer.module.business.privileges.impl;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
*
* @author jotty
*
*/
public class ProfileBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pk;
private String forename;
private String surname;
private String username;
private LocalDateTime duedate;
public String dropdown() {
StringBuilder buf = new StringBuilder();
buf.append(forename).append(" ");
buf.append(surname).append(" (");
buf.append(username).append(", ");
buf.append(duedate == null ? "" : duedate.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")));
buf.append(")");
return buf.toString();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("{pk=").append(pk);
buf.append(", forename=").append(forename);
buf.append(", surname=").append(surname);
buf.append(", username=").append(username);
buf.append(", duedate=").append(duedate);
buf.append("}");
return buf.toString();
}
/**
* @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;
}
/**
* @return the username
*/
public String getUsername() {
return username;
}
/**
* @param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* @return the duedate
*/
public LocalDateTime getDuedate() {
return duedate;
}
/**
* @param duedate the duedate to set
*/
public void setDuedate(LocalDateTime duedate) {
this.duedate = duedate;
}
}

View File

@ -2,7 +2,6 @@ package de.jottyfan.camporganizer.module.common;
import javax.servlet.http.HttpServletRequest;
import org.jooq.exception.DataAccessException;
import org.keycloak.KeycloakSecurityContext;
import org.springframework.ui.Model;
@ -27,6 +26,7 @@ public abstract class CommonController {
/**
* setup the session for the template
*
* @param model the model
* @param request the request
*/
@ -34,4 +34,14 @@ public abstract class CommonController {
String username = getCurrentUser(request);
model.addAttribute("currentUser", username);
}
/**
* return true if the user has a valid keycloak session token
*
* @param request the request
* @return true or false
*/
public boolean isLoggedIn(HttpServletRequest request) {
return getCurrentUser(request) != null;
}
}

View File

@ -29,19 +29,19 @@ public class IndexController extends CommonController {
public String index(Model model) {
super.setupSession(model, request);
model.addAttribute("camps", service.getAllCamps());
return "/index";
return super.isLoggedIn(request) ? dashboard(model) : "/index";
}
@GetMapping("/user")
public String user(Model model) {
@GetMapping("/dashboard")
public String dashboard(Model model) {
super.setupSession(model, request);
model.addAttribute("camps", service.getAllCamps());
return "/user/index";
return "/dashboard";
}
@GetMapping("/logout")
public String getLogout(HttpServletRequest request) throws ServletException {
request.logout();
LOGGER.debug("logout");
return "redirect:/";
}
}

View File

@ -2,10 +2,8 @@ package de.jottyfan.camporganizer.module.common;
import static de.jottyfan.camporganizer.db.jooq.Tables.V_CAMP;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;

View File

@ -77,7 +77,7 @@ body {
background: transparent;
border: 2px solid transparent;
padding: 8px;
color: silver;
color: gray;
}
.btn-icon-silent:hover {

View File

@ -0,0 +1,77 @@
<!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 Business</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="@{/business/}" class="btn btn-secondary btn-icon-silent" title="zur Finanzübersicht"><i class="far fa-money-bill-alt"></i></a>
<a th:href="@{/business/bookings}" class="btn btn-secondary btn-icon-silent" title="Buchungsübersicht" sec:authorize="hasRole('business_booking')"><i class="fas fa-users"></i></a>
<a th:href="@{'/business/bookings/' + ${booker.pk}}" class="btn btn-secondary btn-icon-silent" title="aktualisieren"><i class="fas fa-sync"></i></a>
</header>
<content>
<div class="mainpage">
<div class="container" style="max-width: 100%">
<div class="row">
<div class="col">
<div class="card" style="width: 480px" sec:authorize="hasRole('business_booking')">
<div class="card-header">Angemeldete Person</div>
<div class="card-body">
<table class="table">
<tbody>
<tr>
<th>Name</th>
<td th:text="${booker.name}"></td>
</tr>
<tr>
<th>Geschlecht</th>
<td th:text="${booker.sex}"></td>
</tr>
<tr>
<th>Freizeit</th>
<td th:text="${booker.camp}"></td>
</tr>
<tr>
<th>Rolle</th>
<td th:text="${booker.role}"></td>
</tr>
<tr>
<th>Angemeldet</th>
<td th:text="${#temporals.format(booker.bookingDate, 'dd.MM.yyyy')}"></td>
</tr>
<tr>
<th>Bestätigt</th>
<td th:text="${booker.accept == null ? '' : (booker.accept ? 'Ja' : 'abgelehnt')}"></td>
</tr>
<tr>
<th>Preis</th>
<td th:text="${booker.price}" />
</tr>
<tr>
<th>Kontostand</th>
<td><span th:text="${#strings.replace(#numbers.formatCurrency(booker.paid), '¤', '€')}" th:if="${booker.paid != null}"></span>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col">
<div class="card" style="width: 480px" sec:authorize="hasRole('business_booking')">
<div class="card-header">Einzahlung</div>
<div class="card-body">
<form action="#" th:action="@{'/business/bookings/payment/' + ${booker.pk}}" th:object="${addBean}" method="post">
<input type="number" step="0.01" th:field="*{payment}"> <input type="submit" style="padding: 4px" value="einzahlen">
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</content>
</body>
</html>

View File

@ -0,0 +1,58 @@
<!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 Business</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="@{/business/}" class="btn btn-secondary btn-icon-silent" title="zur Finanzübersicht"><i class="far fa-money-bill-alt"></i></a>
<a th:href="@{/business/bookings}" class="btn btn-secondary btn-icon-silent" title="aktualisieren"><i class="fas fa-sync"></i></a>
</header>
<content>
<div class="mainpage">
<div class="card" sec:authorize="hasRole('business_booking')">
<div class="card-header">Angemeldete Personen</div>
<div class="card-body">
<table id="bookers" class="table">
<thead>
<tr>
<th>Name</th>
<th>Geschlecht</th>
<th>Freizeit</th>
<th>Rolle</th>
<th>Kontostand</th>
<th>Angemeldet</th>
<th>Bestätigt</th>
</tr>
</thead>
<tbody>
<th:block th:each="b : ${bookers}">
<tr>
<td th:text="${b.name}"></td>
<td th:text="${b.sex}"></td>
<td th:text="${b.camp}"></td>
<td th:text="${b.role}"></td>
<td><span th:text="${#strings.replace(#numbers.formatCurrency(b.paid), '¤', '€')}"></span> <a class="btn btn-icon-silent" th:href="@{/business/bookings/{id}(id=${b.pk})}"
title="bearbeiten"><i class="fas fa-edit"></i></a></td>
<td th:text="${#temporals.format(b.bookingDate, 'dd.MM.yyyy')}"></td>
<td th:text="${b.accept == null ? '' : (b.accept ? 'Ja' : 'abgelehnt')}"></td>
</tr>
</th:block>
</tbody>
</table>
<script>
$(document).ready(function() {
$("#bookers").DataTable({
language : locale_de
});
});
</script>
</div>
</div>
</div>
</content>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!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 Business</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="@{/business}" class="btn btn-secondary btn-icon-silent" title="aktualisieren"><i class="fas fa-sync"></i></a>
<a th:href="@{/business/bookings}" class="btn btn-secondary btn-icon-silent" title="zur Buchungsübersicht" sec:authorize="hasRole('business_booking')"><i class="fas fa-users"></i></a>
<a th:href="@{/business/privileges}" class="btn btn-secondary btn-icon-silent" title="Nutzerverwaltung" sec:authorize="hasRole('admin')"><i class="fas fa-user-lock"></i></a>
</header>
<content>
<div class="mainpage">
<div class="card" style="width: 500px">
<div class="card-header">Finanzübersicht über alle Freizeiten</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th scope="col">Jahr</th>
<th scope="col">Freizeit</th>
<th scope="col">Kontostand</th>
</tr>
</thead>
<tbody>
<th:block th:each="b : ${campBudgets}" sec:authorize="hasRole('business')">
<tr>
<td th:text="${#numbers.formatDecimal(b.campYear, 1, 0)}"></td>
<td><a th:href="@{/business/camp/{id}(id=${b.campId})}" th:text="${b.campName}" class="tablelink" title="Freizeitübersicht laden"></a></td>
<td th:text="${#strings.replace(#numbers.formatCurrency(b.budget), '¤', '€')}"></td>
</tr>
</th:block>
</tbody>
</table>
</div>
</div>
</div>
</content>
</body>
</html>

View File

@ -0,0 +1,105 @@
<!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 Business</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="@{/business/}" class="btn btn-secondary btn-icon-silent" title="zur Finanzübersicht"><i class="far fa-money-bill-alt"></i></a>
<a th:href="@{/business/camp/{id}(id=${campId})}" class="btn btn-secondary btn-icon-silent" title="aktualisieren"><i class="fas fa-sync"></i></a>
</header>
<content>
<div class="mainpage">
<div class="container" style="max-width: 100%">
<div class="row">
<div class="col">
<div class="card" style="width: 480px" th:if="${camp != null}">
<div class="card-header">
<span th:text="${camp.name}"></span> von <span th:text="${#numbers.formatInteger(camp.year, 0)}"></span>
</div>
<div class="card-body">
<table class="table">
<tbody>
<tr>
<td>Freizeitname</td>
<th th:text="${camp.name}"></th>
</tr>
<tr>
<td>Zeit</td>
<th><span th:text="${#temporals.format(camp.arrive, 'dd.MM.')}"></span> - <span th:text="${#temporals.format(camp.depart, 'dd.MM.yyyy')}"></span></th>
<tr>
<td>Ort</td>
<th th:text="${camp.locationName}"></th>
</tr>
<tr>
<td>Preis</td>
<th><pre th:utext="${camp.price}"></pre></th>
</tr>
<tr>
<td>bestätigt</td>
<th th:text="${booking.approved}"></th>
</tr>
<tr>
<td>abgelehnt</td>
<th th:text="${booking.rejected}"></th>
</tr>
<tr>
<td>offen</td>
<th th:text="${booking.open}"></th>
</tr>
<tr>
<td>Beiträge</td>
<th th:text="${#strings.replace(#numbers.formatCurrency(booking.paid), '¤', '€')}"></th>
</tbody>
</table>
</div>
</div>
</div>
<div class="col" sec:authorize="hasRole('business_booking')">
<div class="card">
<div class="card-header">Angemeldete Personen</div>
<div class="card-body">
<table id="bookers" class="table">
<thead>
<tr>
<th>Name</th>
<th>Geschlecht</th>
<th>Rolle</th>
<th>Kontostand</th>
<th>Angemeldet</th>
<th>Bestätigt</th>
</tr>
</thead>
<tbody>
<th:block th:each="b : ${bookers}">
<tr>
<td th:text="${b.name}"></td>
<td th:text="${b.sex}"></td>
<td th:text="${b.role}"></td>
<td th:text="${#strings.replace(#numbers.formatCurrency(b.paid), '¤', '€')}"></td>
<td th:text="${#temporals.format(b.bookingDate, 'dd.MM.yyyy')}"></td>
<td th:text="${b.accept == null ? '' : (b.accept ? 'Ja' : 'abgelehnt')}"></td>
</tr>
</th:block>
</tbody>
</table>
<script>
$(document).ready(function() {
$("#bookers").DataTable({
language: locale_de
});
});
</script>
</div>
</div>
</div>
</div>
</div>
<div class="alert alert-danger" role="alert" th:if="${camp == null}">Es wurde keine Freizeit gefunden, die über diese ID angesprochen werden kann.</div>
</div>
</content>
</body>
</html>

View File

@ -0,0 +1,80 @@
<!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 Business</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="@{/business/}" class="btn btn-secondary btn-icon-silent" title="zur Finanzübersicht"><i class="far fa-money-bill-alt"></i></a>
<a th:href="@{/business/privileges}" class="btn btn-secondary btn-icon-silent" title="aktualisieren"><i class="fas fa-sync"></i></a>
</header>
<content>
<div class="mainpage">
<div class="card">
<div class="card-header">Nutzerverwaltung für die Abrechnung von Freizeiten</div>
<div class="card-body">
<table id="privs">
<thead>
<tr>
<th scope="col">Freizeit</th>
<th scope="col">Abrechnung darf bearbeiten</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<th:block th:each="e : ${privileges.entrySet()}" sec:authorize="hasRole('admin')">
<tr>
<td><span th:text="${e.value.name}"></span>&nbsp;<span th:text="${#numbers.formatDecimal(e.value.year, 1, 0)}"></span></td>
<td><th:block th:each="p : ${e.value.profiles}">
<div class="dropdown" style="display: inline" th:if="${p.pk != null}">
<button class="btn dropdown-toggle" style="border: 1px solid silver" type="button" th:id="'btn_' + ${e.key} + '_' + ${p.pk}" data-bs-toggle="dropdown" aria-expanded="false">
<span th:text="${p.forename} + ' ' + ${p.surname}"></span>
</button>
<ul class="dropdown-menu" style="background-image: linear-gradient(to bottom right, #defac0, #9ef542) !important;" th:aria-labelledby="'btn_' + ${e.key} + '_' + ${p.pk}">
<li><div style="padding: 8px">
Login: <span th:text="${p.username}"></span>
</div></li>
<li><div style="padding: 8px">
Ablaufdatum: <span th:text="${#temporals.format(p.duedate, 'dd.MM.yyyy')}"></span>
</div></li>
<li><hr class="dropdown-divider" th:if="${p.username != currentUser}"></li>
<li><a class="dropdown-item" th:if="${p.username != currentUser}" th:href="@{/business/privileges/delete?fkCamp={c}&fkProfile={r}(c=${e.key},r=${p.pk})}">Recht entziehen</a></li>
</ul>
</div>
</th:block></td>
<td>
<form action="#" th:action="@{/business/privileges/add?fkCamp={cid}(cid=${e.key})}" th:object="${bean}" method="post">
<span class="btn-group"> <select th:id="${e.value.pk}" class="form-control select2-single" th:field="*{fkProfile}">
<option value="">Auswählen</option>
<th:block th:each="u : ${profiles}">
<option th:if="${u != null}" th:value="${u.pk}" th:text="${u.dropdown()}" />
</th:block>
</select> <input type="submit" style="padding: 4px" value="Recht erteilen">
</span>
<script>
var id = "[[${e.value.pk}]]";
$("#" + id).select2();
</script>
</form>
</td>
</tr>
</th:block>
</tbody>
</table>
<script>
$(document).ready(function() {
$("#privs").DataTable({
language: locale_de
});
});
</script>
</div>
</div>
</div>
</content>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!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 2</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="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>
<span>Das Buchungsportal dess Onkel Werner Freizeiten e.V.</span>
</header>
<content>
<div class="mainpage">
</div>
</content>
</body>
</html>

View File

@ -23,7 +23,7 @@
<b th:inline="text">[[${currentUser}]]</b> aus Version <span th:text="${@manifestBean.getVersion()}"></span> <a th:href="@{/logout}">ausloggen</a>
</span>
<span th:if="${#strings.isEmpty(currentUser)}">
<a th:href="@{/user}">einloggen</a>
<a th:href="@{/dashboard}">einloggen</a>
</span>
</div>
<span th:replace="${header}">Layout header</span>

View File

@ -1,61 +0,0 @@
<!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 2</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="aktualisieren"><i class="fas fa-sync"></i></a>
<span>Das Buchungsportal dess Onkel Werner Freizeiten e.V.</span>
</header>
<content>
<div class="mainpage">
<script type="text/javascript">
var mytoggle = new MyToggle();
</script>
<h1>Unsere Freizeiten</h1>
<div class="card bottomdist16" th:each="c : ${camps}">
<div class="card-header mytoggle_btn" th:onclick="mytoggle.toggle('campdiv_[[${c.pk}]]')">
<span th:text="${c.name}"></span>&nbsp;<span th:text="${#numbers.formatInteger(c.year, 0)}" th:if="${c.year != null}"></span>
</div>
<div th:id="'campdiv_' + ${c.pk}" class="card-body mytoggle_collapsed">
<div class="container">
<div class="row">
<div class="col-sm-3">Ort</div>
<div class="col-sm-9">
<a th:href="${c.url}" th:text="${c.locationName}" target="_blank"></a>
</div>
</div>
<div class="row">
<div class="col-sm-3">Jungen und Mädchen</div>
<div class="col-sm-9" th:text="${c.minAge} + ' - ' + ${c.maxAge}"></div>
</div>
<div class="row">
<div class="col-sm-3">Zeit</div>
<div class="col-sm-9">
<span th:text="${#temporals.format(c.arrive, 'dd.MM.')} + ' - ' + ${#temporals.format(c.depart, 'dd.MM.yyyy')}" th:if="${c.arrive != null && c.depart != null}"></span>
</div>
</div>
<div class="row">
<div class="col-sm-3">Preis</div>
<div class="col-sm-9" th:text="${c.price}"></div>
</div>
<div class="row">
<div class="col-sm-3">Ferien</div>
<div class="col-sm-9" th:text="${c.countries}"></div>
</div>
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm-9">
<button class="btn btn-primary">jetzt anmelden</button>
</div>
</div>
</div>
</div>
</div>
</div>
</content>
</body>
</html>