Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94a0b86620 |
@@ -23,6 +23,24 @@ wird kein Webserver gestartet.
|
||||
|
||||
Kurze Zeiten wie `6:01` oder `01:52` sind ebenfalls erlaubt.
|
||||
|
||||
## Brutto, Pause, Netto und Tages-Saldo
|
||||
|
||||
```sh
|
||||
goTimecalc --brutto 09:26:12 --pause 00:30:00 --soll 07:53:00
|
||||
```
|
||||
|
||||
Ausgabe:
|
||||
|
||||
```text
|
||||
Brutto-Zeit 09:26:12
|
||||
- Pause 00:30:00
|
||||
= Netto-Zeit 08:56:12
|
||||
- Sollzeit 07:53:00
|
||||
= Tagesaldo +01:03:12
|
||||
```
|
||||
|
||||
Der Tages-Saldo kann positiv oder negativ sein.
|
||||
|
||||
## Start, Ende und Pause
|
||||
|
||||
```sh
|
||||
@@ -32,12 +50,13 @@ goTimecalc --start 08:00 --ende 16:30 --pause 00:30 --soll 07:53
|
||||
Beispielausgabe:
|
||||
|
||||
```text
|
||||
Start 08:00:00
|
||||
Ende 16:30:00
|
||||
- Pause 00:30:00
|
||||
= Netto 08:00:00
|
||||
Soll 07:53:00
|
||||
Ueberzeit +00:07:00
|
||||
Start 08:00:00
|
||||
Ende 16:30:00
|
||||
Brutto-Zeit 08:30:00
|
||||
- Pause 00:30:00
|
||||
= Netto-Zeit 08:00:00
|
||||
- Sollzeit 07:53:00
|
||||
= Tagesaldo +00:07:00
|
||||
```
|
||||
|
||||
Wenn die Endzeit kleiner als die Startzeit ist, wird eine Nachtschicht angenommen:
|
||||
|
||||
@@ -54,6 +54,7 @@ func run(args []string, out io.Writer) error {
|
||||
addrInput := flags.String("addr", "127.0.0.1:8080", "Adresse fuer das Webinterface")
|
||||
nettoInput := flags.String("netto", "", "Netto-Zeit, z.B. 06:01 oder 06:01:14")
|
||||
minusInput := flags.String("minus", "", "Minuszeit, z.B. 01:52")
|
||||
bruttoInput := flags.String("brutto", "", "Brutto-Zeit, z.B. 09:26:12")
|
||||
startInput := flags.String("start", "", "Startzeit, z.B. 08:00")
|
||||
endInput := flags.String("ende", "", "Endzeit, z.B. 16:30")
|
||||
pauseInput := flags.String("pause", "00:00", "Pause, z.B. 00:30")
|
||||
@@ -64,6 +65,7 @@ func run(args []string, out io.Writer) error {
|
||||
flags.Usage = func() {
|
||||
fmt.Fprintln(out, "Nutzung:")
|
||||
fmt.Fprintln(out, " goTimecalc 06:01:14 01:51:46")
|
||||
fmt.Fprintln(out, " goTimecalc --brutto 09:26:12 --pause 00:30 --soll 07:53")
|
||||
fmt.Fprintln(out, " goTimecalc --start 08:00 --ende 16:30 --pause 00:30 --soll 07:53")
|
||||
fmt.Fprintln(out, " goTimecalc --day 08:00-16:30,00:30 --day 09:00-17:00 --soll 15:46")
|
||||
fmt.Fprintln(out, " goTimecalc --set-soll 07:53")
|
||||
@@ -103,6 +105,10 @@ func run(args []string, out io.Writer) error {
|
||||
return runDays(out, days, *sollInput)
|
||||
}
|
||||
|
||||
if *bruttoInput != "" {
|
||||
return runBruttoPause(out, *bruttoInput, *pauseInput, *sollInput)
|
||||
}
|
||||
|
||||
if *startInput != "" || *endInput != "" {
|
||||
return runStartEnd(out, *startInput, *endInput, *pauseInput, *sollInput)
|
||||
}
|
||||
@@ -133,6 +139,20 @@ func runNettoMinus(out io.Writer, nettoInput string, minusInput string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runBruttoPause(out io.Writer, bruttoInput string, pauseInput string, sollInput string) error {
|
||||
brutto, err := parseDuration(bruttoInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Brutto-Zeit: %w", err)
|
||||
}
|
||||
|
||||
pause, err := parseDuration(pauseInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Pause: %w", err)
|
||||
}
|
||||
|
||||
return printBruttoNetto(out, brutto, pause, sollInput)
|
||||
}
|
||||
|
||||
func runStartEnd(out io.Writer, startInput string, endInput string, pauseInput string, sollInput string) error {
|
||||
if startInput == "" || endInput == "" {
|
||||
return errors.New("--start und --ende muessen zusammen angegeben werden")
|
||||
@@ -153,24 +173,18 @@ func runStartEnd(out io.Writer, startInput string, endInput string, pauseInput s
|
||||
return fmt.Errorf("Pause: %w", err)
|
||||
}
|
||||
|
||||
netto := workDuration(start, end, pause)
|
||||
if netto < 0 {
|
||||
return errors.New("Pause ist laenger als die Arbeitszeit")
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Start %8s\n", formatDuration(start))
|
||||
fmt.Fprintf(out, "Ende %8s\n", formatDuration(end))
|
||||
fmt.Fprintf(out, "- Pause %8s\n", formatDuration(pause))
|
||||
fmt.Fprintf(out, "= Netto %8s\n", formatDuration(netto))
|
||||
|
||||
return printBalance(out, netto, sollInput)
|
||||
brutto := grossDuration(start, end)
|
||||
return printBruttoNetto(out, brutto, pause, sollInput)
|
||||
}
|
||||
|
||||
func runDays(out io.Writer, days dayList, sollInput string) error {
|
||||
total := 0
|
||||
|
||||
for i, day := range days {
|
||||
netto := workDuration(day.start, day.end, day.pause)
|
||||
netto := grossDuration(day.start, day.end) - day.pause
|
||||
if netto < 0 {
|
||||
return fmt.Errorf("Tag %d: Pause ist laenger als die Arbeitszeit", i+1)
|
||||
}
|
||||
@@ -206,6 +220,19 @@ func printSollzeit(out io.Writer, netto int, minuszeit int) {
|
||||
fmt.Fprintf(out, "= Sollzeit %8s\n", formatDuration(netto+minuszeit))
|
||||
}
|
||||
|
||||
func printBruttoNetto(out io.Writer, brutto int, pause int, sollInput string) error {
|
||||
netto := brutto - pause
|
||||
if netto < 0 {
|
||||
return errors.New("Pause ist laenger als die Brutto-Zeit")
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Brutto-Zeit %8s\n", formatDuration(brutto))
|
||||
fmt.Fprintf(out, "- Pause %8s\n", formatDuration(pause))
|
||||
fmt.Fprintf(out, "= Netto-Zeit %8s\n", formatDuration(netto))
|
||||
|
||||
return printBalance(out, netto, sollInput)
|
||||
}
|
||||
|
||||
func printBalance(out io.Writer, netto int, sollInput string) error {
|
||||
soll, found, err := wantedSoll(sollInput)
|
||||
if err != nil {
|
||||
@@ -216,13 +243,13 @@ func printBalance(out io.Writer, netto int, sollInput string) error {
|
||||
}
|
||||
|
||||
diff := netto - soll
|
||||
fmt.Fprintf(out, "Soll %8s\n", formatDuration(soll))
|
||||
fmt.Fprintf(out, "- Sollzeit %8s\n", formatDuration(soll))
|
||||
if diff >= 0 {
|
||||
fmt.Fprintf(out, "Ueberzeit +%s\n", formatDuration(diff))
|
||||
fmt.Fprintf(out, "= Tagesaldo +%s\n", formatDuration(diff))
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Minuszeit -%s\n", formatDuration(-diff))
|
||||
fmt.Fprintf(out, "= Tagesaldo -%s\n", formatDuration(-diff))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -318,10 +345,14 @@ func parsePart(input string, label string) (int, error) {
|
||||
}
|
||||
|
||||
func workDuration(start int, end int, pause int) int {
|
||||
return grossDuration(start, end) - pause
|
||||
}
|
||||
|
||||
func grossDuration(start int, end int) int {
|
||||
if end < start {
|
||||
end += 24 * 3600
|
||||
}
|
||||
return end - start - pause
|
||||
return end - start
|
||||
}
|
||||
|
||||
func formatDuration(totalSeconds int) string {
|
||||
|
||||
+35
-7
@@ -24,9 +24,20 @@ func TestOldNettoMinusUsage(t *testing.T) {
|
||||
func TestStartEndWithPauseAndSoll(t *testing.T) {
|
||||
output := runCommand(t, "--start", "08:00", "--ende", "16:30", "--pause", "00:30", "--soll", "07:53")
|
||||
|
||||
assertContains(t, output, "= Netto 08:00:00")
|
||||
assertContains(t, output, "Soll 07:53:00")
|
||||
assertContains(t, output, "Ueberzeit +00:07:00")
|
||||
assertContains(t, output, "Brutto-Zeit 08:30:00")
|
||||
assertContains(t, output, "= Netto-Zeit 08:00:00")
|
||||
assertContains(t, output, "- Sollzeit 07:53:00")
|
||||
assertContains(t, output, "= Tagesaldo +00:07:00")
|
||||
}
|
||||
|
||||
func TestBruttoPauseWithPositiveTagesaldo(t *testing.T) {
|
||||
output := runCommand(t, "--brutto", "09:26:12", "--pause", "00:30:00", "--soll", "07:53:00")
|
||||
|
||||
assertContains(t, output, "Brutto-Zeit 09:26:12")
|
||||
assertContains(t, output, "- Pause 00:30:00")
|
||||
assertContains(t, output, "= Netto-Zeit 08:56:12")
|
||||
assertContains(t, output, "- Sollzeit 07:53:00")
|
||||
assertContains(t, output, "= Tagesaldo +01:03:12")
|
||||
}
|
||||
|
||||
func TestMultipleDays(t *testing.T) {
|
||||
@@ -39,7 +50,7 @@ func TestMultipleDays(t *testing.T) {
|
||||
assertContains(t, output, "Tag 1 08:00:00")
|
||||
assertContains(t, output, "Tag 2 07:30:00")
|
||||
assertContains(t, output, "= Summe 15:30:00")
|
||||
assertContains(t, output, "Minuszeit -00:16:00")
|
||||
assertContains(t, output, "= Tagesaldo -00:16:00")
|
||||
}
|
||||
|
||||
func TestShortDurationInput(t *testing.T) {
|
||||
@@ -87,8 +98,25 @@ func TestWebRangeCalculation(t *testing.T) {
|
||||
if len(resp.Lines) == 0 {
|
||||
t.Fatal("expected result lines")
|
||||
}
|
||||
assertLine(t, resp.Lines, "= Netto", "08:00:00")
|
||||
assertLine(t, resp.Lines, "Ueberzeit", "+00:07:00")
|
||||
assertLine(t, resp.Lines, "Brutto-Zeit", "08:30:00")
|
||||
assertLine(t, resp.Lines, "= Netto-Zeit", "08:00:00")
|
||||
assertLine(t, resp.Lines, "= Tagesaldo", "+00:07:00")
|
||||
}
|
||||
|
||||
func TestWebBruttoCalculation(t *testing.T) {
|
||||
resp, err := calculateWeb(webCalcRequest{
|
||||
Mode: "brutto",
|
||||
Brutto: "09:26:12",
|
||||
Pause: "00:30:00",
|
||||
Soll: "07:53:00",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertLine(t, resp.Lines, "Brutto-Zeit", "09:26:12")
|
||||
assertLine(t, resp.Lines, "= Netto-Zeit", "08:56:12")
|
||||
assertLine(t, resp.Lines, "= Tagesaldo", "+01:03:12")
|
||||
}
|
||||
|
||||
func TestWebDayCalculation(t *testing.T) {
|
||||
@@ -105,7 +133,7 @@ func TestWebDayCalculation(t *testing.T) {
|
||||
}
|
||||
|
||||
assertLine(t, resp.Lines, "= Summe", "15:30:00")
|
||||
assertLine(t, resp.Lines, "Minuszeit", "-00:16:00")
|
||||
assertLine(t, resp.Lines, "= Tagesaldo", "-00:16:00")
|
||||
}
|
||||
|
||||
func runCommand(t *testing.T, args ...string) string {
|
||||
|
||||
@@ -12,6 +12,7 @@ type webCalcRequest struct {
|
||||
Mode string `json:"mode"`
|
||||
Netto string `json:"netto"`
|
||||
Minus string `json:"minus"`
|
||||
Brutto string `json:"brutto"`
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
Pause string `json:"pause"`
|
||||
@@ -93,6 +94,8 @@ func calculateWeb(req webCalcRequest) (webCalcResponse, error) {
|
||||
switch mode {
|
||||
case "netto":
|
||||
return calculateWebNetto(req)
|
||||
case "brutto":
|
||||
return calculateWebBrutto(req)
|
||||
case "range":
|
||||
return calculateWebRange(req)
|
||||
case "days":
|
||||
@@ -121,6 +124,24 @@ func calculateWebNetto(req webCalcRequest) (webCalcResponse, error) {
|
||||
return webCalcResponse{Lines: lines}, nil
|
||||
}
|
||||
|
||||
func calculateWebBrutto(req webCalcRequest) (webCalcResponse, error) {
|
||||
brutto, err := parseDuration(req.Brutto)
|
||||
if err != nil {
|
||||
return webCalcResponse{}, fmt.Errorf("Brutto-Zeit: %w", err)
|
||||
}
|
||||
|
||||
pauseInput := req.Pause
|
||||
if strings.TrimSpace(pauseInput) == "" {
|
||||
pauseInput = "00:00"
|
||||
}
|
||||
pause, err := parseDuration(pauseInput)
|
||||
if err != nil {
|
||||
return webCalcResponse{}, fmt.Errorf("Pause: %w", err)
|
||||
}
|
||||
|
||||
return webBruttoNettoResponse(brutto, pause, req)
|
||||
}
|
||||
|
||||
func calculateWebRange(req webCalcRequest) (webCalcResponse, error) {
|
||||
start, err := parseClock(req.Start)
|
||||
if err != nil {
|
||||
@@ -140,23 +161,37 @@ func calculateWebRange(req webCalcRequest) (webCalcResponse, error) {
|
||||
return webCalcResponse{}, fmt.Errorf("Pause: %w", err)
|
||||
}
|
||||
|
||||
netto := workDuration(start, end, pause)
|
||||
if netto < 0 {
|
||||
return webCalcResponse{}, fmt.Errorf("Pause ist laenger als die Arbeitszeit")
|
||||
}
|
||||
|
||||
lines := []resultLine{
|
||||
{Label: "Start", Value: formatDuration(start)},
|
||||
{Label: "Ende", Value: formatDuration(end)},
|
||||
{Label: "- Pause", Value: formatDuration(pause)},
|
||||
{Label: "= Netto", Value: formatDuration(netto), Kind: "total"},
|
||||
}
|
||||
|
||||
brutto := grossDuration(start, end)
|
||||
resp, err := webBruttoNettoResponse(brutto, pause, req)
|
||||
if err != nil {
|
||||
return webCalcResponse{}, err
|
||||
}
|
||||
resp.Lines = append(lines, resp.Lines...)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func webBruttoNettoResponse(brutto int, pause int, req webCalcRequest) (webCalcResponse, error) {
|
||||
netto := brutto - pause
|
||||
if netto < 0 {
|
||||
return webCalcResponse{}, fmt.Errorf("Pause ist laenger als die Brutto-Zeit")
|
||||
}
|
||||
|
||||
lines := []resultLine{
|
||||
{Label: "Brutto-Zeit", Value: formatDuration(brutto)},
|
||||
{Label: "- Pause", Value: formatDuration(pause)},
|
||||
{Label: "= Netto-Zeit", Value: formatDuration(netto), Kind: "total"},
|
||||
}
|
||||
|
||||
var err error
|
||||
lines, err = appendSollBalance(lines, netto, req.Soll)
|
||||
if err != nil {
|
||||
return webCalcResponse{}, err
|
||||
}
|
||||
|
||||
if req.SetDefault && strings.TrimSpace(req.Soll) != "" {
|
||||
soll, err := parseDuration(req.Soll)
|
||||
if err != nil {
|
||||
@@ -241,12 +276,12 @@ func appendSollBalance(lines []resultLine, netto int, sollInput string) ([]resul
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
lines = append(lines, resultLine{Label: "Soll", Value: formatDuration(soll)})
|
||||
lines = append(lines, resultLine{Label: "- Sollzeit", Value: formatDuration(soll)})
|
||||
diff := netto - soll
|
||||
if diff >= 0 {
|
||||
return append(lines, resultLine{Label: "Ueberzeit", Value: "+" + formatDuration(diff), Kind: "positive"}), nil
|
||||
return append(lines, resultLine{Label: "= Tagesaldo", Value: "+" + formatDuration(diff), Kind: "positive"}), nil
|
||||
}
|
||||
return append(lines, resultLine{Label: "Minuszeit", Value: "-" + formatDuration(-diff), Kind: "negative"}), nil
|
||||
return append(lines, resultLine{Label: "= Tagesaldo", Value: "-" + formatDuration(-diff), Kind: "negative"}), nil
|
||||
}
|
||||
|
||||
const webPageHTML = `<!doctype html>
|
||||
@@ -310,7 +345,7 @@ h1 {
|
||||
}
|
||||
.tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
@@ -449,6 +484,7 @@ input:focus {
|
||||
<section class="panel">
|
||||
<nav class="tabs" aria-label="Rechenmodus">
|
||||
<button class="tab active" data-mode="range" type="button">Start / Ende</button>
|
||||
<button class="tab" data-mode="brutto" type="button">Brutto / Pause</button>
|
||||
<button class="tab" data-mode="netto" type="button">Netto + Minus</button>
|
||||
<button class="tab" data-mode="days" type="button">Mehrere Tage</button>
|
||||
</nav>
|
||||
@@ -462,6 +498,13 @@ input:focus {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mode-brutto" class="mode">
|
||||
<div class="grid">
|
||||
<label>Brutto-Zeit <input id="brutto" value="09:26:12" inputmode="numeric"></label>
|
||||
<label>Pause <input id="brutto-pause" value="00:30:00" inputmode="numeric"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mode-netto" class="mode">
|
||||
<div class="grid">
|
||||
<label>Netto-Zeit <input id="netto" value="06:01:14" inputmode="numeric"></label>
|
||||
@@ -537,9 +580,10 @@ function payload() {
|
||||
mode: activeMode,
|
||||
netto: document.querySelector("#netto").value,
|
||||
minus: document.querySelector("#minus").value,
|
||||
brutto: document.querySelector("#brutto").value,
|
||||
start: document.querySelector("#start").value,
|
||||
end: document.querySelector("#end").value,
|
||||
pause: document.querySelector("#pause").value,
|
||||
pause: activeMode === "brutto" ? document.querySelector("#brutto-pause").value : document.querySelector("#pause").value,
|
||||
soll: document.querySelector("#soll").value,
|
||||
setDefault: document.querySelector("#set-default").checked,
|
||||
days: dayPayload()
|
||||
|
||||
Reference in New Issue
Block a user