You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

343 lines
7.5 KiB
Go

package hr
import (
"errors"
"fmt"
"mercury/src/db"
"mercury/src/mercuryUtil"
"os"
"path"
"strconv"
"strings"
)
type (
PaycorHoursLegacy struct {
Paygroup string
LastName string
FirstName string
HomeDept string
ManagerName string
WorkedDept string
OT float64
Reg float64
Sick float64
Vacation float64
Total float64
}
PaycorHours struct {
EEid int
Bereavement float64
Holiday float64
OT float64
Regular float64
Service float64
Sick float64
Vacation float64
Total float64
WeekOf string
}
PaycorDirectoryEntry struct {
Paygroup string
LastName string
FirstName string
EEid int
DepartmentName string
Manager string
}
)
var (
OldHeaders = []string{"Paygroup", "Last Name", "First Name", "Home Department", "Manager Name", "Worked DeptName", "OT", "Reg", "Sick", "Vac", "Total"}
NewHeaders = []string{"Badge #", "Brv", "Hol", "OT", "Reg", "Service", "Sick", "Vac", "Total"}
)
const (
defaultReportSize = 200
)
func UpdatePaycorReports(pathlike string) error {
updateDirectory()
err := updatePaycorHours(pathlike)
if err != nil {
return err
}
return nil
}
func updateDirectory() {
runner := db.NewRunner("create-mercury-hrPaycorDir-table.sql", db.MercuryDatabaseName)
cx := &db.ConnectorGeneric{}
cx.ExecuteSqlScript(runner)
items := loadDirectory()
db.BlockUpdate[PaycorDirectoryEntry](cx, db.MercuryDatabaseName, "update-mercury-hrPaycorDirectory.sql", items)
}
func updatePaycorHours(pathlike string) error {
files, err := os.ReadDir(pathlike)
cx := &db.ConnectorGeneric{}
tableRunner := db.NewRunner("create-mercury-hrPaycorHours-table.sql", db.MercuryDatabaseName)
cx.ExecuteSqlScript(tableRunner)
itemAccumulator := make([]*PaycorHours, 0, len(files)*defaultReportSize)
if err != nil {
return err
}
for _, v := range files {
if isTempFile(v.Name()) {
continue
}
filePath := path.Join(pathlike, v.Name())
worker := mercuryUtil.NewCsvWorker(filePath, func() *PaycorHours { return &PaycorHours{} }, true)
items := make([]*PaycorHours, 0, defaultReportSize)
err := worker.Process(&items)
if err != nil {
return err
}
for _, i := range items {
i.WeekOf = dateFromFileName(v.Name())
}
itemAccumulator = append(itemAccumulator, items...)
}
db.BlockUpdate[PaycorHours](cx, db.MercuryDatabaseName, "update-mercury-hrPaycorHours.sql", &itemAccumulator)
return nil
}
func loadDirectory() *[]*PaycorDirectoryEntry {
items := make([]*PaycorDirectoryEntry, 0, 200)
//todo hint: I don't like how you implemented this, David.
hardcodedDirectoryFileLocation := "/home/dtookey/work/clarity-reporting/paycor_dir/20220914_Paycor_Employee Roster.csv"
worker := mercuryUtil.NewCsvWorker(
hardcodedDirectoryFileLocation,
func() *PaycorDirectoryEntry { return &PaycorDirectoryEntry{} },
true,
)
err := worker.Process(&items)
if err != nil {
panic(err)
}
return &items
}
func (hl *PaycorHoursLegacy) Set(header string, content string) error {
content = strings.ReplaceAll(content, "\u00a0", "")
switch header {
case "Paygroup":
hl.Paygroup = content
case "Last Name":
hl.LastName = content
case "First Name":
hl.FirstName = content
case "Home Department":
hl.HomeDept = content
case "Manager Name":
hl.ManagerName = content
case "Worked DeptName":
hl.WorkedDept = content
case "OT":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
hl.OT = f
case "Reg":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
hl.Reg = f
case "Sick":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
hl.Sick = f
case "Vac":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
hl.Vacation = f
case "Total":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
hl.Total = f
default:
return errors.New("Could not find header for '" + header + "'")
}
return nil
}
func (h *PaycorHours) Set(header string, content string) error {
switch header {
case "Badge #":
i, err := parseInt(content)
if err != nil {
return err
}
h.EEid = i
case "Brv":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.Bereavement = f
case "Hol":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.Holiday = f
case "OT":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.OT = f
case "Reg":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.Regular = f
case "Service":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.Service = f
case "Sick":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.Sick = f
case "Vac":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.Vacation = f
case "Total":
f, err := parseFloat(content)
if err != nil {
panic(err)
}
h.Total = f
default:
return errors.New("Could not find mapping for '" + header + "'")
}
return nil
}
func (h PaycorHours) ToQueryBlock() string {
return fmt.Sprintf("(%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,'%s')", h.EEid, h.Bereavement, h.Holiday, h.OT, h.Regular, h.Service, h.Sick, h.Vacation, h.Total, h.WeekOf)
}
func (d PaycorDirectoryEntry) ToQueryBlock() string {
return fmt.Sprintf("(%d,'%s','%s','%s','%s','%s')", d.EEid, d.Paygroup, d.LastName, d.FirstName, d.DepartmentName, d.Manager)
}
func (d *PaycorDirectoryEntry) Set(header string, content string) error {
switch header {
case "Paygroup":
d.Paygroup = content
case "Last Name":
d.LastName = content
case "First Name":
d.FirstName = content
case "Badge #":
v, err := strconv.Atoi(content)
if err != nil {
return err
}
d.EEid = v
case "Department Name":
d.DepartmentName = content
case "Manager":
d.Manager = content
default:
return errors.New("Could not find mapping for '" + header + "'")
}
return nil
}
func (hl *PaycorHoursLegacy) ToPaycorHours(directory *map[string]*PaycorDirectoryEntry) (*PaycorHours, error) {
h := PaycorHours{
Bereavement: 0.0,
Holiday: 0.0,
OT: hl.OT,
Regular: hl.Reg,
Service: 0.0,
Sick: hl.Sick,
Vacation: hl.Vacation,
Total: hl.Total,
}
ee, okay := (*directory)[hl.FirstName+hl.LastName]
if !okay {
return nil, errors.New("could not find map entry for [" + hl.FirstName + hl.LastName + "]")
}
h.EEid = ee.EEid
return &h, nil
}
func (h *PaycorHours) ToRow() []string {
return []string{
strconv.Itoa(h.EEid),
strconv.FormatFloat(h.Bereavement, 'f', 2, 64),
strconv.FormatFloat(h.Holiday, 'f', 2, 64),
strconv.FormatFloat(h.OT, 'f', 2, 64),
strconv.FormatFloat(h.Regular, 'f', 2, 64),
strconv.FormatFloat(h.Service, 'f', 2, 64),
strconv.FormatFloat(h.Sick, 'f', 2, 64),
strconv.FormatFloat(h.Vacation, 'f', 2, 64),
strconv.FormatFloat(h.Total, 'f', 2, 64),
}
}
func parseFloat(content string) (float64, error) {
if len(content) == 0 {
return 0.0, nil
} else {
f, err := strconv.ParseFloat(content, 64)
if err != nil {
return 0.0, err
}
return f, nil
}
}
func parseInt(content string) (int, error) {
if len(content) == 0 {
return -1, nil
} else {
i, err := strconv.Atoi(content)
if err != nil {
return -1, err
}
return i, nil
}
}
func dateFromFileName(filename string) string {
parts := strings.Split(filename, "_")
block := parts[0]
year := block[:4]
month := block[4:6]
date := block[6:]
return fmt.Sprintf("%s-%s-%s", year, month, date)
}
func isTempFile(filepath string) bool {
return filepath[len(filepath)-1:] == "#"
}