# VBA Code Templates

Ready-to-use VBA templates for common automation tasks. Copy and customize.

Load `scenes/vba.md` first for code standards and injection workflow.

---

## Template 1: Auto-Generate Monthly Report

```vba
Option Explicit

' ============================================================
' Module: ModMonthlyReport
' Purpose: Auto-generate monthly summary from raw data sheet
' ============================================================

Public Sub GenerateMonthlyReport()
    On Error GoTo ErrHandler
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    
    Dim wsData As Worksheet
    Dim wsSummary As Worksheet
    Dim lastRow As Long
    Dim reportMonth As String
    
    ' Get target month
    reportMonth = InputBox("Enter month (YYYY-MM):", "Report Month", Format(Date, "YYYY-MM"))
    If reportMonth = "" Then GoTo CleanUp
    
    ' Reference sheets
    Set wsData = ThisWorkbook.Sheets("Data")
    
    ' Create or clear summary sheet
    On Error Resume Next
    Set wsSummary = ThisWorkbook.Sheets("Summary_" & reportMonth)
    On Error GoTo ErrHandler
    
    If wsSummary Is Nothing Then
        Set wsSummary = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
        wsSummary.Name = "Summary_" & reportMonth
    Else
        wsSummary.Cells.Clear
    End If
    
    lastRow = wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Row
    
    ' Write headers
    wsSummary.Range("A1").Value = "Monthly Report: " & reportMonth
    wsSummary.Range("A1").Font.Size = 16
    wsSummary.Range("A1").Font.Bold = True
    
    wsSummary.Range("A3").Value = "Category"
    wsSummary.Range("B3").Value = "Total Amount"
    wsSummary.Range("C3").Value = "Count"
    wsSummary.Range("D3").Value = "Average"
    
    ' Aggregate by category (using Dictionary)
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")
    
    Dim i As Long
    Dim cat As String
    Dim amt As Double
    
    For i = 2 To lastRow
        ' Filter by month (assuming date in column A, category in B, amount in C)
        If Format(wsData.Cells(i, 1).Value, "YYYY-MM") = reportMonth Then
            cat = CStr(wsData.Cells(i, 2).Value)
            amt = CDbl(wsData.Cells(i, 3).Value)
            
            If dict.Exists(cat) Then
                dict(cat) = Array(dict(cat)(0) + amt, dict(cat)(1) + 1)
            Else
                dict.Add cat, Array(amt, 1)
            End If
        End If
    Next i
    
    ' Write results
    Dim outRow As Long
    outRow = 4
    Dim key As Variant
    For Each key In dict.Keys
        wsSummary.Cells(outRow, 1).Value = key
        wsSummary.Cells(outRow, 2).Value = dict(key)(0)
        wsSummary.Cells(outRow, 2).NumberFormat = "#,##0.00"
        wsSummary.Cells(outRow, 3).Value = dict(key)(1)
        wsSummary.Cells(outRow, 4).Value = dict(key)(0) / dict(key)(1)
        wsSummary.Cells(outRow, 4).NumberFormat = "#,##0.00"
        outRow = outRow + 1
    Next key
    
    ' Auto-fit columns
    wsSummary.Columns("A:D").AutoFit
    
    MsgBox "Report generated: " & dict.Count & " categories", vbInformation
    
CleanUp:
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
    Exit Sub
    
ErrHandler:
    MsgBox "Error: " & Err.Description, vbCritical
    Resume CleanUp
End Sub
```

---

## Template 2: Batch Process Multiple Sheets

```vba
Option Explicit

' ============================================================
' Module: ModBatchProcess
' Purpose: Apply same operation to all data sheets
' ============================================================

Public Sub BatchProcessSheets()
    On Error GoTo ErrHandler
    Application.ScreenUpdating = False
    
    Dim ws As Worksheet
    Dim processedCount As Long
    
    For Each ws In ThisWorkbook.Worksheets
        ' Skip non-data sheets
        If Left(ws.Name, 1) <> "_" And ws.Name <> "Summary" And ws.Name <> "Config" Then
            Call ProcessSingleSheet(ws)
            processedCount = processedCount + 1
        End If
    Next ws
    
    MsgBox processedCount & " sheets processed.", vbInformation
    
CleanUp:
    Application.ScreenUpdating = True
    Exit Sub
    
ErrHandler:
    MsgBox "Error on sheet '" & ws.Name & "': " & Err.Description, vbCritical
    Resume CleanUp
End Sub

Private Sub ProcessSingleSheet(ws As Worksheet)
    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    
    ' Example: Add a "Total" row at the bottom
    Dim lastCol As Long
    lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
    
    Dim totalRow As Long
    totalRow = lastRow + 1
    
    ws.Cells(totalRow, 1).Value = "Total"
    ws.Cells(totalRow, 1).Font.Bold = True
    
    Dim col As Long
    For col = 2 To lastCol
        ' Only sum if column contains numbers
        If IsNumeric(ws.Cells(2, col).Value) Then
            ws.Cells(totalRow, col).Formula = "=SUM(" & _
                ws.Cells(2, col).Address & ":" & ws.Cells(lastRow, col).Address & ")"
            ws.Cells(totalRow, col).Font.Bold = True
        End If
    Next col
End Sub
```

