Excel VBA to send different emails and log email details - vba

I'm new here and not very proficient, I tend to google what I need and piece it together so please don't feel like you'd be offending me by dumbing things down.
I'm trying to create a spreadsheet where I can send predetermined emails based on conditions in cells, and log the date those emails are sent in a table- hopefully something reasonably straightforward!
Currently working with this table of information on Sheet2 and 3 text boxes on Sheet1 for each body of text and my very rudimentary vba attempt below- email set to display and commented send while testing.
I'm certain I've made a bigger meal of this than I need to, but the goal is to have a user input class, date and time in columns C:E. From there, Column K has an IF statement to determine whether to send email 1, 2 or 3 based on if F is empty (1st email), if F has a value (2nd email) or if G has a value (3rd email). As it stands, I can do this on 3 separate subs but cannot combine them, possibly because I'm trying to reference 'body' to different text boxes in the same sub? So below is the Sub for email 1. It's currently filtering K and copying data out of a table, into another range to then use as an email list (likely unnecessary) before clearing that pasted range and returning to the table.
I'm facing a problem in that if I click the button for this VBA and there are no '1's in column K, it selects all data in the table and sends an email to all. I'd like it to skip rows where C:E are empty and only email the 'outstanding' emails that have new input in those cells.
I'm also facing a problem in figuring out how to then move the class/date/time info over to column L:N (or 11:13 in the table) if K=1, to column O:Q if K=2, to column R:T if K=3, and to skip the row if K="". Also trying to populate the appropriate email column with the date of sending, so If K=1, F=today(), If K=2, G=today(), IF K=3, H=today(). I learned that copying and pasting filtered cells into filtered cells is not straightforward.
Any help would be hugely appreciated! Thank you.
PS- eventually would like to figure out how to remove dates in the log older than 30 days and move the data accordingly- ie, if email 1 is older than 30 days, email 2 details become email 1, so we can maintain a 3 strike in 30 days policy
Sub EMAIL1()
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=11, Criteria1:= _
"1"
Range("A3:E3").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.copy
Sheets("Sheet1").Select
Range("A2").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Dim i As Integer
Dim name, email, body, subject, copy, class, classdate, classtime As String
Dim OutApp As Object
Dim OutMail As Object
body = ActiveSheet.TextBoxes("TextBox 1").Text
i = 2
'Loop down name column starting at row 2 column 1
Do While Cells(i, 1).Value <> ""
name = Split(Cells(i, 1).Value, " ")(0) 'extract first name
email = Cells(i, 2).Value
class = Cells(i, 3).Value
classdate = Cells(i, 4).Value
classtime = Cells(i, 5).Value
body = Replace(body, "[Name]", name)
body = Replace(body, "[Class]", class)
body = Replace(body, "[Class Date]", classdate)
body = Replace(body, "[Class Time]", classtime)
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = email
.subject = "Non attendance"
.body = body
.display
'.Send
End With
body = ActiveSheet.TextBoxes("TextBox 1").Text
i = i + 1
Loop
Set OutMail = Nothing
Set OutApp = Nothing
Range("A2:E2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.ClearContents
Sheets("Sheet2").Select
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=11
MsgBox "Email(s) Sent!"
End Sub

Related

VBA Macro to AutoFilter by criteria, copy visible results from one column into an Array, and then AutoFilter a different sheet by that Array

I'm trying to create a custom report for a technically illiterate client, and I need some help.
So here's what I need to do:
I have two different Workbooks as input files: a Case List (cases.csv) and a Revenue Report (revenue.csv), which are output from a web service.
The Case List contains Case Filenames (Column K), and Usernames (Column W). All cases and users are listed.
The Revenue Report contains Case Filenames (Column C), and the Revenue Data (Columns G through T), but does not contain associated Usernames (which I need to filter by).
The amount of entries in these lists change all the time, so I need something dynamic.
I basically need to AutoFilter the Revenue Report to only show Cases that are assigned to a specific User.
This is what I have so far for the first part:
Set MyRange = Range("A1").CurrentRegion
Selection.AutoFilter Field:=23, Criteria1:="User1"
Range("K2").Select
Range(Selection, Selection.End(xlDown)).Select
Dim arr As Variant
arr = Selection
This will AutoFilter the Case List to only show Cases associated with "User1", and then copy the visible results of Column K (Case Filenames associated with User1) to an array.
I then wanted to use that Array as the criteria for the second AutoFilter on the second sheet (revenue.csv), so that the Revenue Information for only Case Filenames associated with User1 will be visible.
I'm getting stuck on the second part. Preferably, I want the Client to download both reports as CSV files to a specified directory (let's say C:\test\ as an example).
Then I want them to open this Macro-enabled workbook (let's call it macro.xlsm), which will load the Worksheets of both reports into itself, and then run the code to AutoFilter>Results to Array>AutoFilter again.
Here's my sorry attempt at the loading script.
Sub Button1_Click()
Dim directory As String, fileName As String, sheet As Worksheet, total As Integer
Application.ScreenUpdating = False
Application.DisplayAlerts = False
directory = "C:\test\"
fileName = Dir(directory & "*.xl??")
Do While fileName <> ""
Workbooks.Open (directory & fileName)
For Each sheet In Workbooks(fileName).Worksheets
total = Workbooks("macrotest.xlsm").Worksheets.Count
Workbooks(fileName).Worksheets(sheet.Name).Copy _
after:=Workbooks("import-sheets.xlsm").Worksheets(total)
Next sheet
Workbooks(fileName).Close
fileName = Dir()
Loop
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
Basically, I would provide them this macro.xlsm with all the code in it, and all they would have to do is download the two reports into a specific directory, open macro.xlsm, then click a button, and the reports will load in, auto-filter themselves, and the Revenue sheet is filtered by only Cases associated with User1.
HELP!
i have tried to recreate your problem, so the data might be in different cells,
File 1, cases.csv
Col A - Case names
Col B - Usernames
File 2, revenues.csv
Col A - Case names
Col B - Revenue data
File 3, Report.xlsm
Range("A1") = "username"
Col C:D - output (Row 1 is headers)
Run below macro from File 3
Sub getRevenue()
'
' Macro2 Macro
'
'
Range("C2:D" & Range("C" & Rows.Count).End(xlUp).Row).ClearContents
Dim username As String
username = Range("A1")
Dim wbCases As Workbook
Dim numCases As Integer
Set wbCases = Workbooks.Open("C:\xxx\xxx\xxx\cases.csv")
ActiveSheet.Range("$A$1:$B$" & Range("A" & Rows.Count).End(xlUp).Row).AutoFilter _
Field:=2, Criteria1:=username
Range("A2:A" & Range("A" & Rows.Count).End(xlUp).Row).Select
Selection.Copy
ThisWorkbook.Activate
Range("C2").Select
ActiveSheet.Paste
Application.CutCopyMode = False
numCases = Selection.Rows.Count
wbCases.Close
Dim cases() As String
ReDim cases(1 To numCases) As String
For i = 1 To numCases
cases(i) = Selection(i, 1)
Next i
Dim wbRev As Workbook
Set wbRev = Workbooks.Open("C:\xxx\xxx\xxx\Revenue.csv")
ActiveSheet.Range("$A$1:$B$" & Range("A" & Rows.Count).End(xlUp).Row).AutoFilter _
Field:=1, Criteria1:=cases, Operator:=xlFilterValues
Range("$A$2:$B$" & Range("A" & Rows.Count).End(xlUp).Row).Select
Selection.Copy
ThisWorkbook.Activate
Range("C2").Select
ActiveSheet.Paste
Application.CutCopyMode = False
wbRev.Close
End Sub
This will generate a list of Cases names and Revenue data for the specified username in Range("A1")
Cheers

Paste values in dynamic range excel vba

I am writing a script where I want to enable a search in a Database, presenting the results of the search queries in a different worksheet (which I have named Results), so that users do not have access to the whole database at the same time.
In order to do this I want to copy values from the "Database" worksheet into the "Results" worksheet. I have succeeded in selecting the right data from the "Database", in respect to any specific search criteria. I did this with the following code:
With Sheets("Database")
.Range(.Cells(i, 1), .Cells(i, 9)).Copy
End With
Now I want to paste the results into the "Results" spreadsheet and I have done so by writing the following:
Sheets("Results").Range("B600").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
By doing this, I don't quite understand:
if I have strictly defined the paste range as between the first empty row and B600 or;
if I am just defining the beginning of the paste range and, in the case that the search results exceed the 600th row, they will still be pasted after this row.
I ask this because, as the database grows, I will certainly need to guarantee a paste range greater than B600.
I have researched on it but cannot seem to be absolutely sure of what I have done exactly. I must say that I know that the first empty row in the "Results" database will always be 12. In this case, I know that I basically want to paste the search results from the 12th row on. Maybe there is a more straight-forward way to do this.
This is the entire code, for reference:
Private Sub SearchButton_Click()
'This is the search function
'1. declare variables
'2. clear old search results
'3. Find records that match criteria and paste them
Dim country As String
Dim Category As String
Dim Subcategory As String
Dim finalrow As Integer
Dim i As Integer 'row counter
'Erase any entries from the Results sheet
Sheets("Results").Range("B10:J200000").ClearContents
'Deformat any tables in the Results sheet
For Each tbl In Sheets("Results").ListObjects
tbl.Clear
Next
'Define the user-inputed variables
country = Sheets("Results").Range("D5").Value
Category = Sheets("Results").Range("D6").Value
Subcategory = Sheets("Results").Range("D7").Value
finalrow = Sheets("Database").Range("A" & Rows.Count).End(xlUp).Row
'If statement for search
'For every variable i, start comparing from row 2 until the final row
For i = 2 To finalrow
'If the country field is left empty
If country = "" Then
Sheets("Results").Range("B10:J200000").Clear
MsgBox "You must select a country in order to search the database. Please do so in the drop-down list provided."
Sheets("Results").Range("D5").ClearContents
Sheets("Results").Range("D6").ClearContents
Sheets("Results").Range("D7").ClearContents
Exit Sub
'If the country field is filled in and there results from the search made
ElseIf Sheets("Database").Cells(i, 1) = country And _
(Sheets("Database").Cells(i, 3) = Category Or Category = "") And _
(Sheets("Database").Cells(i, 4) = Subcategory Or Subcategory = "") Then
'Copy the headers of the table
With Sheets("Database")
.Range("A1:I1").Copy
End With
Sheets("Results").Range("B10:J10").PasteSpecial
'Copy the rows of the table that match the search query
With Sheets("Database")
.Range(.Cells(i, 1), .Cells(i, 9)).Copy
End With
Sheets("Results").Range("B600").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
'Hides search form
Me.Hide
End If
Next i
'Toggle Results sheet
Sheets("Results").Activate
'Format results as a table
Set rng = Range(Range("B10"), Range("B10").End(xlUp).SpecialCells(xlLastCell))
Set table = Sheets("Results").ListObjects.Add(xlSrcRange, rng, , xlYes)
table.TableStyle = "TableStyleMedium13"
Range("B11").Select
'Make Excel window visible
Application.Visible = True
End Sub
Thank you very much for your help.
You can count from the bottom of the sheet upto the last used cell in column B, and then OFFSET by 1 row. This prevents you needing to worry about
a) that the range to paste to starts from row 12 (they should contain values), and
b) that you are currently using a hard-coded 'anchor' of B600 which will need updating as the data grows.
Sample code:
Dim ws As Worksheet
Dim rngColumnBUsed As Range
Dim lngFirstEmptyRow As Long
Set ws = ThisWorkbook.Sheets("Results")
Set rngColumnBUsed = ws.Range("B" & ws.Rows.Count).End(xlUp).Offset(1, 0)
lngFirstEmptyRow = rngColumnBUsed.Row
Two ListObjects tblDatabase and tblResults
tblResults data gets cleared
A filter is applied to the second, third and fourth columns of tblDatabase
If there are less than 588 results, we copy the filtered records from tblDatabase to tblResults
If there are more than 588 results then we resize the filtered records' range down to the first 588 records and then copy them to tblResults
We never worry about formatting because tblResults keeps it's original format.
Sub ListObjectDemo()
Dim tblDatabase As ListObject, tblResults As ListObject
Set tblDatabase = Worksheets("Database").ListObjects("tblDatabase")
Set tblResults = Worksheets("Results").ListObjects("tblResults")
If Not tblResults.DataBodyRange Is Nothing Then tblResults.DataBodyRange.ClearContents
With tblDatabase.Range
.AutoFilter Field:=2, Criteria1:="Test A"
.AutoFilter Field:=3, Criteria1:="East"
.AutoFilter Field:=4, Criteria1:="Algeria"
End With
With tblDatabase.DataBodyRange
If .Rows.Count <= 588 Then
.Copy tblResults.ListRows.Add.Range
Else
.Resize(588).Copy tblResults.ListRows.Add.Range
End If
End With
End Sub
Dim searchdata as range, inputfromuser as string
inputfromuser = inputbox("type what you wanna search")
set searchdata = sheets("Database").find(inputfromuser).select
searchdata = activecell.value or activecell.offset(10,5).value
sheets("results").activate
with sheets("result")
range("a12",range("a12").end(xldown)).offset(1,0).select
searchdata.copy destination:= activecell
activecell.offset(1,0).select
end with
Not sure, if I understood you corectly mate.
I dont haveexcel sheet or VBE editor. Just wrote this directly on website. Pls amend as per your need.

