avicenna/service/data-hub/business-engine/internal/payroll/service.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(),
)
}