Skip to content

Commit

Permalink
WIP: Use timezone from attendance in report
Browse files Browse the repository at this point in the history
  • Loading branch information
ccremer committed Jan 16, 2023
1 parent 45ccb06 commit 7ce6f22
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 4 deletions.
15 changes: 15 additions & 0 deletions pkg/odoo/timezone.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,18 @@ func (tz *TimeZone) IsEmpty() bool {
}
return false
}

// String returns the location name.
// Returns empty string if nil.
func (tz *TimeZone) String() string {
if tz == nil || tz.Location == nil {
return ""
}
return tz.Location.String()
}

// IsEqualTo returns true if the given TimeZone is equal to other.
// If both are nil, it returns true.
func (tz *TimeZone) IsEqualTo(other *TimeZone) bool {
return tz.String() == other.String()
}
62 changes: 62 additions & 0 deletions pkg/odoo/timezone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ import (
"github.com/stretchr/testify/require"
)

var (
zurichTZ *time.Location
vancouverTZ *time.Location
)

func init() {
zue, err := time.LoadLocation("Europe/Zurich")
if err != nil {
panic(err)
}
zurichTZ = zue
van, err := time.LoadLocation("America/Vancouver")
if err != nil {
panic(err)
}
vancouverTZ = van
}

func TestTimeZone_UnmarshalJSON(t *testing.T) {
tests := map[string]struct {
givenInput string
Expand Down Expand Up @@ -59,6 +77,50 @@ func TestTimeZone_MarshalJSON(t *testing.T) {
}
}

func TestTimeZone_IsEqualTo(t *testing.T) {
tests := map[string]struct {
givenTimeZoneA *TimeZone
givenTimeZoneB *TimeZone
expectedResult bool
}{
"BothNil": {
givenTimeZoneA: nil, givenTimeZoneB: nil,
expectedResult: true,
},
"BothNilNested": {
givenTimeZoneA: NewTimeZone(nil),
givenTimeZoneB: NewTimeZone(nil),
expectedResult: true,
},
"A_IsNil": {
givenTimeZoneA: nil,
givenTimeZoneB: NewTimeZone(vancouverTZ),
expectedResult: false,
},
"B_IsNil": {
givenTimeZoneA: NewTimeZone(vancouverTZ),
givenTimeZoneB: nil,
expectedResult: false,
},
"BothSame": {
givenTimeZoneA: NewTimeZone(zurichTZ),
givenTimeZoneB: NewTimeZone(zurichTZ),
expectedResult: true,
},
"A_NestedNil": {
givenTimeZoneA: NewTimeZone(nil),
givenTimeZoneB: NewTimeZone(zurichTZ),
expectedResult: false,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
actual := tc.givenTimeZoneA.IsEqualTo(tc.givenTimeZoneB)
assert.Equal(t, tc.expectedResult, actual, "zone not equal: zone A: %s, zone B: %s", tc.givenTimeZoneA, tc.givenTimeZoneB)
})
}
}

