first draft of timesheet reports.
still need to modify all existing reports to match up to the schema of what's actually coming out from HR lately.master
parent
bc0c38745d
commit
8e6a2f376d
@ -0,0 +1,337 @@
|
||||
package hr
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"log"
|
||||
"mercury/src/db"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
HourReport struct {
|
||||
FilePath string
|
||||
Records *[][]string
|
||||
SkipFirstRow bool
|
||||
ReportLines *[]*HourReportLine
|
||||
}
|
||||
|
||||
HourReportLoadTask struct {
|
||||
Records *[][]string
|
||||
Err error
|
||||
}
|
||||
|
||||
HourReportLine struct {
|
||||
PayGroup string
|
||||
LName string
|
||||
FName string
|
||||
HomeDept string
|
||||
ManagerName string
|
||||
WeekEnding string
|
||||
EEId int
|
||||
Overtime float64
|
||||
Regular float64
|
||||
Sick float64
|
||||
Vacation float64
|
||||
Bereavement float64
|
||||
Service float64
|
||||
Total float64
|
||||
}
|
||||
)
|
||||
|
||||
var namePattern = regexp.MustCompile("^\\d{1,2}.\\d{1,2}.\\d{2}-(\\d{1,2}.\\d{1,2}.\\d{2}).*csv$")
|
||||
|
||||
func loadReports(pathlikeBase string) *[]HourReport {
|
||||
files, err := getAllFilesInDir(pathlikeBase)
|
||||
reports := make([]HourReport, 0, 300)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, file := range *files {
|
||||
sReport := NewHourReport(file, false)
|
||||
reports = append(reports, *sReport)
|
||||
}
|
||||
|
||||
return &reports
|
||||
}
|
||||
|
||||
func UpdateTimesheetReport(pathlike string) {
|
||||
connector := &db.ConnectorGeneric{}
|
||||
|
||||
reports := loadReports(pathlike)
|
||||
|
||||
tableWipe := db.NewRunner("create-mercury-hrTimesheets-table.sql", db.MercuryDatabaseName)
|
||||
connector.ExecuteSqlScript(tableWipe)
|
||||
for _, hourReport := range *reports {
|
||||
log.Printf("Updating database\n")
|
||||
db.BlockUpdate[HourReportLine](connector, db.MercuryDatabaseName, "update-mercury-hrTimesheets.sql", hourReport.ReportLines)
|
||||
log.Printf("Updates finished.\n")
|
||||
}
|
||||
|
||||
tablePrune := db.NewRunner("update-mercury-hrTimesheetsCleanup.sql", db.MercuryDatabaseName)
|
||||
connector.ExecuteSqlScript(tablePrune)
|
||||
}
|
||||
|
||||
func NewHourReport(pathlike string, skipFirstRow bool) *HourReport {
|
||||
report := HourReport{
|
||||
FilePath: pathlike,
|
||||
Records: nil,
|
||||
SkipFirstRow: skipFirstRow,
|
||||
}
|
||||
|
||||
asyncChan := make(chan *HourReportLoadTask)
|
||||
|
||||
go loadTimeSheet(report.FilePath, asyncChan)
|
||||
recordStatus := <-asyncChan
|
||||
if recordStatus.Err != nil {
|
||||
fmt.Printf("Error in the following file: %s\n", report.FilePath)
|
||||
panic(recordStatus.Err)
|
||||
return nil
|
||||
}
|
||||
|
||||
report.Records = recordStatus.Records
|
||||
|
||||
report.ReportLines = processReportToLines(report)
|
||||
|
||||
return &report
|
||||
}
|
||||
|
||||
func processReportToLines(report HourReport) *[]*HourReportLine {
|
||||
lines := make([]*HourReportLine, 0, 250)
|
||||
localTable := *report.Records
|
||||
headersRaw := (localTable)[0]
|
||||
headers := make([]string, len(headersRaw), len(headersRaw))
|
||||
for i, v := range headersRaw {
|
||||
key := strings.Trim(v, " \t\uFEFF")
|
||||
headers[i] = key
|
||||
}
|
||||
|
||||
for i := 1; i < len(localTable); i++ {
|
||||
row := localTable[i]
|
||||
line := newHourReportLineFromRow(headers, row)
|
||||
line.WeekEnding = fileNameToSQLDate(report.FilePath)
|
||||
lines = append(lines, &line)
|
||||
}
|
||||
|
||||
return &lines
|
||||
}
|
||||
|
||||
func fileNameToSQLDate(fileName string) string {
|
||||
name := path.Base(fileName)
|
||||
parts := strings.Split(name, "_")
|
||||
year := parts[0][:4]
|
||||
month := parts[0][4:6]
|
||||
date := parts[0][6:8]
|
||||
return fmt.Sprintf("%s-%s-%s", year, month, date)
|
||||
}
|
||||
|
||||
// I hate that this has a ton of hard-coded stuff. I'm not sure if there's a way around it though
|
||||
func newHourReportLineFromRow(headers []string, row []string) HourReportLine {
|
||||
line := HourReportLine{}
|
||||
if len(headers) != len(row) {
|
||||
panic("header array and row array are different sizes in newHourReportLineFromRow")
|
||||
}
|
||||
//"Paygroup", "Last Name", "First Name", "Home Department", "Manager Name", "Worked DeptName", "OT", "Reg", "Sick", "Vac", "Total"
|
||||
|
||||
for i, header := range headers {
|
||||
strVal := row[i]
|
||||
switch header {
|
||||
case "Paygroup":
|
||||
line.PayGroup = strVal
|
||||
case "Last Name":
|
||||
line.LName = strVal
|
||||
case "First Name":
|
||||
line.FName = strVal
|
||||
case "Home Department":
|
||||
line.HomeDept = strVal
|
||||
case "Manager Name":
|
||||
line.ManagerName = strVal
|
||||
case "Worked DeptName":
|
||||
continue
|
||||
case "OT":
|
||||
v, err := strconv.ParseFloat(strVal, 64)
|
||||
if err != nil {
|
||||
v = 0
|
||||
}
|
||||
line.Overtime = v
|
||||
|
||||
case "Reg":
|
||||
|
||||
v, err := strconv.ParseFloat(strVal, 64)
|
||||
if err != nil {
|
||||
v = 0
|
||||
}
|
||||
line.Regular = v
|
||||
|
||||
case "Sick":
|
||||
|
||||
v, err := strconv.ParseFloat(strVal, 64)
|
||||
if err != nil {
|
||||
v = 0
|
||||
}
|
||||
line.Sick = v
|
||||
|
||||
case "Vac":
|
||||
|
||||
v, err := strconv.ParseFloat(strVal, 64)
|
||||
if err != nil {
|
||||
v = 0
|
||||
}
|
||||
line.Vacation = v
|
||||
|
||||
case "Total":
|
||||
|
||||
v, err := strconv.ParseFloat(strVal, 64)
|
||||
if err != nil {
|
||||
v = 0
|
||||
}
|
||||
line.Total = v
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return line
|
||||
}
|
||||
|
||||
func loadTimeSheet(pathlike string, asyncChan chan<- *HourReportLoadTask) {
|
||||
f, err := os.OpenFile(pathlike, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
asyncChan <- &HourReportLoadTask{nil, err}
|
||||
close(asyncChan)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
reader := csv.NewReader(f)
|
||||
records, err := reader.ReadAll()
|
||||
|
||||
if err != nil {
|
||||
asyncChan <- &HourReportLoadTask{nil, err}
|
||||
close(asyncChan)
|
||||
return
|
||||
}
|
||||
|
||||
asyncChan <- &HourReportLoadTask{&records, nil}
|
||||
close(asyncChan)
|
||||
}
|
||||
|
||||
func (line HourReportLine) ToQueryBlock() string {
|
||||
return fmt.Sprintf(
|
||||
"('%s','%s','%s','%s','%s','%s','%d','%f','%f','%f','%f','%f','%f','%f')",
|
||||
line.PayGroup,
|
||||
line.LName,
|
||||
line.FName,
|
||||
line.HomeDept,
|
||||
line.ManagerName,
|
||||
line.WeekEnding,
|
||||
line.EEId,
|
||||
line.Overtime,
|
||||
line.Regular,
|
||||
line.Sick,
|
||||
line.Vacation,
|
||||
line.Bereavement,
|
||||
line.Service,
|
||||
line.Total,
|
||||
)
|
||||
}
|
||||
|
||||
//deprecated
|
||||
func rename(report HourReport) {
|
||||
fileName := path.Base(report.FilePath)
|
||||
if namePattern.MatchString(fileName) {
|
||||
idx := namePattern.FindAllStringSubmatch(fileName, -1)
|
||||
parts := strings.Split(idx[0][1], ".")
|
||||
year, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
month, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
date, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
full := fmt.Sprintf("20%02d%02d%02d", year, month, date)
|
||||
tStamp := time.Date(year, getMonth(month), date, 10, 0, 0, 0, time.UTC)
|
||||
_, week := tStamp.ISOWeek()
|
||||
fileName := fmt.Sprintf("%s_Paycor_W%d.csv", full, week)
|
||||
|
||||
err = copyFile(report.FilePath, fileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Couldn't find match for %s\n", fileName)
|
||||
}
|
||||
}
|
||||
|
||||
func getMonth(month int) time.Month {
|
||||
switch month {
|
||||
case 1:
|
||||
return time.January
|
||||
case 2:
|
||||
return time.February
|
||||
case 3:
|
||||
return time.March
|
||||
case 4:
|
||||
return time.April
|
||||
case 5:
|
||||
return time.May
|
||||
case 6:
|
||||
return time.June
|
||||
case 7:
|
||||
return time.July
|
||||
case 8:
|
||||
return time.August
|
||||
case 9:
|
||||
return time.September
|
||||
case 10:
|
||||
return time.October
|
||||
case 11:
|
||||
return time.November
|
||||
case 12:
|
||||
return time.December
|
||||
default:
|
||||
return time.January
|
||||
}
|
||||
}
|
||||
|
||||
func getAllFilesInDir(pathlikeBase string) (*[]string, error) {
|
||||
listing, err := os.ReadDir(pathlikeBase)
|
||||
res := make([]string, 0, 300)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, list := range listing {
|
||||
if list.IsDir() || path.Ext(list.Name()) != ".csv" {
|
||||
fmt.Printf("Skipping: %s\n", list.Name())
|
||||
continue
|
||||
} else {
|
||||
res = append(res, path.Join(pathlikeBase, list.Name()))
|
||||
}
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func copyFile(inPath string, outpath string) error {
|
||||
outPathBase := "/home/dtookey/work/clarity-reporting/pcorrect"
|
||||
outFinal := path.Join(outPathBase, outpath)
|
||||
b, err := os.ReadFile(inPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(outFinal, b, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
DROP TABLE IF EXISTS mercury.hr_timesheet_report;
|
||||
|
||||
CREATE TABLE mercury.hr_timesheet_report
|
||||
(
|
||||
PayGroup VARCHAR(150),
|
||||
LName VARCHAR(150),
|
||||
FName VARCHAR(150),
|
||||
HomeDept VARCHAR(150),
|
||||
ManagerName VARCHAR(150),
|
||||
WeekEnding VARCHAR(150),
|
||||
EEId INT,
|
||||
Overtime REAL,
|
||||
Regular REAL,
|
||||
Sick REAL,
|
||||
Vacation REAL,
|
||||
Bereavement REAL,
|
||||
Service REAL,
|
||||
Total REAL
|
||||
);
|
||||
@ -0,0 +1,3 @@
|
||||
SELECT refnum, location, IF(price_override = 1, override_price, default_price) as fee
|
||||
FROM projects.all_projects
|
||||
inner join billing b on all_projects.refnum = b.refNumber;
|
||||
@ -0,0 +1,3 @@
|
||||
INSERT INTO mercury.hr_timesheet_report (PayGroup, LName, FName, HomeDept, ManagerName, WeekEnding, EEId, Overtime, Regular,
|
||||
Sick, Vacation, Bereavement, Service, Total)
|
||||
VALUES %s;
|
||||
@ -0,0 +1 @@
|
||||
DELETE FROM mercury.hr_timesheet_report where FName = '' and LName = '';
|
||||
@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"log"
|
||||
"mercury/src/db"
|
||||
"mercury/src/mercury"
|
||||
"mercury/src/projectClarity"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
projectPattern = regexp.MustCompile("^[A-z]{3}\\d{7}$")
|
||||
locationPattern = regexp.MustCompile(".*, ([NSC]{2})")
|
||||
)
|
||||
|
||||
func main() {
|
||||
csvPath := "/home/dtookey/work/clarity-reporting/billing2021.CSV"
|
||||
outPath := "/home/dtookey/work/clarity-reporting/extended-billing2021.csv"
|
||||
records := open(csvPath)
|
||||
projectMap := getClarityData()
|
||||
augmentedData := augmentData(records, *projectMap)
|
||||
write(augmentedData, outPath)
|
||||
}
|
||||
|
||||
func getClarityData() *map[string]projectClarity.ClarityProjectBilling {
|
||||
cdb := mercury.NewInterconnect()
|
||||
cdb.PseudoInit()
|
||||
cb := func(rows *sql.Rows) *projectClarity.ClarityProjectBilling {
|
||||
container := projectClarity.ClarityProjectBilling{}
|
||||
err := rows.Scan(&container.Refnum, &container.Location, &container.Fee)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
return &container
|
||||
}
|
||||
rMap := make(map[string]projectClarity.ClarityProjectBilling)
|
||||
thing := db.QueryForObjects[projectClarity.ClarityProjectBilling](cdb.InsightDBConnector.ConnectorGeneric, db.ClarityDatabaseName, "read-clarity-billingProjects.sql", cb)
|
||||
for _, thing := range *thing {
|
||||
rMap[thing.Refnum] = *thing
|
||||
}
|
||||
|
||||
return &rMap
|
||||
}
|
||||
|
||||
func augmentData(data *[][]string, clarityData map[string]projectClarity.ClarityProjectBilling) *[][]string {
|
||||
results := make([][]string, 0, 10000)
|
||||
longest := ""
|
||||
for _, row := range *data {
|
||||
refNum := row[3]
|
||||
nRow := row
|
||||
if projectPattern.MatchString(refNum) {
|
||||
project := clarityData[refNum]
|
||||
deciString := strconv.FormatFloat(project.Fee, 'f', 2, 64)
|
||||
if len(project.Location) > len(longest) {
|
||||
longest = project.Location
|
||||
}
|
||||
|
||||
nRow = append(nRow, project.Location)
|
||||
nRow = append(nRow, deciString)
|
||||
b := []byte(project.Location)
|
||||
if locationPattern.Match(b) {
|
||||
idx := locationPattern.FindAllSubmatchIndex(b, -1)[0]
|
||||
fmt.Printf("%#v\n", idx)
|
||||
state := string(b[idx[2]:idx[3]])
|
||||
fmt.Println(state)
|
||||
nRow = append(nRow, state)
|
||||
} else {
|
||||
nRow = append(nRow, "")
|
||||
}
|
||||
} else {
|
||||
nRow = append(nRow, "")
|
||||
nRow = append(nRow, "")
|
||||
nRow = append(nRow, "")
|
||||
}
|
||||
results = append(results, nRow)
|
||||
}
|
||||
fmt.Println(longest)
|
||||
|
||||
return &results
|
||||
}
|
||||
|
||||
func write(data *[][]string, pathlike string) {
|
||||
f, err := os.Create(pathlike)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
writer := csv.NewWriter(f)
|
||||
for _, row := range *data {
|
||||
err := writer.Write(row)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func open(pathlike string) *[][]string {
|
||||
f, err := os.OpenFile(pathlike, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
reader := csv.NewReader(f)
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &records
|
||||
}
|
||||
@ -1,12 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"mercury/src/finance"
|
||||
"mercury/src/mercury"
|
||||
)
|
||||
import "mercury/src/hr"
|
||||
|
||||
func main() {
|
||||
ic := mercury.NewInterconnect()
|
||||
ic.ResetTables()
|
||||
finance.GenerateArAgingReport("/home/dtookey/work/clarity-reporting/qb/ar/")
|
||||
reportBase := "/home/dtookey/work/clarity-reporting/paycor/"
|
||||
hr.LoadReports(reportBase)
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue