added bulk slot creation

This commit is contained in:
Jörg Henke
2024-01-05 17:30:04 +01:00
parent b779590309
commit 1f71d9edeb
7 changed files with 251 additions and 12 deletions

View File

@ -7,7 +7,7 @@ plugins {
apply plugin: 'io.spring.dependency-management'
group = 'de.jottyfan'
version = '1.4.4'
version = '1.4.5'
description = """timetrack"""

View File

@ -21,6 +21,7 @@ import de.jottyfan.timetrack.modules.done.model.DoneBean;
import de.jottyfan.timetrack.modules.done.model.DoneModel;
import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
import de.jottyfan.timetrack.modules.done.model.SlotBean;
import de.jottyfan.timetrack.modules.done.model.SlotRangeBean;
import de.jottyfan.timetrack.modules.done.model.SummaryBean;
import jakarta.annotation.security.RolesAllowed;
@ -69,7 +70,7 @@ public class DoneController extends CommonController {
model.addAttribute("favorites", doneService.getFavorites(username));
return "done/list";
}
@RolesAllowed("timetrack_user")
@PostMapping("/done/list")
public String getListForDate(Model model, @ModelAttribute("day") LocalDate day) {
@ -168,28 +169,28 @@ public class DoneController extends CommonController {
Integer amount = doneService.doDelete(id);
return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(id, model);
}
@RolesAllowed("timetrack_user")
@GetMapping(value = "/done/favorize/{id}")
public String favorize(@PathVariable Integer id) {
doneService.favorize(id);
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@GetMapping(value = "/done/unfavorize/{id}")
public String unfavorize(@PathVariable Integer id) {
doneService.unfavorize(id);
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@GetMapping(value = "/done/usefav/{id}")
public String usefavorite(@PathVariable Integer id) {
doneService.usefavorite(id);
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@PostMapping(value = "/done/overtime/update")
public String upsertOvertime(@ModelAttribute("overtimeBean") OvertimeBean bean) {
@ -197,7 +198,7 @@ public class DoneController extends CommonController {
doneService.upsertOvertime(bean, username);
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@GetMapping("/done/slot/{id}")
public String loadSlot(@PathVariable("id") Integer id, Model model) {
@ -205,7 +206,7 @@ public class DoneController extends CommonController {
model.addAttribute("bean", doneService.getSlot(id, username));
return "/done/slot/item";
}
@RolesAllowed("timetrack_user")
@GetMapping("/done/slot/add")
public String addSlot(@RequestParam("day") LocalDate day, Model model) {
@ -219,11 +220,26 @@ public class DoneController extends CommonController {
doneService.upsert(bean, provider.getName());
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@GetMapping("/done/slot/{id}/delete")
public String deleteSlot(@PathVariable("id") Integer slotId) {
doneService.delete(slotId, provider.getName());
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@GetMapping("/done/slot/range")
public String toAddRange(Model model) {
model.addAttribute("bean", new SlotRangeBean());
return "/done/slot/range";
}
@RolesAllowed("timetrack_user")
@PostMapping("/done/slot/addrange")
public String addRange(@ModelAttribute("bean") SlotRangeBean bean) {
doneService.addSlotRange(bean.getMinutes(), bean.getFrom(), bean.getUntil(), bean.getReason(), provider.getName(),
bean.getIncludeSaturday(), bean.getIncludeSunday());
return "redirect:/done/list";
}
}

View File

@ -9,8 +9,10 @@ import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
@ -21,10 +23,12 @@ import org.jooq.DeleteConditionStep;
import org.jooq.Field;
import org.jooq.InsertOnDuplicateSetMoreStep;
import org.jooq.InsertOnDuplicateStep;
import org.jooq.InsertReturningStep;
import org.jooq.Record1;
import org.jooq.Record3;
import org.jooq.Record4;
import org.jooq.Record5;
import org.jooq.Row4;
import org.jooq.SelectConditionStep;
import org.jooq.SelectHavingStep;
import org.jooq.SelectSeekStep1;
@ -213,7 +217,34 @@ public class DoneRepository {
: SlotBean.of(r.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME), r.get(T_REQUIRED_WORKTIME.DAY),
r.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES), r.get(T_REQUIRED_WORKTIME.REASON));
}
private String nullIfEmpty(String s) {
return s == null ? null : (s.isBlank() ? null : s);
}
public void addSlotRange(Integer minutes, String login, String reason, List<LocalDate> days) {
Integer fkLogin = jooq.select(T_LOGIN.PK).from(T_LOGIN).where(T_LOGIN.LOGIN.eq(login)).fetchOne(T_LOGIN.PK);
List<Row4<LocalDate, Integer, String, Integer>> rows = new ArrayList<Row4<LocalDate,Integer,String,Integer>>();
for(LocalDate day : days) {
rows.add(DSL.row(day, minutes, nullIfEmpty(reason), fkLogin));
}
InsertReturningStep<TRequiredWorktimeRecord> sql = jooq
// @formatter:off
.insertInto(T_REQUIRED_WORKTIME,
T_REQUIRED_WORKTIME.DAY,
T_REQUIRED_WORKTIME.REQUIRED_MINUTES,
T_REQUIRED_WORKTIME.REASON,
T_REQUIRED_WORKTIME.FK_LOGIN)
.valuesOfRows(rows)
.onConflict(T_REQUIRED_WORKTIME.FK_LOGIN, T_REQUIRED_WORKTIME.DAY)
.doUpdate()
.setAllToExcluded();
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
public void addSlot(SlotBean bean, String login) {
InsertOnDuplicateSetMoreStep<TRequiredWorktimeRecord> sql = jooq
// @formatter:off
@ -223,13 +254,13 @@ public class DoneRepository {
T_REQUIRED_WORKTIME.REASON,
T_REQUIRED_WORKTIME.FK_LOGIN)
.select(jooq
.select(DSL.val(bean.getDay()), DSL.val(bean.getMinutes()), DSL.val(bean.getReason()), T_LOGIN.PK)
.select(DSL.val(bean.getDay()), DSL.val(bean.getMinutes()), DSL.val(nullIfEmpty(bean.getReason())), T_LOGIN.PK)
.from(T_LOGIN)
.where(T_LOGIN.LOGIN.eq(login)))
.onConflict(T_REQUIRED_WORKTIME.FK_LOGIN, T_REQUIRED_WORKTIME.DAY)
.doUpdate()
.set(T_REQUIRED_WORKTIME.REQUIRED_MINUTES, bean.getMinutes())
.set(T_REQUIRED_WORKTIME.REASON, bean.getReason());
.set(T_REQUIRED_WORKTIME.REASON, nullIfEmpty(bean.getReason()));
// @formatter:off
LOGGER.trace(sql);
sql.execute();
@ -240,7 +271,7 @@ public class DoneRepository {
// @formatter:off
.update(T_REQUIRED_WORKTIME)
.set(T_REQUIRED_WORKTIME.REQUIRED_MINUTES, bean.getMinutes())
.set(T_REQUIRED_WORKTIME.REASON, bean.getReason())
.set(T_REQUIRED_WORKTIME.REASON, nullIfEmpty(bean.getReason()))
.where(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(bean.getId()))
.and(T_REQUIRED_WORKTIME.FK_LOGIN.in(jooq
.select(T_LOGIN.PK)

View File

@ -1,5 +1,6 @@
package de.jottyfan.timetrack.modules.done;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
@ -305,4 +306,25 @@ public class DoneService {
public void delete(Integer slotId, String username) {
repository.deleteSlot(slotId, username);
}
public void addSlotRange(Integer minutes, LocalDate from, LocalDate until, String reason, String username, Boolean includeSaturdays, Boolean includeSundays) {
List<LocalDate> days = new ArrayList<>();
if (!from.isBefore(until)) {
LocalDate tmp = from;
from = until;
until = tmp;
}
includeSaturdays = includeSaturdays == null ? false : includeSaturdays;
includeSundays = includeSundays == null ? false : includeSundays;
for (LocalDate i = from; i.isBefore(until.plusDays(1)); i = i.plusDays(1)) {
if (i.getDayOfWeek().equals(DayOfWeek.SUNDAY) && !includeSundays) {
// ignore
} else if (i.getDayOfWeek().equals(DayOfWeek.SATURDAY) && !includeSaturdays) {
// ignore
} else {
days.add(i);
}
}
repository.addSlotRange(minutes, username, reason, days);
}
}

View File

@ -0,0 +1,104 @@
package de.jottyfan.timetrack.modules.done.model;
import java.io.Serializable;
import java.time.LocalDate;
/**
*
* @author jotty
*
*/
public class SlotRangeBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer minutes;
private LocalDate from;
private LocalDate until;
private String reason;
private Boolean includeSaturday;
private Boolean includeSunday;
/**
* @return the minutes
*/
public Integer getMinutes() {
return minutes;
}
/**
* @param minutes the minutes to set
*/
public void setMinutes(Integer minutes) {
this.minutes = minutes;
}
/**
* @return the from
*/
public LocalDate getFrom() {
return from;
}
/**
* @param from the from to set
*/
public void setFrom(LocalDate from) {
this.from = from;
}
/**
* @return the until
*/
public LocalDate getUntil() {
return until;
}
/**
* @param until the until to set
*/
public void setUntil(LocalDate until) {
this.until = until;
}
/**
* @return the reason
*/
public String getReason() {
return reason;
}
/**
* @param reason the reason to set
*/
public void setReason(String reason) {
this.reason = reason;
}
/**
* @return the includeSaturday
*/
public Boolean getIncludeSaturday() {
return includeSaturday;
}
/**
* @param includeSaturday the includeSaturday to set
*/
public void setIncludeSaturday(Boolean includeSaturday) {
this.includeSaturday = includeSaturday;
}
/**
* @return the includeSunday
*/
public Boolean getIncludeSunday() {
return includeSunday;
}
/**
* @param includeSunday the includeSunday to set
*/
public void setIncludeSunday(Boolean includeSunday) {
this.includeSunday = includeSunday;
}
}

View File

@ -269,6 +269,13 @@
class="slot_badge_right" th:unless="${s.id}">&nbsp;--:--&nbsp;</span>
</div>
</div>
<br />
<div class="row">
<div class="col">
<a th:href="@{/done/slot/range}" class="btn btn-outline-primary">mehrere Slots auf einmal anlegen</a>
</div>
</div>
<br />
<div class="row alert alert-info">
<div class="col-sm-12">
<span style="text-decoration: underline">Legende</span>

View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}">
<head>
<title>Slot aktualisieren</title>
</head>
<body>
<ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
<a class="nav-link btn btn-outline-primary btn-white-text" th:href="@{/done/list}">zur Arbeitszeit</a>
</li>
</ul>
<main layout:fragment="content">
<div class="container formpane">
<form th:action="@{/done/slot/addrange}" method="post" th:object="${bean}">
<div class="row g-2" th:if="${bean}">
<div class="col-sm-3">ab</div>
<div class="col-sm-9">
<input type="date" th:field="*{from}" class="form-control" />
</div>
<div class="col-sm-3">bis</div>
<div class="col-sm-9">
<input type="date" th:field="*{until}" class="form-control" />
</div>
<div class="col-sm-3">vereinbarte Arbeitszeit in Minuten</div>
<div class="col-sm-9">
<input type="number" th:field="*{minutes}" class="form-control">
</div>
<div class="col-sm-3">Abweichungsgrund</div>
<div class="col-sm-9">
<select th:field="*{reason}" class="form-select">
<option value="">-</option>
<option value="Ar">Arbeits- und Dienstbefreiung</option>
<option value="Di">Dienstreise, Dienstgänge</option>
<option value="gF">gesetzlicher Feiertag</option>
<option value="Gl">Freistellung aus Gleitzeitguthaben</option>
<option value="Kr">Arbeits- und Dienstunfähigkeit</option>
<option value="mK">"mit Kind krank"</option>
<option value="Ur">Urlaub, Sonderurlaub, Kur</option>
<option value="Üb">Überstunden, Mehrarbeit</option>
</select>
</div>
<div class="col-sm-3">inklusive Samstage</div>
<div class="col-sm-9">
<input type="checkbox" th:checked="*{includeSaturday}" name="includeSaturday" />
</div>
<div class="col-sm-3">inklusive Sonntage</div>
<div class="col-sm-9">
<input type="checkbox" th:checked="*{includeSunday}" name="includeSunday" />
</div>
<div class="col-sm-3"></div>
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-primary">Anlegen</button>
</div>
</div>
</form>
</div>
</main>
</body>
</html>