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(), ) }