From fcd639130d05ffdcb481109ac11daefff2b1f00e Mon Sep 17 00:00:00 2001 From: Marco Reinwarth Date: Thu, 25 Apr 2019 11:52:55 +0200 Subject: [PATCH] Add vacation command --- README.md | 3 + cmd/vacation.go | 158 ++++++++++++++++++++++++++++++++++++++++ config/config.go | 10 +++ domain/datetime.go | 20 ++++- domain/datetime_test.go | 66 +++++++++++++++++ domain/time_entry.go | 2 + mite/time_entry.go | 6 ++ mite/time_entry_test.go | 7 +- 8 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 cmd/vacation.go diff --git a/README.md b/README.md index c8b8258..3f5e503 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ $PATH (or %PATH% on windows). 2. run `mite config activity."your activity name here".projectId="the project id"` 3. run `mite config activity."your activity name here".serviceId= user config, if not set explain how + }) + if err != nil { + return err + } + + today := domain.Today() + var minutesInYear int + var minutesInPast int + var minutesInFuture int + for _, entry := range entries { + minutesInYear += entry.Minutes.Value() + + if entry.Date.Before(today) { + minutesInPast += entry.Minutes.Value() + } else { + minutesInFuture += entry.Minutes.Value() + } + } + + var daysInYear = domain.MinutesAsDays(minutesInYear, fullVacationDayDuration) + var daysInPast = domain.MinutesAsDays(minutesInPast, fullVacationDayDuration) + var daysInFuture = domain.MinutesAsDays(minutesInFuture, fullVacationDayDuration) + var daysUnplanned = 28 - daysInYear // => user config, if not set explain how + + if vacationDetailsverbose { + fmt.Printf("Vacation statistics of %d:\n"+ + " - total: %d days\n"+ + "---------------------\n"+ + " - booked: %.1f days\n"+ + " - taken: %.1f days\n"+ + " - planned: %.1f days\n"+ + " - unplanned: %.1f days\n", + domain.ThisYear(), + 28, + daysInYear, + daysInPast, + daysInFuture, + daysUnplanned) + } else { + fmt.Printf("Vacation statistics of %d:\n"+ + " - booked: %.1f days\n"+ + " - unplanned: %.1f days\n", + domain.ThisYear(), + daysInYear, + daysUnplanned) + } + + return nil + }, +} + +var vacationCreateCommand = &cobra.Command{ + Use: "create", + Short: "creates a vacation entry (WIP: currently this command creates a vacation day only for today)", + RunE: func(cmd *cobra.Command, args []string) error { + vacationActivity := application.Conf.GetVacation() + + if vacationActivity.ProjectId == "" || vacationActivity.ServiceId == "" { + return errors.New(textProjectOrServiceNotConfigured) + } + + projectId, err := strconv.Atoi(vacationActivity.ProjectId) + if err != nil { + return errors.New(textProjectOrServiceNotConfigured) + } + + serviceId, err := strconv.Atoi(vacationActivity.ServiceId) + if err != nil { + return errors.New(textProjectOrServiceNotConfigured) + } + + projectIdForVacation := domain.NewProjectId(projectId) + serviceIdForVacation := domain.NewServiceId(serviceId) + today := domain.Today() + + minutes := domain.NewMinutesFromHours(fullVacationDayDuration) + if vacationHalfDay { + minutes = domain.NewMinutesFromHours(halfVacationDayDuration) + } + + var dates []domain.LocalDate + dates = append(dates, today) + + for _, date := range dates { + timeEntry := domain.TimeEntryCommand{ + Date: &date, + Minutes: &minutes, + Note: vacationNote, + ProjectId: projectIdForVacation, + ServiceId: serviceIdForVacation, + } + + _, err := application.MiteApi.CreateTimeEntry(&timeEntry) + if err != nil { + return err + } + } + + return nil + }, +} diff --git a/config/config.go b/config/config.go index 218b262..0edda37 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ type Config interface { GetApiKey() string GetActivity(activity string) Activity GetDisplayLocation() *time.Location + GetVacation() Activity Get(key string) string Set(key string, value string) PrintAll() @@ -60,6 +61,15 @@ func (c *config) GetActivity(activity string) Activity { } } +func (c *config) GetVacation() Activity { + projectId := c.Get("vacation.projectId") + serviceId := c.Get("vacation.serviceId") + return Activity{ + ProjectId: projectId, + ServiceId: serviceId, + } +} + func (c *config) GetDisplayLocation() *time.Location { s := c.Get("display.location") if s == "" { diff --git a/domain/datetime.go b/domain/datetime.go index b48f290..0d04491 100644 --- a/domain/datetime.go +++ b/domain/datetime.go @@ -12,6 +12,10 @@ type LocalDate struct { time time.Time } +func (d *LocalDate) Before(b LocalDate) bool { + return d.time.Before(b.time) +} + func NewLocalDate(t time.Time) LocalDate { return LocalDate{time: t} } @@ -20,6 +24,10 @@ func Today() LocalDate { return NewLocalDate(time.Now().Local()) } +func ThisYear() int { + return time.Now().Year() +} + func ParseLocalDate(s string) (LocalDate, error) { t, err := time.ParseInLocation(ISO8601, s, time.Local) if err != nil { @@ -41,8 +49,12 @@ type Minutes struct { duration time.Duration } -func NewMinutes(i int) Minutes { - return Minutes{duration: time.Duration(i) * time.Minute} +func NewMinutes(minutes int) Minutes { + return Minutes{duration: time.Duration(minutes) * time.Minute} +} + +func NewMinutesFromHours(hours int) Minutes { + return Minutes{duration: time.Duration(hours*60) * time.Minute} } func ParseMinutes(s string) (Minutes, error) { @@ -61,3 +73,7 @@ func (m Minutes) Value() int { func (m Minutes) String() string { return strings.TrimSuffix(m.duration.String(), "0s") } + +func MinutesAsDays(minutes int, workingDayInHours float64) float64 { + return float64(minutes) / 60 / workingDayInHours +} diff --git a/domain/datetime_test.go b/domain/datetime_test.go index 5f4470e..fda9a68 100644 --- a/domain/datetime_test.go +++ b/domain/datetime_test.go @@ -14,6 +14,25 @@ func TestToday(t *testing.T) { assert.Equal(t, expected, actual) } +func TestBefore(t *testing.T) { + timeOlder := time.Date(1979, 10, 07, 14, 23, 17, 12, time.Local) + timeNewer := time.Date(2015, 10, 22, 12, 17, 19, 33, time.Local) + + older := domain.NewLocalDate(timeOlder) + newer := domain.NewLocalDate(timeNewer) + + assert.True(t, older.Before(newer)) + assert.False(t, newer.Before(older)) + assert.False(t, newer.Before(newer)) +} + +func TestThisYear(t *testing.T) { + expected := time.Now().Year() + actual := domain.ThisYear() + + assert.Equal(t, expected, actual) +} + func TestParseLocalDate(t *testing.T) { expected := domain.NewLocalDate(time.Date(1970, time.January, 1, 0, 0, 0, 0, time.Local)) actual, err := domain.ParseLocalDate("1970-01-01") @@ -71,3 +90,50 @@ func TestMinutes_String(t *testing.T) { assert.Equal(t, expected, actual) } + +func TestMinutesFromHours_Value(t *testing.T) { + expected := 60 + actual := domain.NewMinutesFromHours(1).Value() + + assert.Equal(t, expected, actual) + + expected = 480 + actual = domain.NewMinutesFromHours(8).Value() + + assert.Equal(t, expected, actual) +} + +func TestMinutesFromHours_String(t *testing.T) { + expected := "1h0m" + actual := domain.NewMinutesFromHours(1).String() + + assert.Equal(t, expected, actual) + + expected = "8h0m" + actual = domain.NewMinutesFromHours(8).String() + + assert.Equal(t, expected, actual) +} + +func TestMinutesAsDays(t *testing.T) { + workingDayInHours := 8.0 + minutes := 480 + expected := 1.0 + actual := domain.MinutesAsDays(minutes, workingDayInHours) + + assert.Equal(t, expected, actual) + + workingDayInHours = 8.0 + minutes = 240 + expected = 0.5 + actual = domain.MinutesAsDays(minutes, workingDayInHours) + + assert.Equal(t, expected, actual) + + workingDayInHours = 8.0 + minutes = 160 + expected = 0.3333333333333333 + actual = domain.MinutesAsDays(minutes, workingDayInHours) + + assert.Equal(t, expected, actual) +} diff --git a/domain/time_entry.go b/domain/time_entry.go index 850c156..f258293 100644 --- a/domain/time_entry.go +++ b/domain/time_entry.go @@ -56,9 +56,11 @@ type TimeEntryCommand struct { } type TimeEntryQuery struct { + At string From *LocalDate To *LocalDate Direction string + ServiceId ServiceId } type TimeEntryApi interface { diff --git a/mite/time_entry.go b/mite/time_entry.go index 8062ec7..2776826 100644 --- a/mite/time_entry.go +++ b/mite/time_entry.go @@ -27,6 +27,9 @@ func fromCommand(c *domain.TimeEntryCommand) *timeEntryRequest { func fromQuery(q *domain.TimeEntryQuery) url.Values { v := url.Values{} if q != nil { + if q.At != "" { + v.Add("at", q.At) + } if q.From != nil { v.Add("from", q.From.String()) } @@ -36,6 +39,9 @@ func fromQuery(q *domain.TimeEntryQuery) url.Values { if q.Direction != "" { v.Add("direction", q.Direction) } + if q.ServiceId != 0 { + v.Add("service_id", q.ServiceId.String()) + } } return v diff --git a/mite/time_entry_test.go b/mite/time_entry_test.go index 0234703..33676fb 100644 --- a/mite/time_entry_test.go +++ b/mite/time_entry_test.go @@ -109,9 +109,11 @@ func TestApi_TimeEntries_WithQuery(t *testing.T) { // when today := domain.Today() query := &domain.TimeEntryQuery{ + At: "this_year", From: &today, To: &today, Direction: "asc", + ServiceId: timeEntryObject.ServiceId, } timeEntries, err := api.TimeEntries(query) @@ -120,7 +122,10 @@ func TestApi_TimeEntries_WithQuery(t *testing.T) { assert.Equal(t, []*domain.TimeEntry{&timeEntryObject}, timeEntries) assert.Equal(t, http.MethodGet, rec.RequestMethod()) - assert.Equal(t, fmt.Sprintf("/time_entries.json?direction=%s&from=%s&to=%s", query.Direction, query.From, query.To), rec.RequestURI()) + assert.Equal(t, fmt.Sprintf( + "/time_entries.json?at=%s&direction=%s&from=%s&service_id=%s&to=%s", + query.At, query.Direction, query.From, query.ServiceId, query.To), + rec.RequestURI()) assert.Empty(t, rec.RequestContentType()) assert.Equal(t, testUserAgent, rec.RequestUserAgent()) assert.Equal(t, testApiKey, rec.RequestMiteKey())