VBA Code to Populate Balance Sheet from Trial Balance Data - excel

Good afternoon folks,
I've been a long time reader but first time poster. I am doing a project that requires me to take trial balance data in Excel and format that data into a "balance sheet".
Basically I have the trial balance data in one worksheet ("Data") and the balance sheet template in another sheet ("Balance Sheet")
I need to populate the balance sheet from the ("Data") sheet to the ("Balance Sheet"). I am having trouble wrapping my head around how to do this
I have a first macro that I recorded that formats the trial balance data by account number and a second macro that sums together each group of accounts (ex. all cash accounts on are summed together on one line in the balance sheet).
But I am having trouble making this code robust and flexible, currently it is hard coded to the values in the balance sheet. How can I make this code flexible so that it populates correctly, (for example, if I added another "cash" account to the cash group, it would add that amount to the "cash" line in the balance sheet)
Here is the file if needed to look into it. Not a whole lot of code so any help would be greatly appreciated!
http://s000.tinyupload.com/?file_id=22382427361802516291
http://imgur.com/a/bYjUp

I haven't downloaded your project yet but it seems that what you need to do is create an array for each type of account. For simplicity, let's say you just have arrCash and arrLiability. You would then fill the arrays with each known gl code.or another way would be to keep a list of gl codes on a seperate spreadsheet. Now comes the fun part. You would loop through your excel spreadsheet and compare each code to the elements in your arrays. If the comparison equals true then add that amount to a one of your variables. If the comparison equals false then create a routine that redims the array the gl code needs to be added to then adds that gl code to the array. Or adding to that seperate spreadsheet. After adding the new gl code to the array you would need to add that amount to it's corresponding variable. After all calculations are completed, then you would update your balance sheet with the amoubts in the variables. Easy enough, right?

The following function accepts a comma delimited list of values (the value from column a in Data sheet) and will sum all rows in the data sheet that match the provided values.
eg: ?getSum("10300-000,10303-000") = 433094.74
Public Function getSum(ByVal Search As String) As Double
Dim Data As Worksheet: Set Data = ThisWorkbook.Worksheets("Data")
Dim List() As String: List = Split(Search, ",")
Dim ListSize As Integer: ListSize = UBound(List)
Dim Values() As Variant
Dim Row As Integer
Dim Rows As Integer
Dim Match As Integer
Dim Matched As Boolean
Dim Result As Double: Result = 0
Rows = Data.Range("A" & Data.Rows.Count).End(xlUp).Row
Values = Data.Range("A1", "C" & Rows).Value2
Row = 1
Do
Matched = False
For Match = 0 To ListSize
If Values(Row, 1) = List(Match) Then
Matched = True
Exit For
End If
Next Match
If Matched = True Then
Result = Result + CDbl(Values(Row, 3))
End If
If Row >= Rows Then
Exit Do
Else
Row = Row + 1
End If
Loop
getSum = Result
End Function
Updated to allow range of accounts instead of list
Public Function getSum2(ByVal sFirst As String, ByVal sLast As String) As Double
Dim Data As Worksheet: Set Data = ThisWorkbook.Worksheets("Data")
Dim Values() As Variant
Dim Row As Integer
Dim Rows As Integer
Dim First As Long: First = CLng(Left(sFirst, 5))
Dim Test As Long
Dim Last As Long: Last = CLng(Left(sLast, 5))
Dim Result As Double: Result = 0
Rows = Data.Range("A" & Data.Rows.Count).End(xlUp).Row
Values = Data.Range("A1", "C" & Rows).Value2
Row = 1
Do
Test = CLng(Left(Values(Row, 1), 5))
If Test >= First And Test <= Last Then
Result = Result + CDbl(Values(Row, 3))
End If
If Row >= Rows Then
Exit Do
Else
Row = Row + 1
End If
Loop
getSum2 = Result
End Function

Related

Create a Range Object from Multiple Areas Items

