178 lines
6.1 KiB
Go
178 lines
6.1 KiB
Go
package payroll
|
|
|
|
import (
|
|
"business-engine/internal/database"
|
|
excelread "business-engine/internal/excel_read"
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
type Repository interface {
|
|
ListActiveEmployees(ctx context.Context) ([]database.ListActiveEmployeesRow, error)
|
|
GetWorkSummaryForEmployeeInPeriod(ctx context.Context, args database.GetWorkSummaryForEmployeeInPeriodParams) (database.GetWorkSummaryForEmployeeInPeriodRow, error)
|
|
GetSicknessSummaryForEmployeeInPeriod(ctx context.Context, args database.GetSicknessSummaryForEmployeeInPeriodParams) (database.GetSicknessSummaryForEmployeeInPeriodRow, error)
|
|
ListWorkTimesForEmployeeInPeriod(ctx context.Context, args database.ListWorkTimesForEmployeeInPeriodParams) ([]database.WorkTime, error)
|
|
ListPlannedTimesForEmployeeInPeriod(ctx context.Context, args database.ListPlannedTimesForEmployeeInPeriodParams) ([]database.ListPlannedTimesForEmployeeInPeriodRow, error)
|
|
FetchDrivenToursInInterval(ctx context.Context, args database.FetchDrivenToursInIntervalParams) ([]database.Tour, error)
|
|
CountTourengeldAmountInPeriod(ctx context.Context, args database.CountTourengeldAmountInPeriodParams) (float64, error)
|
|
GetEmployeeByName(ctx context.Context, args database.GetEmployeeByNameParams) (database.GetEmployeeByNameRow, error)
|
|
}
|
|
|
|
type Engine interface {
|
|
Calculate() // payroll input -> payroll statement
|
|
}
|
|
|
|
type Service struct {
|
|
repo Repository
|
|
engine Engine
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewService(repo Repository, logger *slog.Logger) *Service {
|
|
return &Service{
|
|
repo: repo,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (s *Service) FetchPayrollInput(ctx context.Context, month time.Time, employeeFirstname, employeeLastname string) (excelread.PayrollInputData, error) {
|
|
s.logger.Info("fetching payroll input", "month", month)
|
|
|
|
employeeIDs, err := s.repo.GetEmployeeByName(ctx, database.GetEmployeeByNameParams{
|
|
Firstname: employeeFirstname,
|
|
Lastname: employeeLastname,
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("could not find employee with that name", "firstname", employeeFirstname, "lastname", employeeLastname, "error", err)
|
|
return excelread.PayrollInputData{}, err
|
|
}
|
|
s.logger.Debug("got employee data", "employee", employeeIDs)
|
|
|
|
startOfMonth := BeginningOfMonth(month.AddDate(0, -1, 0))
|
|
endOfMonth := EndOfMonth(month.AddDate(0, -1, 0))
|
|
s.logger.Info("fetching data for interval", "startDate", startOfMonth, "endDate", endOfMonth)
|
|
|
|
tourengeld, err := s.repo.CountTourengeldAmountInPeriod(ctx, database.CountTourengeldAmountInPeriodParams{
|
|
StartDate: startOfMonth,
|
|
EndDate: endOfMonth,
|
|
EmployeeID: pgtype.Text{
|
|
String: employeeIDs.DispoliveID,
|
|
Valid: true,
|
|
},
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("could not count tourengeld for employee", "employeeId", employeeIDs.DyflexisID, "error", err)
|
|
}
|
|
|
|
plannedtimes, err := s.repo.ListPlannedTimesForEmployeeInPeriod(ctx, database.ListPlannedTimesForEmployeeInPeriodParams{
|
|
EmployeeID: fmt.Sprintf("%d", employeeIDs.DyflexisID),
|
|
StartDate: &startOfMonth,
|
|
EndDate: &endOfMonth,
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("could not fetch plannedtimes for employee", "employeeId", employeeIDs.DyflexisID, "error", err)
|
|
}
|
|
|
|
worktimes, err := s.repo.ListWorkTimesForEmployeeInPeriod(ctx, database.ListWorkTimesForEmployeeInPeriodParams{
|
|
EmployeeID: fmt.Sprintf("%d", employeeIDs.DyflexisID),
|
|
StartDate: &startOfMonth,
|
|
EndDate: &endOfMonth,
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("could not fetch worktimes for employee", "employeeId", employeeIDs.DyflexisID, "error", err)
|
|
}
|
|
|
|
s.logger.Info("Fetched payroll input", "employee", fmt.Sprintf("%s %s", employeeFirstname, employeeLastname), "tourengeld", tourengeld, "workdays", len(worktimes))
|
|
|
|
return excelread.NewPayrollInputData(employeeIDs.ContractSalaryHour, tourengeld, worktimes, plannedtimes), nil
|
|
}
|
|
|
|
func (s *Service) GenerateMonthlyRun(ctx context.Context, month time.Time) ([]PayrollInput, error) {
|
|
s.logger.Info("starting monthly payroll run", "month", month)
|
|
|
|
employees, err := s.repo.ListActiveEmployees(ctx)
|
|
if err != nil {
|
|
s.logger.Error("could not get active employees", "error", err)
|
|
return []PayrollInput{}, err
|
|
}
|
|
s.logger.Info("found active employees for payroll run", "count", len(employees))
|
|
|
|
var inputs []PayrollInput
|
|
|
|
for _, employee := range employees {
|
|
log := s.logger.With("employee_id", employee.ID)
|
|
log.Info("processing employee")
|
|
|
|
payrollInput := NewPayrollInput(employee)
|
|
startOfMonth := BeginningOfMonth(month.AddDate(0, -1, 0))
|
|
endOfMonth := EndOfMonth(month.AddDate(0, -1, 0))
|
|
|
|
workSummary, err := s.repo.GetWorkSummaryForEmployeeInPeriod(ctx, database.GetWorkSummaryForEmployeeInPeriodParams{
|
|
EmployeeID: fmt.Sprintf("%d", employee.ID),
|
|
StartDate: &startOfMonth,
|
|
EndDate: &endOfMonth,
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("could not get work summary for employee", "error", err)
|
|
return []PayrollInput{}, err
|
|
}
|
|
|
|
payrollInput.WorkSummary = struct {
|
|
Days int64
|
|
Hours float64
|
|
Minutes int64
|
|
}(workSummary)
|
|
|
|
sicknessSummary, err := s.repo.GetSicknessSummaryForEmployeeInPeriod(ctx, database.GetSicknessSummaryForEmployeeInPeriodParams{
|
|
EmployeeID: fmt.Sprintf("%d", employee.ID),
|
|
StartDate: &startOfMonth,
|
|
EndDate: &endOfMonth,
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("could not get work summary for employee", "error", err)
|
|
return []PayrollInput{}, err
|
|
}
|
|
|
|
payrollInput.SicknessSummary = struct {
|
|
Days int64
|
|
Hours float64
|
|
Minutes int64
|
|
}(sicknessSummary)
|
|
|
|
worktimes, err := s.repo.ListWorkTimesForEmployeeInPeriod(ctx, database.ListWorkTimesForEmployeeInPeriodParams{
|
|
EmployeeID: fmt.Sprintf("%d", employee.ID),
|
|
StartDate: &startOfMonth,
|
|
EndDate: &endOfMonth,
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("could not get worktimes for employee", "error", err)
|
|
return []PayrollInput{}, nil
|
|
}
|
|
|
|
payrollInput.WorkTime = worktimes
|
|
|
|
inputs = append(inputs, payrollInput)
|
|
}
|
|
|
|
return inputs, nil
|
|
}
|
|
|
|
func BeginningOfMonth(date time.Time) time.Time {
|
|
return date.AddDate(0, 0, -date.Day()+1)
|
|
}
|
|
|
|
func EndOfMonth(date time.Time) time.Time {
|
|
eom := date.AddDate(0, 1, -date.Day())
|
|
return time.Date(
|
|
eom.Year(),
|
|
eom.Month(),
|
|
eom.Day(),
|
|
23, 59, 59, 999,
|
|
eom.Location(),
|
|
)
|
|
}
|