func mustLoadLocation(name string) *time.Location {
loc, err := time.LoadLocation(name)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions pkg/timesheet/dailysummary.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ func (s *DailySummary) ValidateTimesheetEntries() error {
return NewValidationError(s.Date, fmt.Errorf("the reasons for shift %s and %s should be equal: start %s (%s), end %s (%s)",
model.ActionSignIn, model.ActionSignOut, shift.Start.DateTime.Format(odoo.TimeFormat), shift.Start.Reason, shift.End.DateTime.Format(odoo.TimeFormat), shift.End.Reason))
}
if !shift.Start.Timezone.IsEqualTo(shift.End.Timezone) {
return NewValidationError(s.Date, fmt.Errorf("if given, explicit timezones for attendances in a shift must be equal: start %s (%s), end: %s (%s)",
shift.Start.DateTime.Format(odoo.TimeFormat), shift.Start.Timezone, shift.End.DateTime.Format(odoo.TimeFormat), shift.End.Timezone,
))
}
totalDuration += shiftDuration
}
if totalDuration > 24*time.Hour {
Expand Down
9 changes: 9 additions & 0 deletions pkg/timesheet/dailysummary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,15 @@ func TestDailySummary_ValidateTimesheetEntries(t *testing.T) {
},
expectedError: "the reasons for shift sign_in and sign_out should be equal: start 08:00:00 (), end 10:00:00 (Sick / Medical Consultation)",
},
"ExplicitTimezoneDifferent": {
givenShifts: []AttendanceShift{
{
Start: model.Attendance{DateTime: odoo.NewDate(2021, 01, 02, 8, 0, 0, time.UTC), Action: model.ActionSignIn},
End: model.Attendance{DateTime: odoo.NewDate(2021, 01, 02, 18, 0, 0, time.UTC), Action: model.ActionSignIn, Timezone: odoo.NewTimeZone(vancouverTZ)},
},
},
expectedError: "if given, explicit timezones for attendances in a shift must be equal: start 08:00:00 (), end: 18:00:00 (America/Vancouver)",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand Down
4 changes: 3 additions & 1 deletion pkg/timesheet/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,20 @@ func (r *ReportBuilder) getTimeZone() *time.Location {
}

func (r *ReportBuilder) addAttendancesToDailyShifts(attendances model.AttendanceList, dailies []*DailySummary) {
tz := r.getTimeZone()
monthTz := r.getTimeZone()
dailyMap := make(map[string]*DailySummary, len(dailies))
for _, dailySummary := range dailies {
dailyMap[dailySummary.Date.Format(odoo.DateFormat)] = dailySummary
}

for _, attendance := range attendances.Items {
tz := attendance.Timezone.LocationOrDefault(monthTz)
date := attendance.DateTime.In(tz)
daily, exists := dailyMap[date.Format(odoo.DateFormat)]
if !exists {
continue // irrelevant attendance
}
daily.Date = daily.Date.In(tz) // Update the timezone of the day
var shift AttendanceShift
shiftCount := len(daily.Shifts)
newShift := false
Expand Down
9 changes: 6 additions & 3 deletions pkg/timesheet/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,11 @@ func TestReportBuilder_CalculateReport(t *testing.T) {
{DateTime: odoo.NewDate(2021, 1, 5, 10, 0, 0, zurichTZ), Action: model.ActionSignIn},
{DateTime: odoo.NewDate(2021, 1, 5, 17, 0, 0, zurichTZ), Action: model.ActionSignOut}, // 7h worked

{DateTime: odoo.NewDate(2021, 1, 7, 8, 0, 0, zurichTZ), Action: model.ActionSignIn},
{DateTime: odoo.NewDate(2021, 1, 7, 17, 5, 0, zurichTZ), Action: model.ActionSignOut}, // faked signed out, still working though
{DateTime: odoo.NewDate(2021, 1, 7, 10, 0, 0, vancouverTZ), Action: model.ActionSignIn, Timezone: odoo.NewTimeZone(vancouverTZ)},
{DateTime: odoo.NewDate(2021, 1, 7, 18, 0, 0, vancouverTZ), Action: model.ActionSignIn, Timezone: odoo.NewTimeZone(vancouverTZ)}, // 8h worked

{DateTime: odoo.NewDate(2021, 1, 8, 8, 0, 0, zurichTZ), Action: model.ActionSignIn},
{DateTime: odoo.NewDate(2021, 1, 8, 17, 5, 0, zurichTZ), Action: model.ActionSignOut}, // faked signed out, still working though
}}
givenLeaves := odoo.List[model.Leave]{Items: []model.Leave{
{DateFrom: odoo.NewDate(2021, 01, 06, 0, 0, 0, zurichTZ), DateTo: odoo.NewDate(2021, 01, 06, 23, 59, 0, zurichTZ), Type: &model.LeaveType{Name: TypeLegalLeavesPrefix}, State: StateApproved},
Expand All @@ -463,7 +466,7 @@ func TestReportBuilder_CalculateReport(t *testing.T) {
report, err := b.CalculateReport(start, end)
assert.NoError(t, err)
assert.Equal(t, report.Employee.Name, givenEmployee.Name, "employee name")
assert.Equal(t, ((9+3+7+9)*time.Hour)+(5*time.Minute), report.Summary.TotalWorkedTime, "total worked time")
assert.Equal(t, ((9+3+7+9+8)*time.Hour)+(5*time.Minute), report.Summary.TotalWorkedTime, "total worked time")
assert.Equal(t, ((1+3+1)*time.Hour)+(5*time.Minute), report.Summary.TotalOvertime, "total over time")
assert.Equal(t, 1.0, report.Summary.TotalLeave, "total leave")
assert.Equal(t, (8+2)*time.Hour, report.Summary.TotalExcusedTime, "total excused time")
Expand Down

0 comments on commit 7ce6f22

Please sign in to comment.