I have a function that aims to return the visible cells (as a range) after applying an autofilter to an inactive worksheet; the autofilter data is
represented by the range "filteredData" passed to the function. The returned range can then be looped through by the calling code obtaining various
values from the nth row.
I now understand that if the filtered data contains non-contiguous row groupings, only the first group of those rows is returned as a range, using
.SpecialCells(xlCellTypeVisible), and that each of those non-contiguous row groupings is represented by an item, all contained
by the same, single Areas collection - I think.
Is it possible to "convert" those area items into an overall range object? I have tried using the Address property of the item and UNION,
but this only seems to work for the first area item and seems to fail silently when attempting to add a second; no error occurs, but the row count of the
newRange remains unchanged.
Several other scripts are tied into this function and I would like to try to avoid a large re-write.
Any advice would be appreciated.
Thanks
Function getFilteredData(filteredData As Range) As Range
Dim areasData As Range
Dim areaCount As Long
Dim j As Long
Dim areaRg As Range
Dim sheetName As String
Dim newRange As Range
Dim itemAddress AS String
Dim itemRg AS Range
Set areasData = filteredData.Resize(filteredData.Rows.Count - 1, filteredData.Columns.Count).Offset(1).SpecialCells(xlCellTypeVisible)
sheetName = "'" & filteredData.Parent.Name & "'!"
areaCount = areasData.Areas.Count
For j = 1 To areaCount
'unsure if this can be treated as a range...possibly Area object
Set areaRg = areasData.Areas.item(j)
itemAddress = sheetName & areaRg.CurrentRegion.Address
Set itemRg = Range(itemAddress)
If j = 1 Then
Set newRange = itemRg
Else
Set newRange = Union(newRange, itemRg)
End If
Next j
Set getFilteredData = newRange
End Function
Given your use case of looping through the nth row you could use a utility function, e.g.
Function getRangeRowNum(data As Range, num As Long) As Range
If num < 1 Then num = 1
If data.Areas.Count = 1 Then
If num > data.Rows.Count Then
Set getRangeRowNum = data.Rows(data.Rows.Count)
Else
Set getRangeRowNum = data.Rows(num)
End If
Exit Function
End If
Dim i As Long, runRows As Long
For i = 1 To data.Areas.Count
runRows = runRows + data.Areas(i).Rows.Count
If runRows >= num Then Exit For
Next i
If i > data.Areas.Count Then 'Exit For not executed so return last actual row'
Set getRangeRowNum = data.Areas(i - 1).Rows(data.Areas(i - 1).Rows.Count)
Else
Set getRangeRowNum = data.Areas(i).Rows(num - (runRows - data.Areas(i).Rows.Count))
End If
End Function
Sub testFunction()
Dim i As Long, total As Range
Set total = Application.Union(Range("A5:H6"), Range("A9:H12"), Range("A15:H18"))
Debug.Print "Rows property of 'Total' returns " & total.Rows.Count
Debug.Print "Actual number of rows in 'Total' = " & total.Cells.Count / total.Columns.Count
For i = 1 To 10
Debug.Print getRangeRowNum(total, i).Address
Next i
End Sub
If you do intend to continue with filtering ranges though, I think some re-factoring will be inevitable.
An alternative you might consider is to pass an array between your various scripts, since an array is a properly contiguous structure.
Such an array could be constructed from an ADODB.Recordset object, e.g. this video illustrates data being read into one from a closed workbook (although it works just as well for the workbook you have open, provided all changes are saved, since it is the 'disk copy' that is queried).
With this approach you define your filter in SQL (so it could actually be more sophisticated than with AutoFilter), and the filtered results are what are read into the Recordset. The video illustrates getting data 'out of' the RecordSet, but this one shows how you can also transfer it to an array (although you probably will want to use Application.Transpose to have it in the expected form - you should also test thoroughly if you have long ranges, as Transpose didn't always work with more than 65,536 items).
This snippet illustrates how you can also create a RecordSet directly from a range, although I don't know that it's as efficient as the SQL approach. Regardless of how you populate the RecordSet, it has Filter and Sort properties (illustrated in the previous link) such that you can continue to manipulate the data directly in memory before generating the array required.

WorksheetFunction.Sum returning zero for numbers [duplicate]

This question already has answers here:
VBA WorksheetFunction.Sum doesn't work with array but works with range?
(2 answers)
Closed 3 years ago.
Clean up your code, kiddies. After much wailing and gnashing of teeth, the problem turned out to be the extra parentheses I had in my .Sum arguments. Big thanks to #BigBen and #JvdV.
The Problem: Worksheetfunction.Sum returns a sum of 0 for a dynamic range, but only for rows.count > 1 and only for currency-formatted reference data.
More Detail: I have a userform set up to scrape a reference workbook and return a different number into each of four different textboxes based on user input. On occasion the numbers will need to be a sum of several rows on the reference workbook. Using my code below, this works like a dream for every return textbox as long as the number of rows is 1, but returns 0 (or $0.00 as it were) for anything else. However, it works just fine in all circumstances for the one sum that is just an integer. The rest are formatted as currency.
What I've done: Using MsgBoxes I've verified that the dynamic range returns the correct addresses, i.e. all cells I want summed and that the numbers at those addresses are in fact numbers and not text (verified by a True return for IsNumber). I've tried using .Subtotal and .Aggregate to see if those might help, but I ran into Missing Object and other errors and ran away whimpering because I'm new to VBA.
The Code:
My basic logic is as follows: Search in every sheet of the reference (csrWorkbook) for textbox.value. Once found, measure the height of the merged area (I know I know, but the merging decision is made above my paygrade). Offset to the right to find 4 different related quantities. Sum these quantities if multiple rows exist. Return the sum to four different textboxes.
Help!
Private Sub ScrapeButton_Click()
'Enter search term into first TB
'click search button
'result DOES(!!) appear in second TB
'Variables
Dim csrWorkbook As Workbook
Dim refWorkbook As Workbook
Dim refVariables As Worksheet
Dim csrFilePath As String
Dim csrFileName As String
Dim slinAddress As String
Dim ws As Worksheet
Dim sheetCount As Long
Dim rowCount As Long
Dim slinCell As Excel.Range
Dim quantCells As Excel.Range
Dim costCells As Excel.Range
Dim feeCells As Excel.Range
Dim totalCells As Excel.Range
Dim i As Integer
Dim iCost As Double
Dim iFee As Double
Dim iTotal As Double
Set refWorkbook = Workbooks("AutomationBackbone.xlsm")
csrFileName = refWorkbook.Sheets("Variable Storage").Range("A2").Value
Set csrWorkbook = Workbooks(csrFileName)
sheetCount = csrWorkbook.Sheets.Count
'search all worksheets for data in a known column
For i = 1 To sheetCount
Set slinCell = csrWorkbook.Sheets(i).Range("C1:C100").find(Me.TextBox1.Value)
If Not slinCell Is Nothing Then
'Find sums and populate
rowCount = slinCell.MergeArea.Rows.Count 'count the number of rows in merged area
Set quantCells = slinCell.Offset(0, 2).Resize(rowCount, 1) 'establish a new range of that same height
Set costCells = quantCells.Offset(0, 6)
Set feeCells = quantCells.Offset(0, 7)
Set totalCells = quantCells.Offset(0, 8)
Me.iQuantityTB.Value = Application.WorksheetFunction.Sum((quantCells)) 'populate the Initial Quantity
iCost = Application.WorksheetFunction.Sum((costCells)) 'find sum of Cost range
iFee = Application.WorksheetFunction.Sum((feeCells)) 'find sum of Fee range
iTotal = Application.WorksheetFunction.Sum((totalCells)) 'find sum of Total range
Me.iCostTB.Value = iCost 'populate textboxes
Me.iFeeTB.Value = iFee
Me.iTotalTB.Value = iTotal
'original code commented out to see if being more explicit helped.
'Narrator: it didn't
'Me.iCostTB.Value = Application.WorksheetFunction.Sum((quantCells.Offset(0, 6))) 'populate the Initial Cost
'Me.iFeeTB.Value = Application.WorksheetFunction.Sum((quantCells.Offset(0, 7))) 'populate the Initial Fee
'Me.iTotalTB.Value = Application.WorksheetFunction.Sum((quantCells.Offset(0, 8))) 'populate the Initial Total
Exit Sub
End If
Next i
End Sub
Edit: Pics added for clarity.
Parentheses strike again!
Including the extra parentheses causes the inner expression to be evaluated, and the result to be passed to Sum.
So
Application.WorksheetFunction.Sum((costCells))
is equivalent to
Application.WorksheetFunction.Sum(costCells.Value)
which returns zero when the underlying data is Currency.
As a small reproducible example for Sums behavior here (which is not what I expected):
Dim x(0 To 1) As Currency
x(0) = 1
x(1) = 2
Debug.Print Application.Sum(x) '<~ returns zero.
Note that .Value2 does not use the Currency data type, and the line
Application.WorksheetFunction.Sum(costCells.Value2)
would return the correct result regardless of the underlying value.
Note that similar behavior has been noted and explained here.

Map unique values to a specific Excel sheet

I'm wondering if its possible to create a VBA that map a random "numerical codes" from an excel Spreadsheet 2 (let's say column A) to a column B (Spreadsheet 1).
Some of the values on the spreadsheet 2 are repeated, I would like to build a unique correspondence (no repeated values from column A / Spreadsheet 2 to my column B / Spreadsheet 1)
Spreadsheet1:
Spreadsheet2
Desired output, column filled from Spreadsheet2 (Unique)values :
Is this possible?? feasible??
The following VBA code uses for loops to iterate through the list of values in Spreadsheet2 and only copy each value to Spreadsheet1 if the value has not occurred already in the list.
Option Explicit
Sub ListUniqueCodes()
Dim code As Long
Dim codes As Range
Dim i As Integer
Dim j As Integer
Dim last_row As Integer
Dim output_cell As Range
Dim unique_codes As New Collection 'You could also use a Scripting.Dictionary here as suggested by JvdV
'See https://stackoverflow.com/questions/18799590/avoid-duplicate-values-in-collection
'Store the length of the list of codes that is in Spreadsheet2:
last_row = Workbooks("Spreadsheet2.xlsx").Sheets("Sheet1").Range("A1").End(xlDown).Row
'Store the list of codes that is in Spreadsheet2:
Set codes = Workbooks("Spreadsheet2.xlsx").Sheets("Sheet1").Range("A1:A" & last_row)
'For each code...
For i = 1 To codes.Rows.Count
code = codes.Cells(i).Value2
'...if it does not equal zero...
If code <> 0 Then
'...and if it is not already in the collection unique_codes...
For j = 1 To unique_codes.Count
If unique_codes(j) = code Then Exit For
Next j
'...then add it to the collection unique_codes:
If j = (unique_codes.Count + 1) Then
unique_codes.Add code
End If
End If
Next i
Set output_cell = Workbooks("Spreadsheet1.xlsm").Sheets("Sheet1").Range("B2")
'Write out the unique codes in Spreadsheet1:
For i = 1 To unique_codes.Count
output_cell.Offset(i - 1, 0).Value2 = unique_codes(i)
Next i
End Sub

