prorate.go 1.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. package payroll
  2. import (
  3. "time"
  4. "github.com/example/payroll-svc/internal/domain/payroll"
  5. )
  6. // prorateSalary scales a full period rate down when the contract covers only
  7. // part of the cycle window (a mid-period joiner or leaver).
  8. func prorateSalary(fullCents int64, contract payroll.Contract, cycle payroll.Cycle) int64 {
  9. window := calendarDays(cycle.Start, cycle.End)
  10. if window <= 0 {
  11. return 0
  12. }
  13. covered := calendarDays(laterOf(cycle.Start, contract.StartsOn), earlierOf(cycle.End, contract.EndsOn))
  14. if covered >= window {
  15. return fullCents
  16. }
  17. if covered <= 0 {
  18. return 0
  19. }
  20. return fullCents * int64(covered) / int64(window)
  21. }
  22. // prorateAllowance applies the same window rule to a recurring allowance.
  23. func prorateAllowance(a payroll.Allowance, cycle payroll.Cycle, e payroll.Employee) int64 {
  24. if !a.Prorated {
  25. return a.AmountCents
  26. }
  27. return prorateSalary(a.AmountCents, e.Contract, cycle)
  28. }
  29. func calendarDays(from, to time.Time) int {
  30. if to.Before(from) {
  31. return 0
  32. }
  33. return int(to.Sub(from).Hours()/24) + 1
  34. }
  35. func laterOf(a, b time.Time) time.Time {
  36. if b.IsZero() || a.After(b) {
  37. return a
  38. }
  39. return b
  40. }
  41. func earlierOf(a, b time.Time) time.Time {
  42. if b.IsZero() || a.Before(b) {
  43. return a
  44. }
  45. return b
  46. }