*** PS_HOURS.3_1 July |1 |2 |3 |4 |5 |6 |7 Name |Total|Total|Total|Total|Total|Total|Total Abram, John |1.00 | | | |3.00 |3.00 |3.00 Agara, Hara |2.00 | | | |2.00 |2.00 |3.00 Alsi, Idria |2.00 | | | |2.00 |2.00 |2.00 Amon, Char |2.00 | | | |3.00 |3.00 |2.00 Base, Adron |3.00 | | | |3.00 |2.00 |3.00
Screenshot:
Data is entered into this spreadsheet manually:
- Job coaches write down a clients start time/stop time on a piece of paper
- This paper is then brought to the person who works in our central office, and she takes all the pieces of paper (probably about 8-9 pieces of paper for a week and makes a hand written master sheet which looks like above (minus the quarter hours), but is 55 rows deep and 31 columns wide. If for instance Hara Agara works on July 13th then that box is filled in with the amount of hours he worked for that day.
- I input her master sheet into this spreadsheet. I could easily have her do it, but at least the theory now is that the agency still needs to maintain paper copies.
I have the following functional macro based on the above. There's a list of 55 names, and a record of the hours they've worked for every day of the month (31 total columns) starting from cell C going to cell BK (skipping by 2's); cell D going to cell BJ (also skipping by 2's) is a record of the total amount of hours they've worked translated into quarter hour units (i.e. 2 hours worked = 8 hour quarter units). I use this for reporting purposes.
At the far right the total amount of hours a person worked is summed up, and is then reported on a summary page. Each month is given it's own worksheet - IE there's a record for June, July, August, with the name of the worksheet being its NameOfMonth.year
.
This data is used to generate the following report:
Report for John Abram a b c d e f g h i j k l m n o->AF 6 |Date |Staff|Present|ratio|start|stop |total|minwag|hab#|s1|s2|s3|s4|s5|... 7 |07/01| BP | Yes | 1:1 | 8:00| 9:00|1 | yes |2 |x | |x | | | xx 8 |07/02| BP | Yes | 1:1 | | |0 | | | | | | | | 9 |07/03| BP | Yes | 1:1 | | |0 | | | | | | | | 10|07/04| BP | Yes | 1:1 | | |0 | | | | | | | | 11|07/05| BP | Yes | 1:1 | 8:00|11:00|3 | yes |2 |x | |x | | | xx 12|07/06| BP | Yes | 1:1 | 8:00|11:00|3 | yes |2 | | x|x | | | xx 13|07/07| BP | Yes | 1:1 | 8:00|11:00|3 | yes |2 |x | |x | | | xx
Here's what happens:
- In column g there is an index/match function that is keyed to find the data from the corresponding worksheet in PS_Hours.3_1. More on how the spreadsheet knows which worksheet later
- Column E is set to 8:00 AM, and then column F adds the total amount of time (column g) to column f to get a finish time. In the case of John Abram for 07/01, it's 1 hour of work; so start time and end time are 1 hour a part
- Each worksheet contains 7 days of information, in this case from 07/01-07/07. Some of the information is static (columns b,c,d, and what is in columns 0 through AF), but only gets entered if the value in column g of that row is greater than 0
- Column i of each row is a random number between 1 and 3 only if column g of that row is greater than 0.
- Cell j/k/l are randomly filled based on column i of each row.
Now on to the macro portion:
This needs to be done for each person (55 total) so I had to automate the copy/pasting of it:
Sub copy()
'
' copy Macro
'
' Keyboard Shortcut: Ctrl+d
'
Dim dValue, mIndex, mName As Integer
'copy the worksheet to a new worksheet and format it
Range("A1:AF19").Select
Range("AA19").Activate
Selection.copy
Sheets.Add After:=Sheets(Sheets.Count)
ActiveSheet.Paste
Columns("A:A").ColumnWidth = 6
Columns("B:B").ColumnWidth = 8
Columns("C:C").ColumnWidth = 10
Columns("D:D").ColumnWidth = 10
Columns("E:E").ColumnWidth = 8
Columns("F:F").ColumnWidth = 8
Columns("G:G").ColumnWidth = 6
Columns("H:H").ColumnWidth = 6
Columns("I:I").ColumnWidth = 6
Columns("J:J").ColumnWidth = 10
Columns("K:K").ColumnWidth = 10
Columns("L:L").ColumnWidth = 10
Columns("M:M").ColumnWidth = 1
Columns("N:N").ColumnWidth = 1
Columns("O:O").ColumnWidth = 5
Columns("P:P").ColumnWidth = 5
Columns("Q:Q").ColumnWidth = 5
Columns("R:R").ColumnWidth = 5
Columns("S:S").ColumnWidth = 5
Columns("S:S").ColumnWidth = 5
Columns("T:T").ColumnWidth = 5
Columns("U:U").ColumnWidth = 5
Columns("V:V").ColumnWidth = 5
Columns("W:W").ColumnWidth = 5
Columns("X:X").ColumnWidth = 5
Columns("Y:Y").ColumnWidth = 5
Columns("Z:Z").ColumnWidth = 5
Columns("AA:AA").ColumnWidth = 5
Columns("AB:AB").ColumnWidth = 5
Columns("AC:AC").ColumnWidth = 5
Columns("AD:AD").ColumnWidth = 5
Columns("AE:AE").ColumnWidth = 5
Columns("AF:AF").ColumnWidth = 5
Rows("5:5").RowHeight = 55
Rows("7:7").RowHeight = 50
Rows("8:8").RowHeight = 50
Rows("9:9").RowHeight = 50
Rows("10:10").RowHeight = 50
Rows("11:11").RowHeight = 50
Rows("12:12").RowHeight = 50
Rows("13:13").RowHeight = 50
Rows("19:19").RowHeight = 50
Rows("18:18").RowHeight = 50
Rows("17:17").RowHeight = 50
Rows("14:14").RowHeight = 70
Application.CutCopyMode = False
Application.PrintCommunication = False
With ActiveSheet.PageSetup
.PrintTitleRows = ""
.PrintTitleColumns = ""
End With
Application.PrintCommunication = True
ActiveSheet.PageSetup.PrintArea = ""
Application.PrintCommunication = False
With ActiveSheet.PageSetup
.LeftHeader = ""
.CenterHeader = ""
.RightHeader = ""
.LeftFooter = ""
.CenterFooter = ""
.RightFooter = ""
.LeftMargin = Application.InchesToPoints(0.7)
.RightMargin = Application.InchesToPoints(0.7)
.TopMargin = Application.InchesToPoints(0.75)
.BottomMargin = Application.InchesToPoints(0.75)
.HeaderMargin = Application.InchesToPoints(0.3)
.FooterMargin = Application.InchesToPoints(0.3)
.PrintHeadings = False
.PrintGridlines = False
.PrintComments = xlPrintNoComments
.PrintQuality = 600
.CenterHorizontally = False
.CenterVertically = False
.Orientation = xlLandscape
.Draft = False
.PaperSize = xlPaperLetter
.FirstPageNumber = xlAutomatic
.Order = xlDownThenOver
.BlackAndWhite = False
.Zoom = 100
.PrintErrors = xlPrintErrorsDisplayed
.OddAndEvenPagesHeaderFooter = False
.DifferentFirstPageHeaderFooter = False
.ScaleWithDocHeaderFooter = True
.AlignMarginsHeaderFooter = True
.EvenPage.LeftHeader.Text = ""
.EvenPage.CenterHeader.Text = ""
.EvenPage.RightHeader.Text = ""
.EvenPage.LeftFooter.Text = ""
.EvenPage.CenterFooter.Text = ""
.EvenPage.RightFooter.Text = ""
.FirstPage.LeftHeader.Text = ""
.FirstPage.CenterHeader.Text = ""
.FirstPage.RightHeader.Text = ""
.FirstPage.LeftFooter.Text = ""
.FirstPage.CenterFooter.Text = ""
.FirstPage.RightFooter.Text = ""
End With
Application.PrintCommunication = True
ActiveWindow.View = xlPageBreakPreview
ActiveWindow.SmallScroll Down:=-12
ActiveWindow.Zoom = 85
ActiveWindow.Zoom = 100
ActiveWindow.Zoom = 115
ActiveSheet.VPageBreaks(1).DragOff Direction:=xlToRight, RegionIndex:=1
'get the current date value and month value
dValue = Int(Right(Cells(7, 1), 2))
mValue = Int(Left(Cells(7, 1), 2))
If (31 - dValue) > 7 Then
Call updateDates
Else
Call newMonth(mValue)
End If
End Sub
This sub takes the current spreadsheet that's active when ctrl-d is pressed, makes a new spreadsheet, formats it. Then it examines the last two digits of the date in cell a7; if 31-that number is greater than 7, the macro knows that it can safely move to the next set of 7 numbers; if it's less than it knows that the new month is coming up.
Sub updateDates()
Dim dValue, n_mValue, yValue As Integer
n_mValue = Int(Left(Cells(2, 26), 2)) + 1
yValue = Int(Right(Cells(2, 26), 4))
'Calculation for the first day of the following month
date_next_month = DateSerial(yValue, n_mValue, 1)
'Date of the last day
last_day_month = date_next_month - 1
'Number for the last day of month (= last day)
nb_days = Day(last_day_month)
'update the day/month values in cells 7,1 through 13,1
For i = 0 To 6
dValue = Day(Cells(7 + i, 1))
If dValue + 7 <= nb_days Then
Cells(7 + i, 1) = format(DateAdd("d", 7, Cells(7 + i, 1)), "mm/dd")
Else
For j = 1 To 32
Cells(7 + i, j) = ""
Next j
End If
Next i
Call paperwork
End Sub
This sub is only called when 31 minus the right two digits in cell a7 are greater than 7 (or when it's not the last week in the current month).
It updates dates based on the first day of the next month and previous values, and it also wipes the values in each spreadsheet clean.
Sub newMonth(mValue)
Dim mName1, mName2, CurYear As String
For i = 0 To 6
Cells(7 + i, 1) = format(DateSerial(2016, mValue + 1, i + 1), "mm/dd")
Next i
Range("B7:AF7").Select
Selection.copy
Range("B8").Select
ActiveSheet.Paste
Range("B9").Select
ActiveSheet.Paste
Range("B10").Select
ActiveSheet.Paste
Range("B11").Select
ActiveSheet.Paste
Range("B12").Select
ActiveSheet.Paste
Range("B13").Select
ActiveSheet.Paste
Application.CutCopyMode = False
Range("B7:AF13").Select
'mValue is current month
mName1 = Switch(mValue = 1, "January", mValue = 2, "February", mValue = 3, "March", mValue = 4, "April", mValue = 5, "May", mValue = 6, "June", mValue = 7, "July", mValue = 8, "August", mValue = 9, "September", mValue = 10, "October", mValue = 11, "November", mValue = 12, "December")
mValue = mValue + 1
mName2 = Switch(mValue = 1, "January", mValue = 2, "February", mValue = 3, "March", mValue = 4, "April", mValue = 5, "May", mValue = 6, "June", mValue = 7, "July", mValue = 8, "August", mValue = 9, "September", mValue = 10, "October", mValue = 11, "November", mValue = 12, "December")
mName2 = mName2 & "." & Right(Cells(2, 26), 4)
Selection.Replace What:=mName1, Replacement:=mName2, LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Call paperwork
End Sub
Basically, what do I do when I get to the end of the month? I have to start a new month (from the 8's).
If called, this macro finds the current month, adds 1, and replaces all occurrences of the previous month with the next month, and updates accordingly. Then calls paperwork sub.
Sub paperwork()
'
' paperwork Macro
'
'
Dim numHabs, rando As Integer
'first have to clear out anything that was previously written in the middle section
For i = 0 To 7
If (Cells(7 + i, 7) > 0) Then
Cells(7 + i, 9) = Int(3 * Rnd() + 1)
Else
Cells(7 + i, 9) = ""
End If
For j = 0 To 22
Cells(7 + i, 10 + j) = ""
Next j
Next i
'put some new stuff in
For k = 0 To 4
For i = 0 To 7
If Cells((7 + 20 * k) + i, 9) > 0 Then
numHabs = Cells(7 + i, 9)
Cells(7 + i, 20) = "X"
Cells(7 + i, 25) = "X"
End If
' MsgBox ("num habs" = numHabs)
While numHabs > 0
rando = Int(3 * Rnd() + 10)
If Cells((7 + 20 * k) + i, rando).Value = "" Then
Cells((7 + 20 * k) + i, rando) = "x"
numHabs = numHabs - 1
End If
Wend
Next i
Next k
Call facetoface
End Sub
This sub fills cells j/k/l on the sheet dynamically. Important, because this shows that whoever is responsible for overseeing work that day actually did something that day.
Sub facetoface()
Dim pctTime, tempTime, tempWrk As Integer
Dim base, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10 As String
Dim Services As New Collection
Dim Name() As String
base = "Purpose and/or Response to Services Provided: "
Name = Split(Cells(3, 4), ", ", -1)
'MsgBox (Name(0))
Services.Add "% of time spent working with " & Name(1) & " on developing job skills and ability to cope with the demands of the work place; "
Services.Add "% of time spent working with " & Name(1) & " on developing soft skills and ability to speaking properly with others at the work site; "
Services.Add "% of time spent working with " & Name(1) & " on developing good time-keeping habits; "
Services.Add "% of time spent working with " & Name(1) & " on issues that were independently raised to me; "
Services.Add "% of time spent working with " & Name(1) & " on arriving and leaving work properly; "
Services.Add "% of time spent working with " & Name(1) & " to help them understand job goals at the work site; "
Services.Add "% of time spent working with " & Name(1) & " on developing better habits; "
Services.Add "% of time spent working with " & Name(1) & " on issues that were raised by a Medicaid Service Coordinator; "
Services.Add "% of time spent working with " & Name(1) & " on finding new work opportunities; "
Services.Add "% of time spent working with " & Name(1) & " on job training; "
' MsgBox (Services.Item(2))
For i = 0 To 2
If i < 2 Then
tempTime = Round((Int(40 * Rnd() + 10) / 10), 0) * 10
pctTime = pctTime + tempTime
Else
' MsgBox ("PctTime" & pctTime & "TempTime" & tempTime)
tempTime = 100 - pctTime
End If
tempWrk = Int(8 * Rnd() + 1)
base = base & " " & tempTime & Services.Item(tempWrk)
Services.Remove tempWrk
Next i
' MsgBox (base)
If Application.WorksheetFunction.Sum(Cells(7, 7), Cells(8, 7), Cells(9, 7), Cells(10, 7), , Cells(11, 7), Cells(12, 7), Cells(13, 7)) = 0 Then
base = "Purpose and/or Response to Services Provided: " & Name(1) & " did not work during this period."
Else
base = Left(base, Len(base) - 2)
End If
Cells(14, 1) = base
'find the range of dates for the period
str1 = Cells(7, 1)
' MsgBox (str1)
For i = 1 To 6
'MsgBox (Cells(7 + i, 1))
If Cells(7 + i, 1).Value = "" Then
str2 = Cells(6 + i, 1)
' MsgBox ("i = " & i & " & str2 = " & str2)
Exit For
ElseIf i = 6 Then
str2 = Cells(13, 1)
Exit For
End If
Next i
Cells(2, 26) = str1 & "/" & Year(Now()) & " - " & str2 & "/" & Year(Now())
ActiveSheet.Name = Left(str1, 2) & "." & Right(str1, 2) & "-" & Left(str2, 2) & "." & Right(str2, 2)
End Sub
This sub finds 3 randomly selected strings from 10 pre-generated strings to make an output string, which is then placed on cell a14. A sample output would look like.
Purpose and/or response to services provided: 50% of time spent working with John to help them understand job goals at the work site; 40% of time spent working with John on issues that were independently raised to me; 10% of time spent working with John on developing job skills and ability to cope with the demands of the work place.
I work in the human services field and the paperwork requirement can be overbearing. Case notes needs to be written up regularly which I do not want to do, so I decided to try my hand at using Excel/VBA to write them up. Basically, the way this is written, all I need to do is press ctrlD. As long as sufficient data exists in PS_HOURS.3_1
the macro will generate and format a sufficient case note for that period.
Some issues/hopes for future improvement:
- Haven't yet had to switch years
- It might be better to do this with a dynamic way to enter times; so each person doesn't always start at 8:00 AM, but can start whenever depending upon how they are entered into the system.
- It might be better to do this with a database; I don't know the maximum amount of worksheets that excel can have (probably easy to look it up), but at the very least a year of case notes will have 52 work sheets, so it might run into a limit.
- Overall it seems very clunky and trying to generate an output string based on percentages is kind of awkward. I couldn't figure out a better way to dynamically create an output, with the same sort of human service-y feel.
- Ideally, I would like to develop some sort of app which could write to a database from either a phone or a computer with such information like name, time start, time end, total, signature, etc; and then have excel access that database so that a report such as this could be dynamically entered. So for instance, all I would have to do is specify I want range 7/1/2015-6/30/2016 and then a number of reports such as the one above would be generated.
Are there any other programming issues / roadblocks / limitations on the robustness of the macro / suggestions for improvement?