Dynamic calculations by referencing table header for Excel

I'm quite new to the Macro function in Excel.
I'm working with a table with a dynamic number of columns, and while some of the calculations I can do manually in Excel, I'm trying to figure out how to automate this in VBA.
The number of columns between column G (Achieved) and P (Total % Class) may differ, but the calculations will be about the same.
I am hoping to use the Table[Header] to specify where to calculate, perhaps whichever column is on the right of 'Achieved' and left of 'Total % Class'
Total % Class:
= SUM all classes except Unclassed
Total % at Higher Class:
= SUM Class_1, Class_2, Class_2F and Class_2P together
Some of these Classes won't always appear in the data, if any one these are missing it will return a #REF error for 'Total % Class' and 'Total % at Higher Class', so I'm hoping to bypass this.
Here's a screengrab of the data, showing all the classes.
This macro assumes your data is an Excel Structured Table (if it's not, backup your workbook and consider selecting the data and pressing "Ctrl" + "T" to turn it into one)
Customize the section inside the code:
Sub DoCalcs()
' Declare objects variables
Dim excelTable As ListObject
' Declare other variables
Dim sheetName As String
Dim excelTableName As String
Dim achievedColHeader As String
Dim achievedColNum As Integer
Dim calculationColHeader As String
Dim calculationColNum As Integer
Dim firstColHeader As String
Dim lastColHeader As String
'>>>>>>>>Customize to fit your needs
sheetName = "Sheet1"
excelTableName = "Table1"
achievedColHeader = "Achieved"
calculationColHeader = "Total % Class"
'<<<<<<<<
' Initialize the listobject
Set excelTable = ThisWorkbook.Worksheets(sheetName).ListObjects(excelTableName)
' Get the column number of the first column
achievedColNum = excelTable.HeaderRowRange.Find(achievedColHeader).Column
' Get the column number of the calculation column
calculationColNum = excelTable.HeaderRowRange.Find(calculationColHeader).Column
' Get first and last columns headers of sum's range
firstColHeader = excelTable.HeaderRowRange.Columns(achievedColNum + 1)
lastColHeader = excelTable.HeaderRowRange.Columns(calculationColNum - 1)
' Set the calculation formula in cell
excelTable.DataBodyRange.Columns(calculationColNum).Formula = "=SUM(Table1[#[" & firstColHeader & "]:[" & lastColHeader & "]])"
End Sub

Copy a set of data multiple times based on criteria on another sheet

Excel 2010. I am trying to write a macro that could copy a set of data multiple times based on criteria on another sheet, but I've been stuck for a long time. I very much appreciate any help that could be offered to help me solve this problem.
Step 1: In the "Criteria" worksheet, there are three columns in which each row contains a specific combination of data. The first set of combination is "USD, Car".
Criteria worksheet
Step 2: Then the macro will move to the Output worksheet (please refer to the below link for screenshots), and then filter column A and B with the first set of criteria "USD" and "Car" in the "Criteria" worksheet.
Step 3: Afterwards, the macro will copy the filtered data into the last blank row. But the tricky part here is that, the filtered data has to be copied two times (as the "Number of set" column in the "Criteria" tab is 3 in this combination, and it doesn't have to copy the data three times since the filtered data would be treated as the first set of data)
Step4: After the filtered data have been copied, the "Set" column D will need to fill in the corresponding number of set that the rows are in. Therefore, in this 1st example, cell D2 and D8 will have "1" value, cell D14-15 will have "2" value, and cell D16-17 will have "3" value.
Step5: The macro will then move back to the "Criteria" worksheet and continue to based on the 2nd set of combination "USD, Plane" to filter the data in the "Output" worksheet. Again, it will copy the filtered data based on the "Number of set" in the "Criteria" worksheet. This process will continue until all the different combinations in the "Criteria" worksheet have been processed.
Output worksheet
Ok sorry for delay, here is a working version
you just have to add a sheet called "BF" because the autofilter count wasn't working properly so I had to use another sheet
Sub testfct()
Dim ShC As Worksheet
Set ShC = ThisWorkbook.Sheets("Criteria")
Dim EndRow As Integer
EndRow = ShC.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To EndRow
Get_Filtered ShC.Cells(i, 1), ShC.Cells(i, 2), ShC.Cells(i, 3)
Next i
End Sub
Sub Get_Filtered(ByVal FilterF1 As String, ByVal FilterF2 As String, ByVal NumberSetsDisered As Integer)
Dim NbSet As Integer
NbSet = 0
Dim ShF As Worksheet
Set ShF = ThisWorkbook.Sheets("Output")
Dim ColCr1 As Integer
Dim ColCr2 As Integer
Dim ColRef As Integer
ColCr1 = 1
ColCr2 = 2
ColRef = 4
If ShF.AutoFilterMode = True Then ShF.AutoFilterMode = False
Dim RgTotal As String
RgTotal = "$A$1:$" & ColLet(ShF.Cells(1, Columns.Count).End(xlToLeft).Column) & "$" & ShF.Cells(Rows.Count, 1).End(xlUp).Row
ShF.Range(RgTotal).AutoFilter field:=ColCr1, Criteria1:=FilterF1
ShF.Range(RgTotal).AutoFilter field:=ColCr2, Criteria1:=FilterF2
'Erase Header value, fix? or correct at the end?
ShF.AutoFilter.Range.Columns(ColRef).Value = 1
Sheets("BF").Cells.ClearContents
ShF.AutoFilter.Range.Copy Destination:=Sheets("BF").Cells(1, 1)
Dim RgFilt As String
RgFilt = "$A$2:$B" & Sheets("BF").Cells(Rows.Count, 1).End(xlUp).Row '+ 1
Dim VR As Integer
'Here was the main issue, the value I got with autofilter was not correct and I couldn't figure out why....
'ShF.AutoFilter.Range.SpecialCells(xlCellTypeVisible).Rows.Count
'Changed it to a buffer sheet to have correct value
VR = Sheets("BF").Cells(Rows.Count, 1).End(xlUp).Row - 1
Dim RgDest As String
ShF.AutoFilterMode = False
'Now we need to define Set's number and paste N times
For k = 1 To NumberSetsDisered - 1
'define number set
For j = 1 To VR
ShF.Cells(Rows.Count, 1).End(xlUp).Offset(j, 3) = k + 1
Next j
RgDest = "$A$" & ShF.Cells(Rows.Count, 1).End(xlUp).Row + 1 & ":$B$" & (ShF.Cells(Rows.Count, 1).End(xlUp).Row + VR)
Sheets("BF").Range(RgFilt).Copy Destination:=ShF.Range(RgDest)
Next k
ShF.Cells(1, 4) = "Set"
Sheets("BF").Cells.ClearContents
'ShF.AutoFilterMode = False
End Sub
And the function to get column letter using an integer input :
Function ColLet(x As Integer) As String
With ActiveSheet.Columns(x)
ColLet = Left(.Address(False, False), InStr(.Address(False, False), ":") - 1)
End With
End Function

Resources