How to loop through rows, save these as variables and use them as variables VBA

I'm trying to store values in sheets as a variable, and then go on to reference a sheet using that variable as well as use it to filter by.
This will be looped through until the program reaches the first empty cell.
The relevant code I have so far is:
Sub Program()
Dim i As Integer
i = 2
Do Until IsEmpty(Cells(i, 1))
Debug.Print i
Sheets("Button").Activate
Dim First As String
First = Cells(i, 1).Value
Debug.Print First
Dim Second As String
Second = Cells(i, 2).Value
Debug.Print Second
'Filters my Data sheet and copies the data
Sheets("DATA").Activate
Sheets("DATA").Range("A1").AutoFilter _
Field:=2, _
Criteria1:=First 'Filters for relevant organisation
Sheets("DATA").Range("A1").AutoFilter _
Field:=6, _
Criteria1:="=" 'Filters for No Response
Sheets("DATA").Range("A1:H6040").Copy
'This should loop through for each separate group
Sheets(CStr(Second)).Select
Range("A1").Select
ActiveSheet.Paste
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
i = i + 1
Loop
Worksheets("DATA").AutoFilterMode = False
End Sub
I have changed the program significantly trying to add notation such as 'CStr' as there was an error at this line:
Sheets(CStr(Second)).Select when it used to say Sheets(Second)).Select
and the debug.print's to see if it is actually working but it isn't logging to the Immediate Window.
Additionally, when I actually run it, no error comes up but nothing seems to happen.
Not sure what else to add, or what else to try. Cheers!
As a first remark, using (at least the first) sheet activation within the loop seems unnecessary, because the start of the loop is what determines which sheet is being used to control the flow of the loop.
Furthermore, I would argue that it is better to remove the sheet activation altogether, re: the discussion about .Select (the cases aren't the same, but the solution discussed herein works better for both .Select and .Activate in almost all instances): How to avoid using Select in Excel VBA macros.
Let's also see if we can refer to the table in the "DATA" sheet in a more direct manner, as well as do some errorchecking.
My suggestion:
Sub Program()
Dim i As Integer
Dim First, Second As String
Dim secondWs As Worksheet
Dim dataTbl As ListObject
i = 2
Set dataTbl = Worksheets("DATA").Range("A1").ListObject.Name
' The above can be done more elegantly if you supply the name of the table
Sheets("DATA").Activate
Do Until IsEmpty(Cells(i, 1))
Debug.Print i
First = Sheets("Button").Cells(i, 1).Value
Debug.Print First
Second = Sheets("Button").Cells(i, 2).Value
Debug.Print Second
'Filters my Data sheet and copies the data
dataTbl.AutoFilter _
Field:=2, _
Criteria1:=First 'Filters for relevant organisation
dataTbl.AutoFilter _
Field:=6, _
Criteria1:="=" 'Filters for No Response
Sheets("DATA").Range("A1:H6040").Copy
'This should loop through for each separate group
On Error Resume Next
Set secondWs = Worksheets(Second)
On Error GoTo 0
If Not secondWs Is Nothing Then
secondWs.Range("A1").PasteSpecial Paste:=xlPasteValues
Else
Debug.Print "Sheet name SECOND was not found"
End If
i = i + 1
Loop
Worksheets("DATA").AutoFilterMode = False
End Sub
If you get any errors, please state which line it appears on and what the error message actually is.
Ref:
http://www.mrexcel.com/forum/excel-questions/3228-visual-basic-applications-check-if-worksheet-exists.html#post13739

How To Paste My Formula In A Cell With Specific Text Instead Of A Column?

I pretty much have an already working macro for me but for the future it may cause problems because the macro i have finds the column i gave it and then starts to input the formula there. Now my data may change in the future and in that column i might have something new so the macro would obviously run the formulas to the wrong column. Changing it manually is possible but hectic and a lot of work. Is there any possible way i can select a cell with a specific text in it instead of the column? since the text will never change this will me much easier for me to work with. Because doing this the formulas will always be posted in the correct column.
EDIT! I added the whole code to the post so you can see it more clearly and understand what i mean more clearly.
Sub HW_Copy_RawData_Formulas()
Dim intChoice As Integer
Dim strPath As String
Dim I As Integer
Dim filePath As String
Dim SourceWb As Workbook
Dim TargetWb As Workbook
Dim Lastrow As Long
Dim Nrow As Long
Set TargetWb = ActiveWorkbook
' Delete Rows
On Error Resume Next
TargetWb.Worksheets("Raw Data").Activate
Range("A2:AL2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Delete Shift:=xlUp
Range("A2:AL2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Delete Shift:=xlUp
'Copy Formulas
Range("AF2").Formula = "=IF([#ServDt]<DATE(2013,1,1), DATE(YEAR([#ServDt]),12,31),EOMONTH([#ServDt],0))"
Range("AG2").Formula = "=IF([#Amount]>1,[#Quantity],0)"
Range("AH2").Formula = "=IF([#Amount]<>0,[#Amount]-[#Adj]-[#[Adjustment ]],0)"
Range("AI2").Formula = "=IF(AND([#Department]=""HH"",[#Pay]=0),[#Amount]/2,0)"
Range("AJ2").Formula = "=IF([#Amount]<>0,[#Bal]-[#[Adjustment ]],[#Bal]+[#Adj])"
Range("AK2").Formula = "=VLOOKUP([Department],Service[#All],2,FALSE)"
Range("AL2").Formula = "=VLOOKUP([#Entity],Site,3,FALSE)"
MSG1 = MsgBox("Add Raw Data", vbYesNo)
If MSG1 = vbYes Then
'only allow the user to select one file
Application.FileDialog(msoFileDialogOpen).AllowMultiSelect = False
'make the file dialog visible to the user
intChoice = Application.FileDialog(msoFileDialogOpen).Show
'determine what choice the user made
If intChoice <> 0 Then
'get the file path selected by the user
strPath = Application.FileDialog( _
msoFileDialogOpen).SelectedItems(1)
Else: GoTo endmsg
End If
'Setting source of data
Set SourceWb = Workbooks.Open(strPath)
Lastrow = SourceWb.Worksheets(1).Cells(Rows.Count, 1).End(xlUp).Row
SourceWb.Worksheets(1).Range("A2:BJ" & Lastrow).SpecialCells(xlCellTypeVisible).Select
Selection.Copy Destination:=TargetWb.Sheets("Raw Data").Range("A2")
' Close the source workbook without saving changes.
SourceWb.Close savechanges:=False
Else
endmsg:
MsgBox "Complete"
End If
Range("AF2:AL2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Range("AF2").PasteSpecial xlPasteValues
End Sub
The following code snippet might be of use to you. It acquires the range of the cell given a specific value. It can also be used to search a specific row with .Rows() instead.
Dim *YOURCELL* As Range
Set *YOURCELL*= .Columns(1).Find(What:= *WHATYOUWANTTOFIND*, LookAt:=xlWhole, MatchCase:=False, searchformat:=False)
If, however, you do not know where the last used cell is located, then consider reading this other post.
EDIT:
The while loop runs as long as the currently selected cell is not empty. In this loop, it selects the next cell to the right and increments a count. After the loop has finished, the currently selected cell is the first empty cell in the second row. Count has found the column number of it by incrementing alongside the loop, so it can then be used as needed. I used cells instead of range afterwards because it can use the column number.
Range("A2").Select
Dim count As Integer
count = 1
'skip all used cells in the row
Do While Not (ActiveCell.value = None)
ActiveCell.Offset(0, 1).Range("A1").Select
count = count + 1
Loop
Cells(count, 2).Formula = your_formula
Cells(count + 1, 2).Formula = your_formula ' next cell to the right
Cells(count + 2, 2).Formula = your_formula ' next cell to the right

How to paste data to specific cell range?

I'm new to vba and need a little help. I have a sheet named "Archive" which will have 12 sets of data displayed/structured in somewhat of a table format. My goal is to pull data from other sheets within the same workbook and paste it in a specific range that corresponds to the appropriate "table" for that data. Here is my code for data that is being pulled from a sheet named "Daily DB" and is being pasted to the "Archive" sheet.
Sub GetDailyDataByWeek()
Dim cw As Integer ' current week
Dim lr As Long 'last row of data
Dim i As Long ' row counter
'Clear exsisting contents
Worksheets("Archive").Range("A5:E11").ClearContents
'Get week number and year of current date
cw = Format(Date, "ww")
With Worksheets("Daily DB")
' Find last row of data
lr = .Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
For i = 2 To lr
If Format(.Cells(i, 1).Value, "ww") = cw Then
.Range(.Cells(i, 1), .Cells(i, 5)).Copy
Worksheets("Archive").Range("a" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
End With
Application.CutCopyMode = False
End Sub
This code does what I want it to do. The line that I need help in fixing is:
Worksheets("Archive").Range("a" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
As this line looks for the last row of data, which in my case would be the header row of the 12th table. However, I'd like this particular data to go into the first table which after the header row starts at "A5", but I'm not sure how to go about that. Any and all help is greatly appreciated.
If you want to replicate the data from another cell or range into the same workbook I would use the "Value" method of the Range object, like this:
Worksheets("Archive").Range("A" & i).Value = Worksheets("XXX").Range("Z" & j).Value
By doing it like this you would avoid doing all the copy and paste operations.
If you dont want to specify a Range for each value, you could activate the firs cell of the first row and then "offset" your way through, like this:
Worksheets("Archive").Range("A" & i).Activate
ActiveCell.Value = blah blah blah
ActiveCell.Offset(1, 0).Activate 'If you want to move to the next row (same column)
ActiveCell.Offset(0, 1).Activate 'If you want to move to the next column (Same row)