Creating inexpensive timesheet solution
Overview of solution
Using VBA in excel, one can do almost everything in automated fashion, saving great amount of time. This is based on the fact that managers today already do everything on excel, though it is all manual.
This solution, considers, independent excel sheets maintained by individuals to fill timesheets. A coordinator, who pulls data from these individual sheets and populates central database.
Globals and Initialization
Globals Global shtActiveTimesheet As Worksheet Global wbkActiveTS As Workbook Global wbkTI As Workbook Global shtTIDb As Worksheet Global shtTICfg As Worksheet 'initialization Sub initTsImport() Dim shtCfg As Worksheet Set wbkTI = ActiveWorkbook Set shtTIDb = ActiveWorkbook.Worksheets("db") Set shtTICfg = wbkTI.Worksheets("cfg") End Sub
Configuration
This sheet is meant to maintain variables that user can chose to change without going into VBA code.
Support Functions
'get name of the timesheet file for an employee and for a particular week Function getTimesheetName(empid As Integer, weekOffset As Integer) As String ' 0 for this week ' -1 for last week ' -2 for last to last week d = getMonday(weekOffset) getTimesheetName = Format(empid, "0###") getTimesheetName = getTimesheetName & "_" & Format(Year(d)) getTimesheetName = getTimesheetName & "_" & Format(Month(d), "0#") getTimesheetName = getTimesheetName & "_" & Format(Day(d), "0#") getTimesheetName = getTimesheetName & "_timesheet.xlsx" End Function
'get date for the monday of this week, of last week, or last to last week ' can be further enhanced to seek any week backward or in future Function getMonday(offset As Integer) As Date If (weekOffset = 0) Then getMonday = Now() - Weekday(Now(), 3) ElseIf (weekOffset = -1) Then getMonday = Now() - Weekday(Now(), 3) - 7 ElseIf (weekOffset = -2) Then getMonday = Now() - Weekday(Now(), 3) - 14 End If getMonday = DateValue(Format(getMonday, "dd/mm/yyyy")) End Function
Individual Timesheets (IT)
Individual timesheets need to have following information
- Starting date
- Days of the week
- Employee Id
- Project Id
- Task (optional - rather leave it, why complicate)
- Hours Spent
File name itself shall suggest the date of the timesheet and also the employee id. Latter value is maintained inside the sheet, though it does not change.
There is no automation required within this sheet. It just provides information to be collated by someone else.
Integrated Timesheet
Integrated timesheet (ITS) has two sheets to work through.
- Database of each entries available through individual timesheets - (db)
- Tracking sheet to capture who has submitted, how many hours, in total (required to do followup for those who have not filled it yet) - (timesheet_tracking)
Macro and it’s explanation
Iterate through every cell of individual and each date, if the cells in tracking sheet is already updated, than do not fetch / update value in “DB”
Look through configuration (cfg) sheet, for each team member timesheet path and open timesheet path for current week. Get date of monday for current week. If available, iterate through each cell of this timesheet and create an entry into db for non-zero and non-blank cells. Also add to the timesheet_tracking sheet.
There is an option to extend it’s functionality to fetch information from last-n weeks.
Sub importTimesheet(ts As Worksheet, emp As Integer, dt As Date) Dim addr As Range, addr2 As Range, li As Range, ndt As Date, hrs As Double Set li = shtTIDb.Range("A1:A10000").Find("Last Import") Set addr = shtTIDb.Range(shtTIDb.Cells(li.Row() + 1, 1), shtTIDb.Cells(li.Row() + 40000, 1)).Find("") Set addr2 = ts.Range("A1:A10000").Find("#") rowIdx = addr.Row() If (Not addr Is Nothing) Then For Each prj In ts.Range(ts.Cells(addr2.offset(1, 3).Row(), 3), ts.Cells(addr2.offset(1, 3).Row() + 10000, 3)) If (prj = "") Then Exit For tp = prj.offset(0, -1).Value For Each dy In ts.Range(ts.Cells(prj.Row(), 4), ts.Cells(prj.Row(), 10)) If (dy.Value <> 0 And dy.Value <> "") Then ndt = DateValue(Format(dt, "dd/mm/yyyy")) + dy.Column() - 4 hrs = dy.Value shtTIDb.Cells(rowIdx, 1).Value = ndt shtTIDb.Cells(rowIdx, 2).Value = emp shtTIDb.Cells(rowIdx, 3).Value = prj.Value shtTIDb.Cells(rowIdx, 4).Value = hrs ab = updateHoursToTracking(ndt, emp, hrs) rowIdx = rowIdx + 1 End If Next dy Next prj End If li.offset(0, 1).Value = Now() End Sub
Here is the main code that triggers all. This is the macro which is assigned to the button.
Dim dt As Date, emp As Integer Dim addr As Range, wbkTs As Workbook, shtTs As Worksheet initTsImport Set addr = shtTICfg.Range("C3:C1000").Find("Employee Timesheets") For Each ts In shtTICfg.Range(shtTICfg.Cells(addr.Row() + 1, 3), shtTICfg.Cells(addr.Row() + 1000, 3)) If (ts = "") Then Exit For shtName = getTimesheetName(ts.Value, 0) dt = getMonday(0) On Error GoTo error_opening If (Not isTimesheetFilled(ts.Value, dt)) Then If Dir(ts.offset(0, 2) & shtName) <> "" Then Set wbkTs = Workbooks.Open(ts.offset(0, 2) & shtName, True) Set shtTs = wbkTs.Worksheets("timesheet") importTimesheet ts:=shtTs, emp:=ts.Value, dt:=dt wbkTs.Close Else MsgBox ("File not found: " & ts.offset(0, 2) & shtName) End If End If GoTo continue error_opening: MsgBox ("Error in opening file" & Err.Number & " " & Err.Description) continue: Next ts End Sub
Timesheet Tracking(TS)
TS helps to ensure timely submission of timesheets and also have birds eye-view on who is working and how much. A blank cells implies that the timesheet for that particular cell is not yet filled up.
'check if the timesheet for the week is filled Function isTimesheetFilled(emp As Integer, dt As Date) As Boolean Dim shtTracking As Worksheet, addr As Range, addr2 As Range Set shtTracking = wbkTI.Worksheets("timesheet_tracking") Set addr = shtTracking.Range("A2:A1000").Find(dt) Set addr2 = shtTracking.Range("A2:A1000").Find("Date") Set addr2 = shtTracking.Range(shtTracking.Cells(addr2.Row(), 2), shtTracking.Cells(addr2.Row(), 100)).Find(emp) isTimesheetFilled = False If ((Not addr2 Is Nothing) And (Not addr Is Nothing)) Then If (shtTracking.Cells(addr.Row(), addr2.Column()).Value <> "") Then isTimesheetFilled = True End If End If End Function
Function updateHoursToTracking(dt As Date, emp As Integer, hrs As Double) As Double Dim shtTracking As Worksheet, addr As Range, addr2 As Range Set shtTracking = wbkTI.Worksheets("timesheet_tracking") Set addr = shtTracking.Range("A2:A1000").Find(dt) Set addr2 = shtTracking.Range("A2:A1000").Find("Date") Set addr2 = shtTracking.Range(shtTracking.Cells(addr2.Row(), 2), shtTracking.Cells(addr2.Row(), 100)).Find(emp) If ((Not addr2 Is Nothing) And (Not addr Is Nothing)) Then If (shtTracking.Cells(addr.Row(), addr2.Column()).Formula = "") Then shtTracking.Cells(addr.Row(), addr2.Column()).Formula = "=" & hrs Else shtTracking.Cells(addr.Row(), addr2.Column()).Formula = shtTracking.Cells(addr.Row(), addr2.Column()).Formula & "+" & hrs End If End If End Function
Applications / Extensions
This is a small implementation of what goes in the mind of manager who is concerned about recording time spent on different project / activities by team members. For individuals, it is easy to fill and enables them to maintain records which they can always refer back.
Having done this, one can easily calculate hours spent on every project, link it with salaries of individuals and arrive at costs incurred on project. Salaries being the bigges component of an organization, gets accounted for appropriately, converting indirect costs into direct costs, without the need of SAP.