---

## Template 3: Data Validation & Cleanup

```vba
Option Explicit

' ============================================================
' Module: ModDataCleanup
' Purpose: Validate and clean data, log issues
' ============================================================

Public Sub ValidateAndClean()
    On Error GoTo ErrHandler
    Application.ScreenUpdating = False
    
    Dim wsData As Worksheet
    Dim wsLog As Worksheet
    Dim lastRow As Long
    Dim logRow As Long
    Dim issueCount As Long
    
    Set wsData = ThisWorkbook.Sheets("Data")
    lastRow = wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Row
    
    ' Create log sheet
    On Error Resume Next
    Application.DisplayAlerts = False
    ThisWorkbook.Sheets("ValidationLog").Delete
    Application.DisplayAlerts = True
    On Error GoTo ErrHandler
    
    Set wsLog = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
    wsLog.Name = "ValidationLog"
    wsLog.Range("A1:D1").Value = Array("Row", "Column", "Issue", "Original Value")
    logRow = 2
    
    Dim i As Long
    For i = 2 To lastRow
        ' Check: Empty required fields (columns A-C)
        Dim col As Long
        For col = 1 To 3
            If IsEmpty(wsData.Cells(i, col)) Or Trim(CStr(wsData.Cells(i, col).Value)) = "" Then
                wsLog.Cells(logRow, 1).Value = i
                wsLog.Cells(logRow, 2).Value = wsData.Cells(1, col).Value
                wsLog.Cells(logRow, 3).Value = "Empty required field"
                logRow = logRow + 1
                issueCount = issueCount + 1
            End If
        Next col
        
        ' Check: Numeric column D should be positive
        If Not IsEmpty(wsData.Cells(i, 4)) Then
            If Not IsNumeric(wsData.Cells(i, 4).Value) Then
                wsLog.Cells(logRow, 1).Value = i
                wsLog.Cells(logRow, 2).Value = wsData.Cells(1, 4).Value
                wsLog.Cells(logRow, 3).Value = "Non-numeric value"
                wsLog.Cells(logRow, 4).Value = wsData.Cells(i, 4).Value
                logRow = logRow + 1
                issueCount = issueCount + 1
            ElseIf CDbl(wsData.Cells(i, 4).Value) < 0 Then
                wsLog.Cells(logRow, 1).Value = i
                wsLog.Cells(logRow, 2).Value = wsData.Cells(1, 4).Value
                wsLog.Cells(logRow, 3).Value = "Negative value"
                wsLog.Cells(logRow, 4).Value = wsData.Cells(i, 4).Value
                logRow = logRow + 1
                issueCount = issueCount + 1
            End If
        End If
        
        ' Clean: Trim whitespace from text columns
        For col = 1 To 3
            If Not IsEmpty(wsData.Cells(i, col)) Then
                Dim cleaned As String
                cleaned = Trim(CStr(wsData.Cells(i, col).Value))
                If cleaned <> CStr(wsData.Cells(i, col).Value) Then
                    wsData.Cells(i, col).Value = cleaned
                End If
            End If
        Next col
    Next i
    
    ' Format log
    wsLog.Columns("A:D").AutoFit
    wsLog.Range("A1:D1").Font.Bold = True
    
    If issueCount > 0 Then
        wsLog.Activate
        MsgBox issueCount & " issues found. See ValidationLog sheet.", vbExclamation
    Else
        MsgBox "All data validated. No issues found.", vbInformation
    End If
    
CleanUp:
    Application.ScreenUpdating = True
    Exit Sub
    
ErrHandler:
    MsgBox "Error: " & Err.Description, vbCritical
    Resume CleanUp
End Sub
```

---

## Template 4: Multi-File Consolidation

