@@ 0,0 1,253 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "sort"
+ "time"
+
+ "github.com/360EntSecGroup-Skylar/excelize"
+ "github.com/skratchdot/open-golang/open"
+)
+
+func report() error {
+ now := time.Now()
+ y, m, _ := now.Date()
+ es, err := loadEntries()
+ if err != nil {
+ return fmt.Errorf("loading entries: %w", err)
+ }
+ var ry, rm int
+ if CLI.Report.Month == "" {
+ if m == time.January {
+ ry = y - 1
+ rm = 12
+ } else {
+ ry = y
+ rm = int(m) - 1
+ }
+ } else {
+ t, err := time.Parse("200601", CLI.Report.Month)
+ if err != nil {
+ return fmt.Errorf("parsing report month: %w", err)
+ }
+ ty, tm, _ := t.Date()
+ ry = ty
+ rm = int(tm)
+ }
+ var entries Entries
+ for _, e := range es {
+ if e.Date.Year() > ry || int(e.Date.Month()) > rm {
+ continue
+ }
+ if e.Date.Year() < ry || int(e.Date.Month()) < rm {
+ break
+ }
+ if e.End == zeroTime {
+ continue
+ }
+ entries = append(entries, e)
+ }
+ data := make(map[int]Entries)
+ for _, e := range entries {
+ data[e.Start.Day()-1] = append(data[e.Start.Day()-1], e)
+ }
+
+ xlsx := excelize.NewFile()
+ //styles
+ centerStyle, err := xlsx.NewStyle(`{"alignment":{"horizontal":"center"}}`)
+ if err != nil {
+ panic(err)
+ }
+ centerBoldStyle, err := xlsx.NewStyle(`{"alignment":{"horizontal":"center"},"font":{"bold":true}}`)
+ if err != nil {
+ panic(err)
+ }
+ dateStyle, err := xlsx.NewStyle(`{"custom_number_format": "dd.mm.", "alignment":{"horizontal":"center"}}`)
+ if err != nil {
+ panic(err)
+ }
+ timeStyle, err := xlsx.NewStyle(`{"custom_number_format": "hh:mm:ss", "alignment":{"horizontal":"center"}}`)
+ if err != nil {
+ panic(err)
+ }
+ durationStyle, err := xlsx.NewStyle(`{"custom_number_format": "[hh]:mm:ss", "alignment":{"horizontal":"center"}}`)
+ if err != nil {
+ panic(err)
+ }
+
+ //detail sheet
+ detailSheet := "Detail"
+ xlsx.SetSheetName("Sheet1", detailSheet)
+ xlsx.SetCellValue(detailSheet, "A1", "Client")
+ xlsx.SetCellValue(detailSheet, "B1", "Project")
+ xlsx.SetCellValue(detailSheet, "C1", "Description")
+ xlsx.SetCellValue(detailSheet, "D1", "Start")
+ xlsx.SetCellValue(detailSheet, "E1", "End")
+ xlsx.SetCellValue(detailSheet, "F1", "Duration")
+ xlsx.SetCellValue(detailSheet, "G1", "Tags")
+
+ for i, e := range entries {
+ xlsx.SetCellValue(detailSheet, fmt.Sprintf("A%d", i+2), e.Project)
+ xlsx.SetCellValue(detailSheet, fmt.Sprintf("B%d", i+2), e.Description)
+ xlsx.SetCellValue(detailSheet, fmt.Sprintf("C%d", i+2), e.Start)
+ xlsx.SetCellValue(detailSheet, fmt.Sprintf("D%d", i+2), e.End)
+ xlsx.SetCellValue(detailSheet, fmt.Sprintf("E%d", i+2), e.End.Sub(e.Start))
+ }
+
+ // sumary sheet
+ e := entries[0]
+ sheetName := e.Start.Format("January 2006")
+ index := xlsx.NewSheet(sheetName)
+ xlsx.SetCellValue(sheetName, "A1", "Dochádzková karta")
+ xlsx.SetCellValue(sheetName, "B1", "Mesačný prehľad")
+ xlsx.SetCellValue(sheetName, "A3", "Za mesiac:")
+ xlsx.SetCellValue(sheetName, "B3", sheetName)
+ xlsx.SetCellValue(sheetName, "A4", "Meno:")
+ xlsx.SetCellValue(sheetName, "B4", os.Getenv("USER"))
+
+ xlsx.SetCellStyle(sheetName, "A6", "G6", centerBoldStyle)
+ xlsx.SetCellValue(sheetName, "A6", "Dátum")
+ xlsx.SetCellValue(sheetName, "B6", "Projekt")
+ xlsx.SetCellValue(sheetName, "C6", "Začiatok")
+ xlsx.SetCellValue(sheetName, "D6", "Koniec")
+ xlsx.SetCellValue(sheetName, "E6", "Celkom")
+ xlsx.SetCellValue(sheetName, "F6", "Popis")
+ xlsx.SetColWidth(sheetName, "A", "E", 19)
+ xlsx.SetColWidth(sheetName, "F", "F", 40)
+
+ d1 := time.Date(e.Start.Year(), e.Start.Month(), 1, 0, 0, 0, 0, time.UTC)
+ row := 7
+ for d := d1; d.Month() == d1.Month(); d = d.AddDate(0, 0, 1) {
+ groups := groupEntries(data[d.Day()-1])
+ for _, e := range groups {
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("A%d", row), fmt.Sprintf("A%d", row), dateStyle)
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("B%d", row), fmt.Sprintf("B%d", row), centerStyle)
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("C%d", row), fmt.Sprintf("C%d", row), timeStyle)
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("D%d", row), fmt.Sprintf("D%d", row), timeStyle)
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("E%d", row), fmt.Sprintf("E%d", row), durationStyle)
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("F%d", row), fmt.Sprintf("F%d", row), centerStyle)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("A%d", row), d)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("B%d", row), e.Project)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("C%d", row), e.Start)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("D%d", row), e.End)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("E%d", row), e.End.Sub(e.Start).Seconds()/86400)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("F%d", row), e.Description)
+ row++
+ }
+ if len(groups) == 0 {
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("A%d", row), fmt.Sprintf("A%d", row), dateStyle)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("A%d", row), d)
+ row++
+ }
+ }
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("D%d", row), "Spolu:")
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("E%d", row), fmt.Sprintf("E%d", row), durationStyle)
+ xlsx.SetCellFormula(sheetName, fmt.Sprintf("E%d", row), fmt.Sprintf("=SUM(E7:E%d)", row-1))
+
+ //projects pivot table
+ row += 4
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("A%d", row), fmt.Sprintf("F%d", row), centerBoldStyle)
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("A%d", row), "Projekt")
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("B%d", row), "Celkom")
+ projectsPivot := make(PivotTable)
+ for _, e := range entries {
+ projectsPivot.Add(e.Project, e.End.Sub(e.Start))
+ }
+ row++
+ pivotStart := row
+ xlsx.SetCellStyle(sheetName, fmt.Sprintf("B%d", row), fmt.Sprintf("B%d", row+len(projectsPivot)), durationStyle)
+ for _, p := range projectsPivot.Sorted() {
+ if p.Key == "" {
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("A%d", row), "-")
+ } else {
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("A%d", row), p.Key)
+ }
+ xlsx.SetCellValue(sheetName, fmt.Sprintf("B%d", row), p.Value.Seconds()/86400)
+ row++
+ }
+ pivotEnd := row
+
+ xlsx.AddChart(
+ sheetName,
+ fmt.Sprintf("A%d", pivotEnd+1),
+ fmt.Sprintf(
+ `{"type":"pie","title":{"name":"Podľa projektov"},"series":[{"categories":"='%s'!$A$%d:$A$%d", "values":"='%s'!$B$%d:$B$%d"}],"format":{"x_scale":0.6,"y_scale":1.0}}`,
+ sheetName, pivotStart, pivotEnd-1,
+ sheetName, pivotStart, pivotEnd-1,
+ ),
+ )
+
+ xlsx.SetActiveSheet(index)
+ // Save xlsx file by the given path.
+ if err := xlsx.SaveAs(CLI.Report.Output); err != nil {
+ return fmt.Errorf("writing file %s: %s", CLI.Report.Output, err)
+ }
+ open.Run(CLI.Report.Output)
+ return nil
+}
+
+func groupEntries(es Entries) (ees Entries) {
+ groups := make(map[string]Entries)
+ for _, e := range es {
+ key := e.Project + e.Description
+ groups[key] = append(groups[key], e)
+ }
+ for _, group := range groups {
+ start := group[0].Start
+ end := group[0].End
+ var duration time.Duration
+ for _, g := range group {
+ if start.After(g.Start) {
+ start = g.Start
+ }
+ if end.Before(g.End) {
+ end = g.End
+ }
+ duration += end.Sub(start)
+ }
+ ees = append(
+ ees,
+ Entry{
+ Project: group[0].Project,
+ Description: group[0].Description,
+ Start: start,
+ End: end,
+ },
+ )
+ }
+ return
+}
+
+//PivotTable used to sum up the duration by category and get the sorted data
+type PivotTable map[string]time.Duration
+
+//Add adds new duration to the PivotTable
+func (pt PivotTable) Add(category string, duration time.Duration) {
+ pt[category] += duration
+}
+
+//Sorted returns an sorted list of key-value pairs
+func (pt PivotTable) Sorted() PairList {
+ pl := make(PairList, len(pt))
+ i := 0
+ for k, v := range pt {
+ pl[i] = Pair{k, v}
+ i++
+ }
+ sort.Sort(sort.Reverse(pl))
+ return pl
+}
+
+//Pair represents a kev-value pair
+type Pair struct {
+ Key string
+ Value time.Duration
+}
+
+//PairList is a sortable list of key-value pairs
+type PairList []Pair
+
+func (p PairList) Len() int { return len(p) }
+func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value }
+func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }