implement Gehaltsaufstellung dyflexis data access

This commit is contained in:
Marcel Arndt
2025-11-18 13:32:36 +01:00
parent 4abe9e44d8
commit 4cf79f3094
18 changed files with 1015 additions and 1107 deletions
@@ -215,11 +215,11 @@ type Anomaly struct {
}
type AttendanceRegistration struct {
AttendanceRegistrationId int32 `json:"attendanceRegistrationId"`
EmployeeId int32 `json:"employeeId"`
PersonnelNumber string `json:"personnelNumber"`
DateTime time.Time `json:"dateTime"`
Event string `json:"event"`
AttendanceRegistrationId int32 `json:"attendanceRegistrationId"`
EmployeeId int32 `json:"employeeId"`
PersonnelNumber pgtype.Text `json:"personnelNumber"`
DateTime time.Time `json:"dateTime"`
Event string `json:"event"`
}
type AttendanceRegistrationSource struct {
@@ -239,6 +239,7 @@ type AttendanceRegistrationStaging struct {
EmployeeId int32 `json:"employeeId"`
DateTime time.Time `json:"dateTime"`
Event string `json:"event"`
PersonnelNumber pgtype.Text `json:"personnelNumber"`
}
type AvicennaTenant struct {
@@ -337,6 +338,14 @@ type EmployeeDyflexisStaging struct {
ContractTypeName pgtype.Text `json:"contractTypeName"`
}
type Holiday struct {
ID pgtype.UUID `json:"id"`
Date time.Time `json:"date"`
Locked bool `json:"locked"`
ExcludedFromPayroll bool `json:"excludedFromPayroll"`
EmployeeId int32 `json:"employeeId"`
}
type Patient struct {
ID string `json:"id"`
Surname pgtype.Text `json:"surname"`
@@ -443,6 +452,17 @@ type PrismaMigration struct {
AppliedStepsCount int32 `json:"applied_steps_count"`
}
type Sickday struct {
ID pgtype.UUID `json:"id"`
Start time.Time `json:"start"`
End time.Time `json:"end"`
Hours float64 `json:"hours"`
Days int32 `json:"days"`
Note pgtype.Text `json:"note"`
Locked bool `json:"locked"`
EmployeeId int32 `json:"employeeId"`
}
type SolutionTarget struct {
ID pgtype.UUID `json:"id"`
Name string `json:"name"`
@@ -539,6 +559,9 @@ type Tour struct {
DeletedAt *time.Time `json:"deletedAt"`
TicketId pgtype.UUID `json:"ticketId"`
UpdatedAt *time.Time `json:"updatedAt"`
CompanyName pgtype.Text `json:"companyName"`
OverRTW bool `json:"overRTW"`
Schwerlast bool `json:"schwerlast"`
}
type TourSource struct {
@@ -610,6 +633,9 @@ type TourSource struct {
Fahrer pgtype.Text `json:"fahrer"`
Color pgtype.Text `json:"color"`
LastUpdate pgtype.Text `json:"lastUpdate"`
CompanyName pgtype.Text `json:"companyName"`
OverRTW pgtype.Text `json:"overRTW"`
Schwerlast pgtype.Text `json:"schwerlast"`
}
type TourStaging struct {
@@ -663,6 +689,9 @@ type TourStaging struct {
ConsumptionCosts float64 `json:"consumptionCosts"`
CreatedAt *time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt"`
CompanyName pgtype.Text `json:"companyName"`
OverRTW bool `json:"overRTW"`
Schwerlast bool `json:"schwerlast"`
}
type Trace struct {
@@ -74,11 +74,16 @@ HighActivityDays AS (
GREATEST(0, tour_amount - 6) AS tourengeld
FROM
DailyCounts
),
TourenGeld AS (
SELECT
SUM(tourengeld)::float as tourengeld
FROM
HighActivityDays
)
SELECT
SUM(tourengeld)::float as tourengeld
FROM
HighActivityDays
COALESCE(tourengeld, 0.0)
FROM TourenGeld
`
type CountTourengeldAmountInPeriodParams struct {
@@ -94,99 +99,176 @@ func (q *Queries) CountTourengeldAmountInPeriod(ctx context.Context, arg CountTo
return tourengeld, err
}
const fetchDrivenToursInInterval = `-- name: FetchDrivenToursInInterval :many
SELECT id, done, direction, "carName", "operationId", "driverId", "driverName", "codriverId", "codriverName", "patientId", "patientName", "patientSurname", "patientStreet", "patientZip", "patientCity", "healthInsurance", "healthInsuranceNumber", type, category, "transportType", "ordinanceType", "rangeEndDate", "startInstitution", "startStreet", "startZip", "startCity", "targetInstitution", "targetStreet", "targetZip", "targetCity", "startDate", "check", "occupiedKm", "totalKm", "startBegin", "startEnd", target, "targetBegin", empty, "hasInfection", "infectionName", "revenueDispoLive", "billDate", "billNumber", revenue, "consumptionCosts", "revenueDeviation", "createdAt", "deletedAt", "ticketId", "updatedAt" FROM "Tour"
WHERE "check" <> 3
AND "deletedAt" IS NULL
AND (
"patientId" IS NULL
OR "patientId" NOT IN (
'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
'3554e1a9-9c07-45e5-b53c-692a77e4fa9a'
)
)
AND (
"carName" IS NULL
OR "carName" NOT IN (
'Storno',
'Abgabe',
'Löschen'
)
)
AND "startDate" >= $1
AND "startDate" <= $2
AND ("driverId" = $3 OR "codriverId" = $3)
ORDER BY "startDate" DESC
const countVerpflegungspauschale = `-- name: CountVerpflegungspauschale :one
SELECT
count(*)
FROM "WorkTime"
WHERE
duration > 480
AND "startDate" BETWEEN $1 AND $2
AND "userId"::int = $3::bigint
`
type FetchDrivenToursInIntervalParams struct {
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
EmployeeID pgtype.Text `json:"employee_id"`
type CountVerpflegungspauschaleParams struct {
StartDate *time.Time `json:"start_date"`
EndDate *time.Time `json:"end_date"`
EmployeeID int64 `json:"employee_id"`
}
// ID's for Pause and Feierabend respectively
func (q *Queries) FetchDrivenToursInInterval(ctx context.Context, arg FetchDrivenToursInIntervalParams) ([]Tour, error) {
rows, err := q.db.Query(ctx, fetchDrivenToursInInterval, arg.StartDate, arg.EndDate, arg.EmployeeID)
func (q *Queries) CountVerpflegungspauschale(ctx context.Context, arg CountVerpflegungspauschaleParams) (int64, error) {
row := q.db.QueryRow(ctx, countVerpflegungspauschale, arg.StartDate, arg.EndDate, arg.EmployeeID)
var count int64
err := row.Scan(&count)
return count, err
}
const evaluatePunctualityForEmployee = `-- name: EvaluatePunctualityForEmployee :one
WITH
planned_start_times AS (
SELECT
DISTINCT ON ("startDate"::DATE)
"startDate"::DATE AS plan_day,
"startDate" AS planned_start_time
FROM "PlannedTime" as p
WHERE
p."userId"::int = $1::bigint
AND p."startDate" BETWEEN $2 AND $3
AND p."startDate" IS NOT NULL
AND p.department <> 'Fortbildung'
AND p.note IS NULL
ORDER BY
"startDate"::DATE, "startDate" ASC
),
actual_start_times AS (
SELECT
DISTINCT ON ("startDate"::DATE)
"startDate"::DATE AS work_day,
"startDate" AS actual_start_time
FROM "WorkTime"
WHERE
"userId"::int = $1::bigint
AND "startDate" BETWEEN $2 AND $3
AND "startDate" IS NOT NULL
ORDER BY
"startDate"::DATE, "startDate" ASC
),
daily_comparison AS (
SELECT
p.plan_day,
p.planned_start_time,
a.actual_start_time,
(a.actual_start_time IS NOT NULL AND a.actual_start_time <= p.planned_start_time) AS was_punctual
FROM planned_start_times p
LEFT JOIN actual_start_times a ON p.plan_day = a.work_day
)
SELECT
COUNT(CASE WHEN was_punctual THEN 1 END) AS punctual_days
FROM daily_comparison
`
type EvaluatePunctualityForEmployeeParams struct {
EmployeeID int64 `json:"employee_id"`
StartDate *time.Time `json:"start_date"`
EndDate *time.Time `json:"end_date"`
}
func (q *Queries) EvaluatePunctualityForEmployee(ctx context.Context, arg EvaluatePunctualityForEmployeeParams) (int64, error) {
row := q.db.QueryRow(ctx, evaluatePunctualityForEmployee, arg.EmployeeID, arg.StartDate, arg.EndDate)
var punctual_days int64
err := row.Scan(&punctual_days)
return punctual_days, err
}
const getWeeklyTimeSummaryForEmployee = `-- name: GetWeeklyTimeSummaryForEmployee :many
WITH all_time_entries AS (
SELECT
DATE_TRUNC('week', "startDate") AS week_start,
SUM("duration" / 60.0) as worked_hours,
count(DISTINCT DATE_TRUNC('day', "startDate"))::float as worked_days,
0::float AS sick_hours,
0::float AS holiday_days
FROM "WorkTime"
WHERE
"startDate" BETWEEN $1 AND $2
AND "userId"::int = $3
AND "startDate" IS NOT NULL
GROUP BY
DATE_TRUNC('week', "startDate")
UNION ALL
SELECT
DATE_TRUNC('week', "start") AS week_start,
0::float as worked_hours,
0::float as worked_days,
SUM("hours") AS sick_hours,
0::float AS holiday_days
FROM "Sickdays"
WHERE
"start" BETWEEN $1 AND $2
AND "employeeId" = $3
AND "start" IS NOT NULL
GROUP BY
DATE_TRUNC('week', "start")
UNION ALL
SELECT
DATE_TRUNC('week', "date") AS week_start,
0::float as worked_hours,
0::float as worked_days,
0::float AS sick_hours,
count(*)::float AS holiday_days
FROM "Holiday"
WHERE
"date" BETWEEN $1 AND $2
AND "employeeId" = $3
AND "date" IS NOT NULL
GROUP BY
DATE_TRUNC('week', "date")
)
SELECT
week_start::timestamp,
SUM(worked_hours)::float AS total_worked_hours,
SUM(worked_days)::float AS total_worked_days,
SUM(sick_hours)::float AS total_sick_hours,
SUM(holiday_days)::float AS total_holiday_days
FROM all_time_entries
GROUP BY
week_start
ORDER BY
week_start
`
type GetWeeklyTimeSummaryForEmployeeParams struct {
StartDate *time.Time `json:"start_date"`
EndDate *time.Time `json:"end_date"`
EmployeeID string `json:"employee_id"`
}
type GetWeeklyTimeSummaryForEmployeeRow struct {
WeekStart time.Time `json:"week_start"`
TotalWorkedHours float64 `json:"total_worked_hours"`
TotalWorkedDays float64 `json:"total_worked_days"`
TotalSickHours float64 `json:"total_sick_hours"`
TotalHolidayDays float64 `json:"total_holiday_days"`
}
func (q *Queries) GetWeeklyTimeSummaryForEmployee(ctx context.Context, arg GetWeeklyTimeSummaryForEmployeeParams) ([]GetWeeklyTimeSummaryForEmployeeRow, error) {
rows, err := q.db.Query(ctx, getWeeklyTimeSummaryForEmployee, arg.StartDate, arg.EndDate, arg.EmployeeID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Tour
var items []GetWeeklyTimeSummaryForEmployeeRow
for rows.Next() {
var i Tour
var i GetWeeklyTimeSummaryForEmployeeRow
if err := rows.Scan(
&i.ID,
&i.Done,
&i.Direction,
&i.CarName,
&i.OperationId,
&i.DriverId,
&i.DriverName,
&i.CodriverId,
&i.CodriverName,
&i.PatientId,
&i.PatientName,
&i.PatientSurname,
&i.PatientStreet,
&i.PatientZip,
&i.PatientCity,
&i.HealthInsurance,
&i.HealthInsuranceNumber,
&i.Type,
&i.Category,
&i.TransportType,
&i.OrdinanceType,
&i.RangeEndDate,
&i.StartInstitution,
&i.StartStreet,
&i.StartZip,
&i.StartCity,
&i.TargetInstitution,
&i.TargetStreet,
&i.TargetZip,
&i.TargetCity,
&i.StartDate,
&i.Check,
&i.OccupiedKm,
&i.TotalKm,
&i.StartBegin,
&i.StartEnd,
&i.Target,
&i.TargetBegin,
&i.Empty,
&i.HasInfection,
&i.InfectionName,
&i.RevenueDispoLive,
&i.BillDate,
&i.BillNumber,
&i.Revenue,
&i.ConsumptionCosts,
&i.RevenueDeviation,
&i.CreatedAt,
&i.DeletedAt,
&i.TicketId,
&i.UpdatedAt,
&i.WeekStart,
&i.TotalWorkedHours,
&i.TotalWorkedDays,
&i.TotalSickHours,
&i.TotalHolidayDays,
); err != nil {
return nil, err
}
@@ -198,132 +280,33 @@ func (q *Queries) FetchDrivenToursInInterval(ctx context.Context, arg FetchDrive
return items, nil
}
const getEmployeeByName = `-- name: GetEmployeeByName :one
SELECT
dy.id as dyflexis_id,
dl.id as dispolive_id,
dy."contractSalaryHour"
FROM "DyflexisEmployee" as dy
INNER JOIN
"Employee" as dl ON dy.id::text = dl."personnelNumber"
WHERE
TRIM(dy.firstname) = $1 AND
TRIM(dy.surname) = $2 AND
"contractStart" < CURRENT_DATE AND
"contractEnd" > (CURRENT_DATE - INTERVAL '2 months')
ORDER BY dy."contractEnd" DESC
const isEmployeesDepartmentKTW = `-- name: IsEmployeesDepartmentKTW :one
SELECT COALESCE(
(
bool_or(department LIKE '%KTW%')
AND
bool_and(department <> 'Leitstelle')
),
false
)::bool as meets_requirements
FROM "PlannedTime"
WHERE
"userId"::int = $1::bigint
AND "startDate" BETWEEN $2 AND $3
AND department IS NOT NULL
`
type GetEmployeeByNameParams struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
type GetEmployeeByNameRow struct {
DyflexisID int32 `json:"dyflexis_id"`
DispoliveID string `json:"dispolive_id"`
ContractSalaryHour float64 `json:"contractSalaryHour"`
}
func (q *Queries) GetEmployeeByName(ctx context.Context, arg GetEmployeeByNameParams) (GetEmployeeByNameRow, error) {
row := q.db.QueryRow(ctx, getEmployeeByName, arg.Firstname, arg.Lastname)
var i GetEmployeeByNameRow
err := row.Scan(&i.DyflexisID, &i.DispoliveID, &i.ContractSalaryHour)
return i, err
}
const getEmployeeDyflexis = `-- name: GetEmployeeDyflexis :one
SELECT id, firstname, surname, "contractStart", "contractEnd", "contractHoursWeek", "contractSalaryHour", "contractTypeName", "internalId" FROM "DyflexisEmployee"
WHERE
id = $1 AND
"contractStart" < CURRENT_DATE AND
"contractEnd" > (CURRENT_DATE - INTERVAL '2 months')
LIMIT 1
`
func (q *Queries) GetEmployeeDyflexis(ctx context.Context, id int32) (DyflexisEmployee, error) {
row := q.db.QueryRow(ctx, getEmployeeDyflexis, id)
var i DyflexisEmployee
err := row.Scan(
&i.ID,
&i.Firstname,
&i.Surname,
&i.ContractStart,
&i.ContractEnd,
&i.ContractHoursWeek,
&i.ContractSalaryHour,
&i.ContractTypeName,
&i.InternalId,
)
return i, err
}
const getSicknessSummaryForEmployeeInPeriod = `-- name: GetSicknessSummaryForEmployeeInPeriod :one
WITH pt AS (
SELECT
COUNT(*) as days,
COALESCE(sum(duration), 0)::bigint as minutes
FROM "PlannedTime"
WHERE "userId" = $1 AND "startDate" > $2 AND "startDate" < $3 AND note = 'sick'
)
SELECT
pt.days,
(minutes / 60)::float as hours,
pt.minutes
FROM pt
`
type GetSicknessSummaryForEmployeeInPeriodParams struct {
EmployeeID string `json:"employee_id"`
type IsEmployeesDepartmentKTWParams struct {
EmployeeID int64 `json:"employee_id"`
StartDate *time.Time `json:"start_date"`
EndDate *time.Time `json:"end_date"`
}
type GetSicknessSummaryForEmployeeInPeriodRow struct {
Days int64 `json:"days"`
Hours float64 `json:"hours"`
Minutes int64 `json:"minutes"`
}
func (q *Queries) GetSicknessSummaryForEmployeeInPeriod(ctx context.Context, arg GetSicknessSummaryForEmployeeInPeriodParams) (GetSicknessSummaryForEmployeeInPeriodRow, error) {
row := q.db.QueryRow(ctx, getSicknessSummaryForEmployeeInPeriod, arg.EmployeeID, arg.StartDate, arg.EndDate)
var i GetSicknessSummaryForEmployeeInPeriodRow
err := row.Scan(&i.Days, &i.Hours, &i.Minutes)
return i, err
}
const getWorkSummaryForEmployeeInPeriod = `-- name: GetWorkSummaryForEmployeeInPeriod :one
WITH wt AS (
SELECT
COUNT(*) as days,
COALESCE(sum(duration), 0)::bigint as minutes
FROM "WorkTime"
WHERE "userId" = $1 AND "startDate" > $2 AND "startDate" < $3
)
SELECT
wt.days,
(minutes / 60.0)::float as hours,
wt.minutes
FROM wt
`
type GetWorkSummaryForEmployeeInPeriodParams struct {
EmployeeID string `json:"employee_id"`
StartDate *time.Time `json:"start_date"`
EndDate *time.Time `json:"end_date"`
}
type GetWorkSummaryForEmployeeInPeriodRow struct {
Days int64 `json:"days"`
Hours float64 `json:"hours"`
Minutes int64 `json:"minutes"`
}
func (q *Queries) GetWorkSummaryForEmployeeInPeriod(ctx context.Context, arg GetWorkSummaryForEmployeeInPeriodParams) (GetWorkSummaryForEmployeeInPeriodRow, error) {
row := q.db.QueryRow(ctx, getWorkSummaryForEmployeeInPeriod, arg.EmployeeID, arg.StartDate, arg.EndDate)
var i GetWorkSummaryForEmployeeInPeriodRow
err := row.Scan(&i.Days, &i.Hours, &i.Minutes)
return i, err
func (q *Queries) IsEmployeesDepartmentKTW(ctx context.Context, arg IsEmployeesDepartmentKTWParams) (bool, error) {
row := q.db.QueryRow(ctx, isEmployeesDepartmentKTW, arg.EmployeeID, arg.StartDate, arg.EndDate)
var meets_requirements bool
err := row.Scan(&meets_requirements)
return meets_requirements, err
}
const listActiveEmployees = `-- name: ListActiveEmployees :many
@@ -341,6 +324,8 @@ SELECT
FROM "DyflexisEmployee" as de
INNER JOIN "Employee" as e ON de.id::text = e."personnelNumber"
WHERE de."contractStart" < CURRENT_DATE AND de."contractEnd" > (CURRENT_DATE - INTERVAL '2 months')
AND de.surname <> 'Shahrasebhi'
ORDER BY de.firstname ASC
`
type ListActiveEmployeesRow struct {
@@ -386,105 +371,3 @@ func (q *Queries) ListActiveEmployees(ctx context.Context) ([]ListActiveEmployee
}
return items, nil
}
const listPlannedTimesForEmployeeInPeriod = `-- name: ListPlannedTimesForEmployeeInPeriod :many
SELECT
DISTINCT ON (day)
"startDate"::DATE as day,
"startDate",
"endDate",
department,
pause,
duration
FROM "PlannedTime"
WHERE
"userId" = $1 AND
"startDate" > $2 AND
"startDate" < $3 AND
department <> 'Fortbildung' AND
note IS NULL
ORDER BY "day"
`
type ListPlannedTimesForEmployeeInPeriodParams struct {
EmployeeID string `json:"employee_id"`
StartDate *time.Time `json:"start_date"`
EndDate *time.Time `json:"end_date"`
}
type ListPlannedTimesForEmployeeInPeriodRow struct {
Day pgtype.Date `json:"day"`
StartDate *time.Time `json:"startDate"`
EndDate *time.Time `json:"endDate"`
Department pgtype.Text `json:"department"`
Pause int32 `json:"pause"`
Duration int32 `json:"duration"`
}
func (q *Queries) ListPlannedTimesForEmployeeInPeriod(ctx context.Context, arg ListPlannedTimesForEmployeeInPeriodParams) ([]ListPlannedTimesForEmployeeInPeriodRow, error) {
rows, err := q.db.Query(ctx, listPlannedTimesForEmployeeInPeriod, arg.EmployeeID, arg.StartDate, arg.EndDate)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListPlannedTimesForEmployeeInPeriodRow
for rows.Next() {
var i ListPlannedTimesForEmployeeInPeriodRow
if err := rows.Scan(
&i.Day,
&i.StartDate,
&i.EndDate,
&i.Department,
&i.Pause,
&i.Duration,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listWorkTimesForEmployeeInPeriod = `-- name: ListWorkTimesForEmployeeInPeriod :many
SELECT id, "userId", firstname, surname, "startDate", "endDate", department, pause, duration FROM "WorkTime"
WHERE "userId" = $1 AND "startDate" > $2 AND "startDate" < $3
`
type ListWorkTimesForEmployeeInPeriodParams struct {
EmployeeID string `json:"employee_id"`
StartDate *time.Time `json:"start_date"`
EndDate *time.Time `json:"end_date"`
}
func (q *Queries) ListWorkTimesForEmployeeInPeriod(ctx context.Context, arg ListWorkTimesForEmployeeInPeriodParams) ([]WorkTime, error) {
rows, err := q.db.Query(ctx, listWorkTimesForEmployeeInPeriod, arg.EmployeeID, arg.StartDate, arg.EndDate)
if err != nil {
return nil, err
}
defer rows.Close()
var items []WorkTime
for rows.Next() {
var i WorkTime
if err := rows.Scan(
&i.ID,
&i.UserId,
&i.Firstname,
&i.Surname,
&i.StartDate,
&i.EndDate,
&i.Department,
&i.Pause,
&i.Duration,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}