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:] == "#" }