```vba
Option Explicit

' ============================================================
' Module: ModConsolidate
' Purpose: Merge data from multiple Excel files into one sheet
' ============================================================

Public Sub ConsolidateFiles()
    On Error GoTo ErrHandler
    Application.ScreenUpdating = False
    
    ' Let user select files
    Dim files As Variant
    files = Application.GetOpenFilename( _
        FileFilter:="Excel Files (*.xlsx;*.xlsm),*.xlsx;*.xlsm", _
        Title:="Select Files to Consolidate", _
        MultiSelect:=True)
    
    If Not IsArray(files) Then
        MsgBox "No files selected.", vbInformation
        GoTo CleanUp
    End If
    
    Dim wsDest As Worksheet
    Set wsDest = ThisWorkbook.Sheets("Consolidated")
    wsDest.Cells.Clear
    
    Dim destRow As Long
    destRow = 1
    Dim headerWritten As Boolean
    
    Dim fileIndex As Long
    For fileIndex = LBound(files) To UBound(files)
        Dim wbSource As Workbook
        Set wbSource = Workbooks.Open(CStr(files(fileIndex)), ReadOnly:=True)
        
        Dim wsSource As Worksheet
        Set wsSource = wbSource.Sheets(1)  ' First sheet
        
        Dim srcLastRow As Long
        srcLastRow = wsSource.Cells(wsSource.Rows.Count, "A").End(xlUp).Row
        
        Dim srcLastCol As Long
        srcLastCol = wsSource.Cells(1, wsSource.Columns.Count).End(xlToLeft).Column
        
        ' Copy header from first file only
        If Not headerWritten Then
            wsSource.Range(wsSource.Cells(1, 1), wsSource.Cells(1, srcLastCol)).Copy _
                Destination:=wsDest.Cells(destRow, 1)
            ' Add "Source File" column
            wsDest.Cells(destRow, srcLastCol + 1).Value = "Source File"
            destRow = destRow + 1
            headerWritten = True
        End If
        
        ' Copy data rows
        If srcLastRow >= 2 Then
            wsSource.Range(wsSource.Cells(2, 1), wsSource.Cells(srcLastRow, srcLastCol)).Copy _
                Destination:=wsDest.Cells(destRow, 1)
            
            ' Tag source file
            Dim r As Long
            For r = destRow To destRow + srcLastRow - 2
                wsDest.Cells(r, srcLastCol + 1).Value = Dir(CStr(files(fileIndex)))
            Next r
            
            destRow = destRow + srcLastRow - 1
        End If
        
        wbSource.Close SaveChanges:=False
    Next fileIndex
    
    wsDest.Columns.AutoFit
    MsgBox "Consolidated " & UBound(files) - LBound(files) + 1 & " files, " & _
           destRow - 2 & " data rows.", vbInformation
    
CleanUp:
    Application.ScreenUpdating = True
    Exit Sub
    
ErrHandler:
    MsgBox "Error: " & Err.Description, vbCritical
    If Not wbSource Is Nothing Then wbSource.Close SaveChanges:=False
    Resume CleanUp
End Sub
```

---

## Template 5: Button-Triggered Automation

```vba
' ============================================================
' In ThisWorkbook module — create button on sheet
' ============================================================

' Add button programmatically (run once):
Sub CreateRunButton()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("Dashboard")
    
    Dim btn As Button
    Set btn = ws.Buttons.Add(Left:=10, Top:=10, Width:=120, Height:=36)
    btn.Caption = "Generate Report"
    btn.OnAction = "ModMonthlyReport.GenerateMonthlyReport"
    btn.Font.Size = 11
End Sub
```

---

## Template 6: Protected Sheet with Editable Ranges

```vba
Option Explicit

' ============================================================
' Module: ModProtection
' Purpose: Lock sheet but allow editing in specific ranges
' ============================================================

Public Sub SetupProtection()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("Input")
    
    ' First unlock everything
    ws.Unprotect Password:="admin123"
    ws.Cells.Locked = True
    
    ' Unlock editable ranges
    ws.Range("C5:C20").Locked = False     ' Input cells
    ws.Range("E5:E20").Locked = False     ' Comment cells
    
    ' Visual hint: light yellow for editable cells
    ws.Range("C5:C20").Interior.Color = RGB(255, 255, 230)
    ws.Range("E5:E20").Interior.Color = RGB(255, 255, 230)
    
    ' Protect with options
    ws.Protect Password:="admin123", _
        DrawingObjects:=True, _
        Contents:=True, _
        Scenarios:=True, _
        AllowFormattingCells:=False, _
        AllowInsertingRows:=False, _
        AllowDeletingRows:=False, _
        AllowSorting:=True, _
        AllowFiltering:=True, _
        AllowUsingPivotTables:=False
    
    MsgBox "Sheet protected. Editable ranges highlighted in yellow.", vbInformation
End Sub
```
