Compare commits
3 Commits
48168aaf65
...
568dfc8a64
Author | SHA1 | Date | |
---|---|---|---|
568dfc8a64 | |||
8be05b8afc | |||
742446e46e |
@ -23,7 +23,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'de.jottyfan:timetrackjooq:20240103d'
|
||||
implementation 'de.jottyfan:timetrackjooq:20240104b'
|
||||
|
||||
implementation 'org.apache.logging.log4j:log4j-api:latest.release'
|
||||
implementation 'org.apache.logging.log4j:log4j-core:latest.release'
|
||||
|
@ -15,8 +15,8 @@ import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import de.jottyfan.timetrack.component.OAuth2Provider;
|
||||
import de.jottyfan.timetrack.modules.done.DoneBean;
|
||||
import de.jottyfan.timetrack.modules.done.DoneService;
|
||||
import de.jottyfan.timetrack.modules.done.model.DoneBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.DoneModel;
|
||||
import de.jottyfan.timetrack.modules.done.model.SummaryBean;
|
||||
import de.jottyfan.timetrack.modules.profile.ProfileService;
|
||||
|
@ -8,10 +8,17 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||
|
||||
import de.jottyfan.timetrack.component.OAuth2Provider;
|
||||
import de.jottyfan.timetrack.modules.CommonController;
|
||||
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.SummaryBean;
|
||||
import de.jottyfan.timetrack.modules.profile.ProfileService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
@ -52,6 +59,8 @@ public class DoneController extends CommonController {
|
||||
model.addAttribute("doneList", list);
|
||||
model.addAttribute("sum", sumBean);
|
||||
model.addAttribute("daysum", doneService.getDaysum(day, username));
|
||||
model.addAttribute("overtimeBean", doneService.getOvertimeBean(username));
|
||||
model.addAttribute("slots", doneService.getSlots(username));
|
||||
model.addAttribute("schedule", weekBean.toJson());
|
||||
model.addAttribute("recentList", doneService.getListRecent(username, 10));
|
||||
model.addAttribute("projectList", doneService.getProjects(false));
|
||||
@ -63,6 +72,14 @@ public class DoneController extends CommonController {
|
||||
return "done/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/done/list")
|
||||
public String getListForDate(Model model, @ModelAttribute("day") LocalDate day) {
|
||||
DoneModel doneModel = new DoneModel();
|
||||
doneModel.setDay(day);
|
||||
return getList(model, doneModel);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/abort/{day}")
|
||||
public String abort(@PathVariable String day, Model model) {
|
||||
@ -176,4 +193,20 @@ public class DoneController extends CommonController {
|
||||
doneService.usefavorite(id);
|
||||
return "redirect:/done/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping(value = "/done/overtime/update")
|
||||
public String upsertOvertime(@ModelAttribute("overtimeBean") OvertimeBean bean) {
|
||||
String username = provider.getName();
|
||||
doneService.upsertOvertime(bean, username);
|
||||
return "redirect:/done/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/slot/{id}")
|
||||
public String loadSlot(@PathVariable("id") Integer id, Model model) {
|
||||
String username = provider.getName();
|
||||
model.addAttribute("bean", doneService.getSlot(id, username));
|
||||
return "/done/slot/item";
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ import de.jottyfan.timetrack.db.done.tables.records.VModuleRecord;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.VProjectRecord;
|
||||
import de.jottyfan.timetrack.db.profile.tables.records.TLoginRecord;
|
||||
import de.jottyfan.timetrack.help.LocalDateHelper;
|
||||
import de.jottyfan.timetrack.modules.done.model.DoneBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.FavoriteBean;
|
||||
|
||||
/**
|
||||
|
@ -1,25 +1,40 @@
|
||||
package de.jottyfan.timetrack.modules.done;
|
||||
|
||||
import static de.jottyfan.timetrack.db.done.Tables.V_CURRENT_OVERTIME;
|
||||
import static de.jottyfan.timetrack.db.done.Tables.T_OVERTIME;
|
||||
import static de.jottyfan.timetrack.db.done.Tables.T_REQUIRED_WORKTIME;
|
||||
import static de.jottyfan.timetrack.db.done.Tables.V_DAY;
|
||||
import static de.jottyfan.timetrack.db.profile.Tables.T_LOGIN;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.DatePart;
|
||||
import org.jooq.Field;
|
||||
import org.jooq.Record6;
|
||||
import org.jooq.InsertOnDuplicateStep;
|
||||
import org.jooq.Record1;
|
||||
import org.jooq.Record3;
|
||||
import org.jooq.Record5;
|
||||
import org.jooq.SelectConditionStep;
|
||||
import org.jooq.SelectHavingStep;
|
||||
import org.jooq.SelectSeekStep1;
|
||||
import org.jooq.UpdateConditionStep;
|
||||
import org.jooq.impl.DSL;
|
||||
import org.jooq.types.YearToSecond;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import de.jottyfan.timetrack.db.done.tables.records.TOvertimeRecord;
|
||||
import de.jottyfan.timetrack.modules.done.model.DaysumBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.SlotBean;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -37,22 +52,20 @@ public class DoneRepository {
|
||||
Field<LocalTime> WORKTIME = DSL.field("worktime", LocalTime.class);
|
||||
Field<LocalTime> BREAKTIME = DSL.field("breaktime", LocalTime.class);
|
||||
|
||||
SelectConditionStep<Record6<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond, LocalTime>> sql = jooq
|
||||
SelectConditionStep<Record5<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond>> sql = jooq
|
||||
// @formatter:off
|
||||
.select(V_DAY.STARTTIME,
|
||||
V_DAY.ENDTIME,
|
||||
V_DAY.WORKTIME.cast(LocalTime.class).as(WORKTIME),
|
||||
V_DAY.BREAKTIME.cast(LocalTime.class).as(BREAKTIME),
|
||||
V_DAY.DAY_OVERTIME,
|
||||
V_CURRENT_OVERTIME.OVERTIME.plus(DSL.coalesce(V_DAY.DAY_OVERTIME, YearToSecond.valueOf(0))).as(V_CURRENT_OVERTIME.OVERTIME))
|
||||
V_DAY.DAY_OVERTIME)
|
||||
.from(V_DAY)
|
||||
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(V_DAY.FK_LOGIN))
|
||||
.leftJoin(V_CURRENT_OVERTIME).on(V_CURRENT_OVERTIME.FK_LOGIN.eq(V_DAY.FK_LOGIN))
|
||||
.where(V_DAY.DAY.eq(day))
|
||||
.and(T_LOGIN.LOGIN.eq(login));
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
Record6<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond, LocalTime> r = sql.fetchOne();
|
||||
Record5<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond> r = sql.fetchOne();
|
||||
if (r == null) {
|
||||
return null;
|
||||
} else {
|
||||
@ -63,10 +76,130 @@ public class DoneRepository {
|
||||
bean.setBreaks(r.get(BREAKTIME));
|
||||
YearToSecond dayOvertime = r.get(V_DAY.DAY_OVERTIME);
|
||||
Duration dayOvertimeDuration = dayOvertime == null ? null : dayOvertime.toDuration();
|
||||
String dayOvertimeString = dayOvertimeDuration == null ? null : String.format("%3d:%2d", dayOvertimeDuration.toHours(), Math.abs(dayOvertimeDuration.toMinutes() % 60));
|
||||
bean.setDayOvertime(dayOvertimeString == null ? "?" : dayOvertimeString);
|
||||
bean.setTotalOvertime(r.get(V_CURRENT_OVERTIME.OVERTIME));
|
||||
bean.setDayOvertime(
|
||||
dayOvertimeDuration == null ? null : Long.valueOf(dayOvertimeDuration.toMinutes()).intValue());
|
||||
bean.setTotalOvertime(getOvertimeOf(day, login));
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getOvertimeOf(LocalDate day, String login) {
|
||||
Field<Integer> OVERTIME = DSL.field("overtime", Integer.class);
|
||||
SelectHavingStep<Record1<Integer>> sql = jooq
|
||||
// @formatter:off
|
||||
.select(T_OVERTIME.OVERTIME_MINUTES.plus(DSL.sum(DSL.extract(V_DAY.WORKTIME, DatePart.MINUTE).minus(T_REQUIRED_WORKTIME.REQUIRED_MINUTES))).as(OVERTIME))
|
||||
.from(V_DAY)
|
||||
.innerJoin(T_REQUIRED_WORKTIME).on(T_REQUIRED_WORKTIME.DAY.eq(V_DAY.DAY).and(T_REQUIRED_WORKTIME.FK_LOGIN.eq(V_DAY.FK_LOGIN)))
|
||||
.innerJoin(T_OVERTIME).on(T_OVERTIME.FK_LOGIN.eq(V_DAY.FK_LOGIN))
|
||||
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(V_DAY.FK_LOGIN))
|
||||
.where(T_OVERTIME.IMPACT.ge(V_DAY.DAY.cast(LocalDateTime.class)))
|
||||
.and(V_DAY.DAY.le(day))
|
||||
.and(T_LOGIN.LOGIN.eq(login))
|
||||
.groupBy(V_DAY.FK_LOGIN, T_OVERTIME.OVERTIME_MINUTES);
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
return sql.fetchOne(OVERTIME);
|
||||
}
|
||||
|
||||
public OvertimeBean getOvertimeBean(String login) {
|
||||
SelectConditionStep<Record3<Integer, LocalDateTime, Integer>> sql = jooq
|
||||
// @formatter:off
|
||||
.select(T_OVERTIME.PK_OVERTIME,
|
||||
T_OVERTIME.IMPACT,
|
||||
T_OVERTIME.OVERTIME_MINUTES)
|
||||
.from(T_OVERTIME)
|
||||
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_OVERTIME.FK_LOGIN))
|
||||
.where(T_LOGIN.LOGIN.eq(login));
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
Record3<Integer, LocalDateTime, Integer> r = sql.fetchOne();
|
||||
OvertimeBean bean = new OvertimeBean();
|
||||
if (r == null) {
|
||||
bean.setImpact(LocalDate.now());
|
||||
bean.setOvertimeMinutes(0);
|
||||
} else {
|
||||
bean.setId(r.get(T_OVERTIME.PK_OVERTIME));
|
||||
bean.setImpact(r.get(T_OVERTIME.IMPACT).toLocalDate());
|
||||
bean.setOvertimeMinutes(r.get(T_OVERTIME.OVERTIME_MINUTES));
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
public void upsertOvertime(Integer pkOvertime, String login, LocalDate impact, Integer overtimeMinutes) {
|
||||
if (pkOvertime == null) {
|
||||
InsertOnDuplicateStep<TOvertimeRecord> sql = jooq
|
||||
// @formatter:off
|
||||
.insertInto(T_OVERTIME,
|
||||
T_OVERTIME.IMPACT,
|
||||
T_OVERTIME.OVERTIME_MINUTES,
|
||||
T_OVERTIME.FK_LOGIN)
|
||||
.select(jooq
|
||||
.select(DSL.val(impact == null ? null : impact.atStartOfDay()), DSL.val(overtimeMinutes), T_LOGIN.PK)
|
||||
.from(T_LOGIN)
|
||||
.where(T_LOGIN.LOGIN.eq(login)));
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
sql.execute();
|
||||
}
|
||||
UpdateConditionStep<TOvertimeRecord> sql = jooq
|
||||
// @formatter:off
|
||||
.update(T_OVERTIME)
|
||||
.set(T_OVERTIME.IMPACT, impact == null ? null : impact.atStartOfDay())
|
||||
.set(T_OVERTIME.OVERTIME_MINUTES, overtimeMinutes)
|
||||
.where(T_OVERTIME.PK_OVERTIME.eq(pkOvertime))
|
||||
.and(T_OVERTIME.FK_LOGIN.in(jooq
|
||||
.select(T_LOGIN.PK)
|
||||
.from(T_LOGIN)
|
||||
.where(T_LOGIN.LOGIN.eq(login))));
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
sql.execute();
|
||||
}
|
||||
|
||||
public List<SlotBean> getSlots(String login) {
|
||||
SelectSeekStep1<Record3<Integer, LocalDate, Integer>, LocalDate> sql = jooq
|
||||
// @formatter:off
|
||||
.select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME,
|
||||
T_REQUIRED_WORKTIME.DAY,
|
||||
T_REQUIRED_WORKTIME.REQUIRED_MINUTES)
|
||||
.from(T_REQUIRED_WORKTIME)
|
||||
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN))
|
||||
.where(T_LOGIN.LOGIN.eq(login))
|
||||
.orderBy(T_REQUIRED_WORKTIME.DAY);
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
Iterator<Record3<Integer, LocalDate, Integer>> i = sql.fetch().iterator();
|
||||
List<SlotBean> list = new ArrayList<>();
|
||||
while (i.hasNext()) {
|
||||
Record3<Integer, LocalDate, Integer> n = i.next();
|
||||
list.add(SlotBean.of(n.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME), n.get(T_REQUIRED_WORKTIME.DAY),
|
||||
n.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES)));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* get slot if login fits
|
||||
*
|
||||
* @param id the ID of the slot
|
||||
* @param login the login
|
||||
* @return the slot or null
|
||||
*/
|
||||
public SlotBean getSlot(Integer id, String login) {
|
||||
SelectConditionStep<Record3<Integer, LocalDate, Integer>> sql = jooq
|
||||
// @formatter:off
|
||||
.select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME,
|
||||
T_REQUIRED_WORKTIME.DAY,
|
||||
T_REQUIRED_WORKTIME.REQUIRED_MINUTES)
|
||||
.from(T_REQUIRED_WORKTIME)
|
||||
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN))
|
||||
.where(T_LOGIN.LOGIN.eq(login))
|
||||
.and(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(id));
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
Record3<Integer, LocalDate, Integer> r = sql.fetchOne();
|
||||
return r == null ? null
|
||||
: SlotBean.of(r.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME), r.get(T_REQUIRED_WORKTIME.DAY),
|
||||
r.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES));
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,10 @@ import de.jottyfan.timetrack.db.done.tables.records.VJobRecord;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.VModuleRecord;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.VProjectRecord;
|
||||
import de.jottyfan.timetrack.modules.done.model.DaysumBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.DoneBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.FavoriteBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.SlotBean;
|
||||
import de.jottyfan.timetrack.modules.note.NoteService;
|
||||
|
||||
/**
|
||||
@ -234,4 +237,20 @@ public class DoneService {
|
||||
public DaysumBean getDaysum(LocalDate day, String login) {
|
||||
return repository.getDaysum(day, login);
|
||||
}
|
||||
|
||||
public OvertimeBean getOvertimeBean(String login) {
|
||||
return repository.getOvertimeBean(login);
|
||||
}
|
||||
|
||||
public void upsertOvertime(OvertimeBean bean, String username) {
|
||||
repository.upsertOvertime(bean.getId(), username, bean.getImpact(), bean.getOvertimeMinutes());
|
||||
}
|
||||
|
||||
public List<SlotBean> getSlots(String username) {
|
||||
return repository.getSlots(username);
|
||||
}
|
||||
|
||||
public SlotBean getSlot(Integer id, String username) {
|
||||
return repository.getSlot(id, username);
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,44 @@ public class DaysumBean implements Serializable {
|
||||
private LocalTime daytimeUntil;
|
||||
private LocalTime dayworktime;
|
||||
private LocalTime breaks;
|
||||
private String dayOvertime;
|
||||
private LocalTime totalOvertime;
|
||||
private Integer dayOvertime;
|
||||
private Integer totalOvertime;
|
||||
|
||||
private String lz(Integer i) {
|
||||
if (i < 10) {
|
||||
return "0" + i;
|
||||
} else {
|
||||
return i.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public String printTotalOvertime() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
if (totalOvertime == null) {
|
||||
buf.append("?");
|
||||
} else {
|
||||
Boolean isNegative = totalOvertime < 0;
|
||||
buf.append(isNegative ? "-" : "");
|
||||
buf.append(Math.abs(totalOvertime) / 60);
|
||||
buf.append(":");
|
||||
buf.append(lz(Math.abs(totalOvertime) % 60));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String printDayOvertime() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
if (dayOvertime == null) {
|
||||
buf.append("?");
|
||||
} else {
|
||||
Boolean isNegative = dayOvertime < 0;
|
||||
buf.append(isNegative ? "-" : "");
|
||||
buf.append(Math.abs(dayOvertime) / 60);
|
||||
buf.append(":");
|
||||
buf.append(lz(Math.abs(dayOvertime) % 60));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the daytimeFrom
|
||||
@ -77,28 +113,28 @@ public class DaysumBean implements Serializable {
|
||||
/**
|
||||
* @return the dayOvertime
|
||||
*/
|
||||
public String getDayOvertime() {
|
||||
public Integer getDayOvertime() {
|
||||
return dayOvertime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dayOvertime the dayOvertime to set
|
||||
*/
|
||||
public void setDayOvertime(String dayOvertime) {
|
||||
public void setDayOvertime(Integer dayOvertime) {
|
||||
this.dayOvertime = dayOvertime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the totalovertime
|
||||
*/
|
||||
public LocalTime getTotalOvertime() {
|
||||
public Integer getTotalOvertime() {
|
||||
return totalOvertime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param totalovertime the totalovertime to set
|
||||
*/
|
||||
public void setTotalOvertime(LocalTime totalOvertime) {
|
||||
public void setTotalOvertime(Integer totalOvertime) {
|
||||
this.totalOvertime = totalOvertime;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package de.jottyfan.timetrack.modules.done;
|
||||
package de.jottyfan.timetrack.modules.done.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Duration;
|
@ -0,0 +1,62 @@
|
||||
package de.jottyfan.timetrack.modules.done.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
public class OvertimeBean implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer id;
|
||||
@DateTimeFormat(pattern="yyyy-MM-dd")
|
||||
private LocalDate impact;
|
||||
private Integer overtimeMinutes;
|
||||
|
||||
/**
|
||||
* @return the id
|
||||
*/
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the id to set
|
||||
*/
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the impact
|
||||
*/
|
||||
public LocalDate getImpact() {
|
||||
return impact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param impact the impact to set
|
||||
*/
|
||||
public void setImpact(LocalDate impact) {
|
||||
this.impact = impact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the overtimeMinutes
|
||||
*/
|
||||
public Integer getOvertimeMinutes() {
|
||||
return overtimeMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param overtimeMinutes the overtimeMinutes to set
|
||||
*/
|
||||
public void setOvertimeMinutes(Integer overtimeMinutes) {
|
||||
this.overtimeMinutes = overtimeMinutes;
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package de.jottyfan.timetrack.modules.done.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
public class SlotBean implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer id;
|
||||
private LocalDate day;
|
||||
private Integer minutes;
|
||||
|
||||
public static final SlotBean of(Integer id, LocalDate day, Integer minutes) {
|
||||
SlotBean bean = new SlotBean();
|
||||
bean.setId(id);
|
||||
bean.setDay(day);
|
||||
bean.setMinutes(minutes);
|
||||
return bean;
|
||||
}
|
||||
|
||||
public String printTime() {
|
||||
Integer hours = 0;
|
||||
Integer mins = 0;
|
||||
if (minutes != null) {
|
||||
hours = minutes / 60;
|
||||
mins = minutes % 60;
|
||||
}
|
||||
return String.format("%2d:%02d", hours, mins);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the day
|
||||
*/
|
||||
public LocalDate getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param day the day to set
|
||||
*/
|
||||
public void setDay(LocalDate day) {
|
||||
this.day = day;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minutes
|
||||
*/
|
||||
public Integer getMinutes() {
|
||||
return minutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param minutes the minutes to set
|
||||
*/
|
||||
public void setMinutes(Integer minutes) {
|
||||
this.minutes = minutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id
|
||||
*/
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the id to set
|
||||
*/
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -6,8 +6,6 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import de.jottyfan.timetrack.modules.done.DoneBean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author henkej
|
||||
|
@ -310,6 +310,15 @@ body {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.emphpink {
|
||||
font-weight: bolder;
|
||||
color: #613583;
|
||||
border: 1px solid gray;
|
||||
border-radius: 8px;
|
||||
background-image: linear-gradient(to left, #e6e6e6, white);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.tab-pane-table {
|
||||
background-color: white;
|
||||
padding: 8px;
|
||||
@ -394,3 +403,44 @@ body {
|
||||
.golden {
|
||||
color: darkgoldenrod;
|
||||
}
|
||||
|
||||
.slot_badge {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.slot_badge_left {
|
||||
border: 1px solid silver;
|
||||
border-radius: 12px 0px 0px 12px;
|
||||
background-color: #ccc;
|
||||
color: black;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] .slot_badge_left {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.slot_badge_middle {
|
||||
border-top: 1px solid silver;
|
||||
border-bottom: 1px solid silver;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.slot_badge_middle:hover {
|
||||
color: white;
|
||||
background-image: linear-gradient(to right bottom, #99c1f1, #1a5f64);
|
||||
}
|
||||
|
||||
.slot_badge_right {
|
||||
border: 1px solid silver;
|
||||
border-radius: 0px 12px 12px 0px;
|
||||
background-color: transparent;
|
||||
color: black;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] .slot_badge_right {
|
||||
color: white;
|
||||
}
|
||||
|
@ -52,6 +52,8 @@
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_module">Modul</a></li>
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_job">Aufgabe</a></li>
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_billing">Abrechnung</a></li>
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_overtime">Überstunden</a></li>
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_slot">Slots</a></li>
|
||||
</ul>
|
||||
<div class="tabdivblurred tab-content">
|
||||
<div id="div_list" class="tab-pane active tab-pane-table">
|
||||
@ -104,8 +106,8 @@
|
||||
<td>Ende: <span class="emphgreen" th:text="${#temporals.format(daysum.daytimeUntil, 'HH:mm')}" th:if="${daysum.daytimeUntil}"></span></td>
|
||||
<td>Arbeitszeit total: <span class="emphblue" th:text="${#temporals.format(daysum.dayworktime, 'HH:mm')}" th:if="${daysum.dayworktime}"></span></td>
|
||||
<td>Pausezeit total: <span class="emphorange" th:text="${#temporals.format(daysum.breaks, 'HH:mm')}" th:if="${daysum.breaks}"></span></td>
|
||||
<td>Überstunden heute: <span class="emphred" th:text="${daysum.dayOvertime}"></span></td>
|
||||
<td colspan="2">Überstunden total: <span class="emphred" th:text="${#temporals.format(daysum.totalOvertime, 'HH:mm')}" th:if="${daysum.totalOvertime}"></span></td>
|
||||
<td>Überstunden heute: <span class="emphred" th:text="${daysum.printDayOvertime()}"></span></td>
|
||||
<td colspan="2">Überstunden total: <span class="emphpink" th:text="${daysum.printTotalOvertime()}" th:if="${daysum.totalOvertime}"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
@ -213,6 +215,46 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="div_overtime" class="tab-pane fade tab-pane-table">
|
||||
<form th:action="@{/done/overtime/update}" method="post" th:object="${overtimeBean}">
|
||||
<input type="hidden" th:field="*{id}" />
|
||||
<div class="container">
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-12">
|
||||
<div class="alert alert-info">Hier werden die Überstunden einmalig angegeben. Dabei wird für einen bestimmten Tagesbeginn, an dem die Überstunden bekannt sind, der Wert gesetzt. Alle
|
||||
nachfolgenden Zeiten werden bei der Anzeige der Überstunden während der Datenerfassung berücksichtigt und einberechnet.</div>
|
||||
</div>
|
||||
<div class="col-sm-3">Tagesbeginn</div>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" th:field="*{impact}" class="form-control" />
|
||||
</div>
|
||||
<div class="col-sm-3">Überstunden (min)</div>
|
||||
<div class="col-sm-9">
|
||||
<input type="number" th:field="*{overtimeMinutes}" class="form-control" />
|
||||
</div>
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-9">
|
||||
<button type="submit" class="btn btn-outline-primary">Übernehmen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="div_slot" class="tab-pane fade tab-pane-table">
|
||||
<div class="alert alert-info">
|
||||
Zur Berechnung der täglichen Überstunden müssen Slots angelegt werden, die definieren, an welchen Tagen wieviele Stunden zu arbeiten ist.
|
||||
Urlaub und Arbeitsbefreiung können durch das Entfernen des jeweiligen Slots ermöglicht werden.
|
||||
Die Überstundenberechnung hängt von der Vollständigkeit der vorhandenen Slots ab; fehlen Slots, wird die Arbeitszeit jener Tage nicht eingerechnet.
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col slot_badge" th:each="s : ${slots}">
|
||||
<span class="slot_badge_left" th:text="${#temporals.format(s.day, 'EEE, dd.MM.yyyy')}"></span><a th:href="@{/done/slot/{id}(id=${s.id})}" class="slot_badge_middle"><i class="fas fa-pencil"></i></a><span class="slot_badge_right" th:text="${s.printTime()}"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
TODO: nur die Slots dieses Monats anzeigen, damit die Ladezeit nicht unnötig belastet wird
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
23
src/main/resources/templates/done/slot/item.html
Normal file
23
src/main/resources/templates/done/slot/item.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!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">
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div class="container formpane">
|
||||
<div class="row" th:if="${bean}">
|
||||
<div class="col-sm-3">ID</div>
|
||||
<div class="col-sm-9" th:text="${bean.id}"></div>
|
||||
<div class="col-sm-3">Tag</div>
|
||||
<div class="col-sm-9" th:text="${#temporals.format(bean.day, 'EEEE, dd.MM.yyyy')}"></div>
|
||||
<div class="col-sm-3">vereinbarte Arbeitszeit</div>
|
||||
<div class="col-sm-9" th:text="${bean.printTime()}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user