dev/finalFixes #81
@@ -13,8 +13,10 @@ package models
|
|||||||
// the absence data is based on the entries in the "abwesenheit" database table
|
// the absence data is based on the entries in the "abwesenheit" database table
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -295,3 +297,24 @@ func (a *Absence) Delete() error {
|
|||||||
_, err = qStr.Exec(a.CounterId)
|
_, err = qStr.Exec(a.CounterId)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Absence) IsSubmittedAndAccepted() bool {
|
||||||
|
qStr, err := DB.Prepare(`SELECT bestaetigt from wochen_report WHERE $1 = ANY(abwesenheiten) AND $2 >= woche_start AND $2 < woche_start + INTERVAL '1 week';`) // @> array contains
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Error when preparing SQL Statement", "error", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer qStr.Close()
|
||||||
|
var isSubmittedAndChecked bool = false
|
||||||
|
|
||||||
|
err = qStr.QueryRow(a.CounterId, a.Date()).Scan(&isSubmittedAndChecked)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// No rows found ==> not even submitted
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Unexpected error when executing SQL Statement", "error", err)
|
||||||
|
}
|
||||||
|
return isSubmittedAndChecked
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,27 +95,6 @@ func (b *Booking) Verify() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Booking) IsSubmittedAndChecked() bool {
|
|
||||||
qStr, err := DB.Prepare(`SELECT bestaetigt from wochen_report WHERE $1 = ANY(anwesenheiten);`)
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn("Error when preparing SQL Statement", "error", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer qStr.Close()
|
|
||||||
var isSubmittedAndChecked bool = false
|
|
||||||
|
|
||||||
err = qStr.QueryRow(b.CounterId).Scan(&isSubmittedAndChecked)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// No rows found ==> not even submitted
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn("Unexpected error when executing SQL Statement", "error", err)
|
|
||||||
}
|
|
||||||
return isSubmittedAndChecked
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Booking) Insert() error {
|
func (b *Booking) Insert() error {
|
||||||
if !checkLastBooking(*b) {
|
if !checkLastBooking(*b) {
|
||||||
return SameBookingError{}
|
return SameBookingError{}
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ type CompoundDay struct {
|
|||||||
DayParts []IWorkDay
|
DayParts []IWorkDay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSubmittedAndAccepted implements IWorkDay.
|
||||||
|
func (c *CompoundDay) IsSubmittedAndAccepted() bool {
|
||||||
|
var isSubmittedAndAccepted = true
|
||||||
|
for _, day := range c.DayParts {
|
||||||
|
_isSubmittedAndAccepted := day.IsSubmittedAndAccepted()
|
||||||
|
isSubmittedAndAccepted = isSubmittedAndAccepted && _isSubmittedAndAccepted
|
||||||
|
slog.Info("Result from IsSubmittedCheck", "Result", _isSubmittedAndAccepted, "compount", day.ToString())
|
||||||
|
}
|
||||||
|
return isSubmittedAndAccepted
|
||||||
|
}
|
||||||
|
|
||||||
func NewCompondDay(date time.Time, dayParts ...IWorkDay) *CompoundDay {
|
func NewCompondDay(date time.Time, dayParts ...IWorkDay) *CompoundDay {
|
||||||
return &CompoundDay{Day: date, DayParts: dayParts}
|
return &CompoundDay{Day: date, DayParts: dayParts}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type IWorkDay interface {
|
|||||||
GetTimes(User, WorktimeBase, bool) (work, pause, overtime time.Duration)
|
GetTimes(User, WorktimeBase, bool) (work, pause, overtime time.Duration)
|
||||||
GetOvertime(User, WorktimeBase, bool) time.Duration
|
GetOvertime(User, WorktimeBase, bool) time.Duration
|
||||||
IsEmpty() bool
|
IsEmpty() bool
|
||||||
|
IsSubmittedAndAccepted() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type DayType int
|
type DayType int
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ type PublicHoliday struct {
|
|||||||
worktime int8
|
worktime int8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSubmittedAndAccepted implements IWorkDay.
|
||||||
|
func (p *PublicHoliday) IsSubmittedAndAccepted() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// IsEmpty implements [IWorkDay].
|
// IsEmpty implements [IWorkDay].
|
||||||
func (p *PublicHoliday) IsEmpty() bool {
|
func (p *PublicHoliday) IsEmpty() bool {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -292,10 +292,42 @@ func (u *User) GetNextWeek() WorkWeek {
|
|||||||
func (u *User) GetLastWorkWeekSubmission() time.Time {
|
func (u *User) GetLastWorkWeekSubmission() time.Time {
|
||||||
var lastSub time.Time
|
var lastSub time.Time
|
||||||
qStr, err := DB.Prepare(`
|
qStr, err := DB.Prepare(`
|
||||||
SELECT COALESCE(
|
SELECT new_week
|
||||||
(SELECT woche_start + INTERVAL '1 week' FROM wochen_report WHERE personal_nummer = $1 ORDER BY woche_start DESC LIMIT 1),
|
FROM (
|
||||||
(SELECT timestamp FROM anwesenheit WHERE card_uid = $2 ORDER BY timestamp LIMIT 1)
|
-- Highest priority
|
||||||
) AS letzte_buchung;
|
SELECT
|
||||||
|
woche_start AS new_week,
|
||||||
|
1 AS priority
|
||||||
|
FROM wochen_report
|
||||||
|
WHERE personal_nummer = $1
|
||||||
|
AND bestaetigt IS NULL
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Fallback if #1 returns nothing
|
||||||
|
SELECT
|
||||||
|
woche_start + INTERVAL '1 week' AS new_week,
|
||||||
|
2 AS priority
|
||||||
|
FROM wochen_report wo
|
||||||
|
WHERE personal_nummer = $1
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM wochen_report wi
|
||||||
|
WHERE wi.woche_start = wo.woche_start + INTERVAL '1 week'
|
||||||
|
AND wi.personal_nummer = wo.personal_nummer
|
||||||
|
)
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Final fallback
|
||||||
|
SELECT
|
||||||
|
timestamp AS new_week,
|
||||||
|
3 AS priority
|
||||||
|
FROM anwesenheit
|
||||||
|
WHERE card_uid = $2
|
||||||
|
) t
|
||||||
|
ORDER BY priority, new_week
|
||||||
|
LIMIT 1;
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("Error preparing query statement.", "error", err)
|
slog.Debug("Error preparing query statement.", "error", err)
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"arbeitszeitmessung/helper"
|
"arbeitszeitmessung/helper"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkDay struct {
|
type WorkDay struct {
|
||||||
@@ -425,3 +428,38 @@ func (d *WorkDay) GetDayProgress(u User) int8 {
|
|||||||
progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100
|
progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100
|
||||||
return int8(progress)
|
return int8(progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *WorkDay) IsSubmittedAndAccepted() bool {
|
||||||
|
var isKurzArbeitAccepted bool
|
||||||
|
if d.IsKurzArbeit() {
|
||||||
|
isKurzArbeitAccepted = d.kurzArbeitAbsence.IsSubmittedAndAccepted()
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsEmpty() {
|
||||||
|
return isKurzArbeitAccepted
|
||||||
|
}
|
||||||
|
|
||||||
|
qStr, err := DB.Prepare(`SELECT bestaetigt from wochen_report WHERE anwesenheiten @> $1 AND $2 >= woche_start AND $2 < woche_start + INTERVAL '1 week';`) // @> array contains
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Error when preparing SQL Statement", "error", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
defer qStr.Close()
|
||||||
|
var isSubmittedAndChecked bool = false
|
||||||
|
|
||||||
|
var bookingsIds []int
|
||||||
|
for _, booking := range d.Bookings {
|
||||||
|
bookingsIds = append(bookingsIds, booking.CounterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = qStr.QueryRow(pq.Array(bookingsIds), d.Date()).Scan(&isSubmittedAndChecked)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Unexpected error when executing SQL Statement", "error", err, "BookingsIds", bookingsIds)
|
||||||
|
}
|
||||||
|
return isSubmittedAndChecked
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type WeekStatus int8
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
WeekStatusNone WeekStatus = iota
|
WeekStatusNone WeekStatus = iota
|
||||||
|
WeekStatusCorrected
|
||||||
WeekStatusSent
|
WeekStatusSent
|
||||||
WeekStatusAccepted
|
WeekStatusAccepted
|
||||||
WeekStatusDifferences
|
WeekStatusDifferences
|
||||||
@@ -86,25 +87,31 @@ func (w *WorkWeek) CheckStatus() WeekStatus {
|
|||||||
log.Println("Cannot access Database!")
|
log.Println("Cannot access Database!")
|
||||||
return w.Status
|
return w.Status
|
||||||
}
|
}
|
||||||
qStr, err := DB.Prepare(`SELECT bestaetigt FROM wochen_report WHERE woche_start = $1::DATE AND personal_nummer = $2;`)
|
qStr, err := DB.Prepare(`SELECT bestaetigt, id FROM wochen_report WHERE woche_start = $1::DATE AND personal_nummer = $2;`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error preparing SQL statement", err)
|
log.Println("Error preparing SQL statement", err)
|
||||||
return w.Status
|
return w.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
defer qStr.Close()
|
defer qStr.Close()
|
||||||
var beastatigt bool
|
var beastatigt sql.NullBool
|
||||||
err = qStr.QueryRow(w.WeekStart, w.User.PersonalNummer).Scan(&beastatigt)
|
err = qStr.QueryRow(w.WeekStart, w.User.PersonalNummer).Scan(&beastatigt, &w.Id)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return w.Status
|
return w.Status
|
||||||
}
|
}
|
||||||
|
slog.Info("Bestätigt query res", "Best", beastatigt, "week", w.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error querying database", err)
|
log.Println("Error querying database", err)
|
||||||
return w.Status
|
return w.Status
|
||||||
}
|
}
|
||||||
if beastatigt {
|
switch {
|
||||||
|
case beastatigt.Bool:
|
||||||
w.Status = WeekStatusAccepted
|
w.Status = WeekStatusAccepted
|
||||||
} else {
|
case beastatigt.Valid:
|
||||||
w.Status = WeekStatusSent
|
w.Status = WeekStatusSent
|
||||||
|
default:
|
||||||
|
w.Status = WeekStatusCorrected
|
||||||
|
|
||||||
}
|
}
|
||||||
return w.Status
|
return w.Status
|
||||||
}
|
}
|
||||||
@@ -206,23 +213,33 @@ func (w *WorkWeek) SendWeek() error {
|
|||||||
return ErrRunningWeek
|
return ErrRunningWeek
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.CheckStatus() != WeekStatusNone {
|
switch w.CheckStatus() {
|
||||||
qStr, err = DB.Prepare(`UPDATE "wochen_report" SET bestaetigt = FALSE, arbeitszeit = make_interval(secs => $3::numeric / 1000000000), ueberstunden = make_interval(secs => $4::numeric / 1000000000), anwesenheiten=$5, abwesenheiten=$6 WHERE personal_nummer = $1 AND woche_start = $2;`)
|
case WeekStatusNone:
|
||||||
if err != nil {
|
|
||||||
slog.Warn("Error preparing SQL statement", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qStr, err = DB.Prepare(`INSERT INTO wochen_report (personal_nummer, woche_start, arbeitszeit, ueberstunden, anwesenheiten, abwesenheiten) VALUES ($1, $2, make_interval(secs => $3::numeric / 1000000000), make_interval(secs => $4::numeric / 1000000000), $5, $6);`)
|
qStr, err = DB.Prepare(`INSERT INTO wochen_report (personal_nummer, woche_start, arbeitszeit, ueberstunden, anwesenheiten, abwesenheiten) VALUES ($1, $2, make_interval(secs => $3::numeric / 1000000000), make_interval(secs => $4::numeric / 1000000000), $5, $6);`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Error preparing SQL statement", "error", err)
|
slog.Warn("Error preparing SQL statement", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WeekStatusCorrected:
|
||||||
|
qStr, err = DB.Prepare(`UPDATE "wochen_report" SET bestaetigt = FALSE, arbeitszeit = make_interval(secs => $3::numeric / 1000000000), ueberstunden = make_interval(secs => $4::numeric / 1000000000), anwesenheiten=$5, abwesenheiten=$6 WHERE personal_nummer = $1 AND woche_start = $2;`)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Error preparing SQL statement", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case WeekStatusSent, WeekStatusAccepted:
|
||||||
|
qStr, err = DB.Prepare(`UPDATE "wochen_report" SET bestaetigt = null WHERE personal_nummer = $1 AND woche_start = $2 AND ($3::numeric IS NULL OR TRUE) AND ($4::numeric IS NULL OR TRUE) AND ($5::int[] IS NULL OR TRUE) AND ($6::int[] IS NULL OR TRUE);`)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Error preparing SQL statement", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = qStr.Exec(w.User.PersonalNummer, w.WeekStart, int64(w.Worktime), int64(w.Overtime), pq.Array(anwBookings), pq.Array(awBookings))
|
_, err = qStr.Exec(w.User.PersonalNummer, w.WeekStart, int64(w.Worktime), int64(w.Overtime), pq.Array(anwBookings), pq.Array(awBookings))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error executing query!", err)
|
slog.Error("Error executing query!", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
--color-neutral-300: oklch(87% 0 0);
|
--color-neutral-300: oklch(87% 0 0);
|
||||||
--color-neutral-400: oklch(70.8% 0 0);
|
--color-neutral-400: oklch(70.8% 0 0);
|
||||||
--color-neutral-500: oklch(55.6% 0 0);
|
--color-neutral-500: oklch(55.6% 0 0);
|
||||||
--color-neutral-600: oklch(43.9% 0 0);
|
|
||||||
--color-neutral-700: oklch(37.1% 0 0);
|
--color-neutral-700: oklch(37.1% 0 0);
|
||||||
--color-neutral-800: oklch(26.9% 0 0);
|
--color-neutral-800: oklch(26.9% 0 0);
|
||||||
--color-black: #000;
|
--color-black: #000;
|
||||||
@@ -30,8 +29,6 @@
|
|||||||
--text-sm--line-height: calc(1.25 / 0.875);
|
--text-sm--line-height: calc(1.25 / 0.875);
|
||||||
--text-xl: 1.25rem;
|
--text-xl: 1.25rem;
|
||||||
--text-xl--line-height: calc(1.75 / 1.25);
|
--text-xl--line-height: calc(1.75 / 1.25);
|
||||||
--text-2xl: 1.5rem;
|
|
||||||
--text-2xl--line-height: calc(2 / 1.5);
|
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
--radius-md: 0.375rem;
|
--radius-md: 0.375rem;
|
||||||
--default-transition-duration: 150ms;
|
--default-transition-duration: 150ms;
|
||||||
@@ -253,6 +250,12 @@
|
|||||||
.-my-1 {
|
.-my-1 {
|
||||||
margin-block: calc(var(--spacing) * -1);
|
margin-block: calc(var(--spacing) * -1);
|
||||||
}
|
}
|
||||||
|
.my-2 {
|
||||||
|
margin-block: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
|
.my-4 {
|
||||||
|
margin-block: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
.mt-1 {
|
.mt-1 {
|
||||||
margin-top: calc(var(--spacing) * 1);
|
margin-top: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
@@ -320,6 +323,32 @@
|
|||||||
mask-size: 100% 100%;
|
mask-size: 100% 100%;
|
||||||
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M7.616 20q-.672 0-1.144-.472T6 18.385V6H5V5h4v-.77h6V5h4v1h-1v12.385q0 .69-.462 1.153T16.384 20zM17 6H7v12.385q0 .269.173.442t.443.173h8.769q.23 0 .423-.192t.192-.424zM9.808 17h1V8h-1zm3.384 0h1V8h-1zM7 6v13z'/%3E%3C/svg%3E");
|
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M7.616 20q-.672 0-1.144-.472T6 18.385V6H5V5h4v-.77h6V5h4v1h-1v12.385q0 .69-.462 1.153T16.384 20zM17 6H7v12.385q0 .269.173.442t.443.173h8.769q.23 0 .423-.192t.192-.424zM9.808 17h1V8h-1zm3.384 0h1V8h-1zM7 6v13z'/%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
.icon-\[material-symbols-light--edit-calendar-rounded\] {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
background-color: currentColor;
|
||||||
|
-webkit-mask-image: var(--svg);
|
||||||
|
mask-image: var(--svg);
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
-webkit-mask-size: 100% 100%;
|
||||||
|
mask-size: 100% 100%;
|
||||||
|
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M5.616 21q-.691 0-1.153-.462T4 19.385V6.615q0-.69.463-1.152T5.616 5h1.769V3.308q0-.23.155-.384q.156-.155.386-.155t.383.155t.153.384V5h7.154V3.27q0-.213.143-.357q.144-.144.357-.144t.356.144t.144.356V5h1.769q.69 0 1.153.463T20 6.616v4.601q0 .213-.144.356t-.357.144t-.356-.144t-.143-.356v-.602H5v8.77q0 .23.192.423t.423.192h5.731q.213 0 .357.144t.143.357t-.143.356t-.357.143zm8.615-.808V19.12q0-.153.056-.296q.055-.144.186-.275l5.09-5.065q.149-.13.306-.19t.315-.062q.172 0 .338.064q.166.065.301.194l.925.944q.123.148.188.308q.064.159.064.319t-.052.322t-.2.31l-5.065 5.066q-.131.13-.275.186q-.143.056-.297.056h-1.073q-.343 0-.575-.232t-.232-.576m5.96-4.177l.925-.956l-.925-.944l-.95.95z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
.icon-\[material-symbols-light--lock\] {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
background-color: currentColor;
|
||||||
|
-webkit-mask-image: var(--svg);
|
||||||
|
mask-image: var(--svg);
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
-webkit-mask-size: 100% 100%;
|
||||||
|
mask-size: 100% 100%;
|
||||||
|
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M6.616 21q-.667 0-1.141-.475T5 19.386v-8.77q0-.666.475-1.14T6.615 9H8V7q0-1.671 1.165-2.835Q10.329 3 12 3t2.836 1.165T16 7v2h1.385q.666 0 1.14.475t.475 1.14v8.77q0 .666-.475 1.14t-1.14.475zM12 16.5q.633 0 1.066-.434q.434-.433.434-1.066t-.434-1.066T12 13.5t-1.066.434Q10.5 14.367 10.5 15t.434 1.066q.433.434 1.066.434M9 9h6V7q0-1.25-.875-2.125T12 4t-2.125.875T9 7z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
.icon-\[material-symbols-light--more-time\] {
|
.icon-\[material-symbols-light--more-time\] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1.25em;
|
width: 1.25em;
|
||||||
@@ -395,6 +424,18 @@
|
|||||||
width: calc(var(--spacing) * 5);
|
width: calc(var(--spacing) * 5);
|
||||||
height: calc(var(--spacing) * 5);
|
height: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
|
.size-6 {
|
||||||
|
width: calc(var(--spacing) * 6);
|
||||||
|
height: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
|
.size-8 {
|
||||||
|
width: calc(var(--spacing) * 8);
|
||||||
|
height: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
|
.size-10 {
|
||||||
|
width: calc(var(--spacing) * 10);
|
||||||
|
height: calc(var(--spacing) * 10);
|
||||||
|
}
|
||||||
.h-2 {
|
.h-2 {
|
||||||
height: calc(var(--spacing) * 2);
|
height: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -633,6 +674,9 @@
|
|||||||
.p-2 {
|
.p-2 {
|
||||||
padding: calc(var(--spacing) * 2);
|
padding: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
|
.px-2 {
|
||||||
|
padding-inline: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
.px-3 {
|
.px-3 {
|
||||||
padding-inline: calc(var(--spacing) * 3);
|
padding-inline: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
@@ -660,9 +704,6 @@
|
|||||||
.whitespace-nowrap {
|
.whitespace-nowrap {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.\!text-red-500 {
|
|
||||||
color: var(--color-red-500) !important;
|
|
||||||
}
|
|
||||||
.text-accent {
|
.text-accent {
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
|
|||||||
<div class="grid grid-cols-5 gap-2 lg:grid-cols-1">
|
<div class="grid grid-cols-5 gap-2 lg:grid-cols-1">
|
||||||
if !onlyAccept {
|
if !onlyAccept {
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
|
if week.CheckStatus() == models.WeekStatusCorrected {
|
||||||
|
<span class="flex flex-row gap-2 items-center">
|
||||||
|
<div class="icon-[material-symbols-light--edit-calendar-rounded]"></div>
|
||||||
|
laufende Korrektur
|
||||||
|
</span>
|
||||||
|
}
|
||||||
<span class="flex flex-row gap-2 items-center">
|
<span class="flex flex-row gap-2 items-center">
|
||||||
@statusCheckMark(week.CheckStatus(), models.WeekStatusSent)
|
@statusCheckMark(week.CheckStatus(), models.WeekStatusSent)
|
||||||
Gesendet
|
Gesendet
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ changeButtonComponent(id string, workDay bool) {
|
templ changeButtonComponent(id string, workDay bool, disabled bool) {
|
||||||
|
if disabled {
|
||||||
|
<button class="h-10 change-button-component btn w-auto group/button" type="button" disabled>
|
||||||
|
<p class="hidden md:block group-[.edit]/button:hidden">Ändern</p>
|
||||||
|
</button>
|
||||||
|
} else {
|
||||||
<button class="h-10 change-button-component btn w-auto group/button" type="button" onclick={ templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), id, workDay) }>
|
<button class="h-10 change-button-component btn w-auto group/button" type="button" onclick={ templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), id, workDay) }>
|
||||||
<p class="hidden md:block group-[.edit]/button:hidden">Ändern</p>
|
<p class="hidden md:block group-[.edit]/button:hidden">Ändern</p>
|
||||||
<p class="hidden group-[.edit]/button:md:block">Speichern</p>
|
<p class="hidden group-[.edit]/button:md:block">Speichern</p>
|
||||||
@@ -16,6 +21,7 @@ templ changeButtonComponent(id string, workDay bool) {
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="h-10 hidden group-[.edit]:flex btn basis-[content] items-center" onclick={ templ.JSFuncCall("clearEditState") }><span class="size-5 icon-[material-symbols-light--cancel-outline]"></span></button>
|
<button class="h-10 hidden group-[.edit]:flex btn basis-[content] items-center" onclick={ templ.JSFuncCall("clearEditState") }><span class="size-5 icon-[material-symbols-light--cancel-outline]"></span></button>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ newAbsenceComponent() {
|
templ newAbsenceComponent() {
|
||||||
@@ -85,9 +91,6 @@ templ bookingComponent(booking models.Booking) {
|
|||||||
fehlerhafte Buchung, wird nicht zur Berechnung verwendet!
|
fehlerhafte Buchung, wird nicht zur Berechnung verwendet!
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
if booking.IsSubmittedAndChecked() {
|
|
||||||
<p>submitted</p>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,8 +142,11 @@ templ defaultDayComponent(day models.IWorkDay) {
|
|||||||
<input type="hidden" name="action" value="change"/> <!-- default action value for ändern button -->
|
<input type="hidden" name="action" value="change"/> <!-- default action value for ändern button -->
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-cell flex flex-row gap-2 items-end">
|
<div class="grid-cell flex flex-row gap-2 items-end ">
|
||||||
@changeButtonComponent("time-"+day.Date().Format(time.DateOnly), true)
|
@changeButtonComponent("time-"+day.Date().Format(time.DateOnly), true, day.IsSubmittedAndAccepted())
|
||||||
|
if day.IsSubmittedAndAccepted() {
|
||||||
|
<span class="size-6 my-2 icon-[material-symbols-light--lock]"></span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e # Exit on error
|
|
||||||
|
|
||||||
echo "Creating PostgreSQL user and setting permissions... $POSTGRES_USER for API user $POSTGRES_API_USER"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
|
||||||
CREATE ROLE migrate LOGIN ENCRYPTED PASSWORD '$POSTGRES_PASSWORD';
|
|
||||||
GRANT USAGE, CREATE ON SCHEMA public TO migrate;
|
|
||||||
GRANT CONNECT ON DATABASE arbeitszeitmessung TO migrate;
|
|
||||||
EOSQL
|
|
||||||
|
|
||||||
# psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
|
||||||
|
|
||||||
# GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, user_password, wochen_report, s_feiertage TO $POSTGRES_API_USER;
|
|
||||||
# GRANT DELETE ON abwesenheit TO $POSTGRES_API_USER;
|
|
||||||
# GRANT SELECT ON s_personal_daten, s_abwesenheit_typen, s_anwesenheit_typen, s_feiertage TO $POSTGRES_API_USER;
|
|
||||||
# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER;
|
|
||||||
# EOSQL
|
|
||||||
|
|
||||||
echo "User creation and permissions setup complete!"
|
|
||||||
|
|
||||||
|
|
||||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
|
||||||
|
|
||||||
-- privilege roles
|
|
||||||
DO \$\$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'app_base') THEN
|
|
||||||
CREATE ROLE app_base NOLOGIN;
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
\$\$;
|
|
||||||
|
|
||||||
-- dynamic login role
|
|
||||||
DO \$\$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '$POSTGRES_API_USER') THEN
|
|
||||||
CREATE ROLE $POSTGRES_API_USER
|
|
||||||
LOGIN
|
|
||||||
ENCRYPTED PASSWORD '$POSTGRES_API_PASS';
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
\$\$;
|
|
||||||
|
|
||||||
-- grant base privileges
|
|
||||||
GRANT app_base TO $POSTGRES_API_USER;
|
|
||||||
GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER;
|
|
||||||
GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER;
|
|
||||||
|
|
||||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
||||||
|
|
||||||
EOSQL
|
|
||||||
|
|
||||||
# psql -v ON_ERROR_STOP=1 --username root --dbname arbeitszeitmessung
|
|
||||||
Reference in New Issue
Block a user