added empty days in /team view
This commit is contained in:
@@ -2,11 +2,13 @@ package helper
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns env with default fallback value.
|
// Returns env with default fallback value.
|
||||||
//
|
//
|
||||||
// Params:
|
// Params:
|
||||||
|
//
|
||||||
// key - enviroment var name
|
// key - enviroment var name
|
||||||
// fallback - default value
|
// fallback - default value
|
||||||
func GetEnv(key, fallback string) string {
|
func GetEnv(key, fallback string) string {
|
||||||
@@ -15,3 +17,37 @@ func GetEnv(key, fallback string) string {
|
|||||||
}
|
}
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CacheItem struct {
|
||||||
|
value any
|
||||||
|
expiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
data map[string]CacheItem
|
||||||
|
ttl time.Duration
|
||||||
|
fetch func(key string) (any, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCache(ttl time.Duration, fetchFunc func(key string) (any, error)) *Cache {
|
||||||
|
return &Cache{
|
||||||
|
data: make(map[string]CacheItem),
|
||||||
|
ttl: ttl,
|
||||||
|
fetch: fetchFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Get(key string) (any, error) {
|
||||||
|
if item, found := c.data[key]; found {
|
||||||
|
if time.Now().Before(item.expiration) {
|
||||||
|
return item.value, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := c.fetch(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.data[key] = CacheItem{value: value, expiration: time.Now().Add(c.ttl)}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -227,3 +227,7 @@ func (b *Booking) UpdateTime(newTime time.Time) {
|
|||||||
b.Update(newBooking)
|
b.Update(newBooking)
|
||||||
b.Save()
|
b.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Booking) ToString() string {
|
||||||
|
return fmt.Sprintf("Booking %d: at: %s, as type: %d", b.CounterId, b.Timestamp.Format("15:04"), b.CheckInOut)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
@@ -19,53 +20,53 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
|
|||||||
var workDays []WorkDay
|
var workDays []WorkDay
|
||||||
var workSec, pauseSec float64
|
var workSec, pauseSec float64
|
||||||
qStr, err := DB.Prepare(`
|
qStr, err := DB.Prepare(`
|
||||||
WITH ordered_bookings AS (
|
WITH all_days AS (
|
||||||
|
SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date
|
||||||
|
),
|
||||||
|
ordered_bookings AS (
|
||||||
|
SELECT
|
||||||
|
timestamp::DATE AS work_date,
|
||||||
|
timestamp,
|
||||||
|
check_in_out,
|
||||||
|
counter_id,
|
||||||
|
LAG(timestamp) OVER (PARTITION BY card_uid, timestamp::DATE ORDER BY timestamp) AS prev_timestamp,
|
||||||
|
LAG(check_in_out) OVER (PARTITION BY card_uid, timestamp::DATE ORDER BY timestamp) AS prev_check
|
||||||
|
FROM anwesenheit
|
||||||
|
WHERE card_uid = $1
|
||||||
|
AND timestamp::DATE >= $2
|
||||||
|
AND timestamp::DATE <= $3
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
timestamp::DATE AS work_date, -- Extract date for grouping
|
d.work_date,
|
||||||
timestamp,
|
COALESCE(MIN(b.timestamp), NOW()) AS time_from,
|
||||||
check_in_out,
|
COALESCE(MAX(b.timestamp), NOW()) AS time_to,
|
||||||
LAG(timestamp) OVER (
|
COALESCE(
|
||||||
PARTITION BY card_uid, timestamp::DATE -- Reset for each day
|
EXTRACT(EPOCH FROM SUM(
|
||||||
ORDER BY timestamp
|
CASE
|
||||||
) AS prev_timestamp,
|
WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 255)
|
||||||
LAG(check_in_out) OVER (
|
THEN b.timestamp - b.prev_timestamp
|
||||||
PARTITION BY card_uid, timestamp::DATE
|
ELSE INTERVAL '0'
|
||||||
ORDER BY timestamp
|
END
|
||||||
) AS prev_check
|
)), 0
|
||||||
FROM anwesenheit
|
) AS total_work_seconds,
|
||||||
WHERE card_uid = $1 -- Replace with actual card_uid
|
COALESCE(
|
||||||
AND timestamp::DATE >= $2 -- Set date range
|
EXTRACT(EPOCH FROM SUM(
|
||||||
AND timestamp::DATE <= $3
|
CASE
|
||||||
)
|
WHEN b.prev_check IN (2, 4, 255) AND b.check_in_out IN (1, 3)
|
||||||
SELECT
|
THEN b.timestamp - b.prev_timestamp
|
||||||
work_date,
|
ELSE INTERVAL '0'
|
||||||
MIN(timestamp) AS time_from,
|
END
|
||||||
MAX(timestamp) AS time_to,
|
)), 0
|
||||||
-- Total work time per day
|
) AS total_pause_seconds,
|
||||||
COALESCE(
|
COALESCE(jsonb_agg(jsonb_build_object(
|
||||||
EXTRACT(EPOCH FROM SUM(
|
'check_in_out', b.check_in_out,
|
||||||
CASE
|
'timestamp', b.timestamp,
|
||||||
WHEN prev_check IN (1, 3) AND check_in_out IN (2, 4, 254)
|
'counter_id', b.counter_id
|
||||||
THEN timestamp - prev_timestamp
|
) ORDER BY b.timestamp), '[]'::jsonb) AS bookings
|
||||||
ELSE INTERVAL '0'
|
FROM all_days d
|
||||||
END
|
LEFT JOIN ordered_bookings b ON d.work_date = b.work_date
|
||||||
)), 0
|
GROUP BY d.work_date
|
||||||
) AS total_work,
|
ORDER BY d.work_date;`)
|
||||||
|
|
||||||
-- Extract total pause time in seconds
|
|
||||||
COALESCE(
|
|
||||||
EXTRACT(EPOCH FROM SUM(
|
|
||||||
CASE
|
|
||||||
WHEN prev_check IN (2, 4, 254) AND check_in_out IN (1, 3)
|
|
||||||
THEN timestamp - prev_timestamp
|
|
||||||
ELSE INTERVAL '0'
|
|
||||||
END
|
|
||||||
)), 0
|
|
||||||
) AS total_pause
|
|
||||||
|
|
||||||
FROM ordered_bookings
|
|
||||||
GROUP BY work_date
|
|
||||||
ORDER BY work_date;`)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error preparing SQL statement", err)
|
log.Println("Error preparing SQL statement", err)
|
||||||
@@ -81,13 +82,20 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var workDay WorkDay
|
var workDay WorkDay
|
||||||
if err := rows.Scan(&workDay.Day, &workDay.TimeFrom, &workDay.TimeTo, &workSec, &pauseSec); err != nil {
|
var bookings []byte
|
||||||
|
if err := rows.Scan(&workDay.Day, &workDay.TimeFrom, &workDay.TimeTo, &workSec, &pauseSec, &bookings); err != nil {
|
||||||
log.Println("Error scanning row!", err)
|
log.Println("Error scanning row!", err)
|
||||||
return workDays
|
return workDays
|
||||||
}
|
}
|
||||||
workDay.workTime = time.Duration(workSec * float64(time.Second))
|
workDay.workTime = time.Duration(workSec * float64(time.Second))
|
||||||
workDay.pauseTime = time.Duration(pauseSec * float64(time.Second))
|
workDay.pauseTime = time.Duration(pauseSec * float64(time.Second))
|
||||||
workDay.calcPauseTime()
|
workDay.calcPauseTime()
|
||||||
|
|
||||||
|
err = json.Unmarshal(bookings, &workDay.Bookings)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error parsing bookings JSON!", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
workDays = append(workDays, workDay)
|
workDays = append(workDays, workDay)
|
||||||
}
|
}
|
||||||
if err = rows.Err(); err != nil {
|
if err = rows.Err(); err != nil {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ services:
|
|||||||
backend:
|
backend:
|
||||||
build: ../Backend
|
build: ../Backend
|
||||||
image: git.letsstein.de/tom/arbeitszeit-backend:0.0.1
|
image: git.letsstein.de/tom/arbeitszeit-backend:0.0.1
|
||||||
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Reference in New Issue
Block a user