How to setup automated reports by email

For users or potential users.
Post Reply
KevinRossen
Posts: 293
Joined: Mon Apr 22, 2013 8:49 am
Location: Dallas, TX
Contact:

How to setup automated reports by email

Post by KevinRossen » Tue Feb 18, 2014 2:07 pm

Over the past month or so, I've setup an automated process for generating reports sent to me as a spreadsheet attachment. What I wanted was a daily email with the relevant numbers that I use at our team morning huddle. We track adjusted production, scheduled production, collection, new patients, and reappointment rate on a week-by-week basis. I was scrambling each morning to gather theses numbers, but now I have them automatically in my email inbox each morning, ready to use at our huddle. It's really nice.

The only additional piece of software needed to make this happen, other than Open Dental, is a little piece of software called SendEmail, which allows you to send emails (with or without attachments) from the command line (or as a batch file).

Here's a walk-through. The following should be done from your server:
  1. Decide which query you would like emailed (my report is at the bottom of this post) and save it as a text file with the extension .sql. If you're using Word you have to make sure that you save the file as text only, but I suggest using Notepad or Notepad++. If you already have a report you'd like to use you can just add INTO OUTFILE "C:\\Reports\\KPIThisWeek.csv" FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'; to the end of the report. NOTE: You need to have that line before any commands to DROP tables AND pay attention to the semi-colon. This step will create a CSV spreadsheet to the folder of your choosing.
  2. Create a batch file with commands to run the above query from the command line (see mine at the bottom of this post). The batch file will need three simple commands. First, it will tell MySQL to run the query mentioned above. Second, it will run SendEmail with the query result spreadsheet as an attachment.
  3. Create a scheduled task from Windows Task Scheduler to run the batch file at the time(s) of your choosing. Click the Windows button (or press the Windows key on your keyboard), type "task", and Task Scheduler should be the top application you can select. From there:
Click create basic task.
Image
Name your task and (optionally) add a description.
Image
Set the time(s) you'd like your report to run in trigger.
Image
When prompted to choose an action, select Start a program
Image
Find the batch file you created.
Image
Click Finish.

You can test your setup by selecting the task you just created in the "Task Scheduler Library" (it's on the upper left of the window) then clicking run. If everything worked right you will get an email in your inbox with the csv attachment.

Let me know if you have any questions about this setup. Below is the code I have in my reports:

MySQL Query for our KPI for this week It saves it to a CSV file in C:\Reports\. Save this file as C:\Reports\KPIThisWeek.sql

Code: Select all

DROP TABLE IF EXISTS temp1,temp2,temp3;
SET @FromDate=adddate(curdate(), INTERVAL 1-DAYOFWEEK(curdate()) DAY), @ToDate=adddate(curdate(), INTERVAL 7-DAYOFWEEK(curdate()) DAY);
CREATE TABLE temp1( 
Date date,
$Production double NOT NULL DEFAULT 0,
$Scheduled double NOT NULL DEFAULT 0,
$Total double NOT NULL DEFAULT 0,
$Gross double NOT NULL DEFAULT 0,
$WriteOffs double NOT NULL DEFAULT 0,
$SchedProd double NOT NULL DEFAULT 0,
$SchdWrtOff double NOT NULL DEFAULT 0,
$Adjustments double NOT NULL DEFAULT 0,
$PatIncome double NOT NULL DEFAULT 0,
$InsIncome double NOT NULL DEFAULT 0,
$Collection double NOT NULL DEFAULT 0,
NPs double NOT NULL DEFAULT 0,
Rate CHAR(4) NOT NULL DEFAULT 0);
/*Load Dates of Current Week*/
INSERT INTO temp1(Date)
 VALUES (@FromDate),(ADDDATE(@FromDate, INTERVAL 1 DAY)),(ADDDATE(@FromDate, INTERVAL 2 DAY)),(ADDDATE(@FromDate, INTERVAL 3 DAY)),(ADDDATE(@FromDate, INTERVAL 4 DAY)),(ADDDATE(@FromDate, INTERVAL 5 DAY)),(@ToDate);
/*Gross Prod*/
CREATE TABLE temp2
SELECT DATE(pl.ProcDate) AS 'Date', SUM(pl.ProcFee*(pl.UnitQty+pl.BaseUnits)) AS 'Gross'
FROM procedurelog pl WHERE pl.ProcStatus=2 AND DATE(pl.ProcDate) BETWEEN @FromDate AND @ToDate
GROUP BY DATE(pl.ProcDate);
UPDATE temp1,temp2 SET temp1.$Gross=temp2.Gross WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Sched Prod and writeoffs*/
CREATE TABLE temp2
SELECT DATE(ap.AptDateTime) AS 'Date', SUM(pl.procfee) AS 'SchedProd', SUM(IF((cp.WriteOffEstOverride=-1),IF((cp.WriteOffEst=-1),0,cp.WriteOffEst), cp.WriteOffEstOverride)) AS 'SchdWrtOff'
FROM appointment ap INNER JOIN procedurelog pl ON pl.AptNum=ap.AptNum LEFT JOIN claimproc cp ON cp.ProcNum=pl.ProcNum
WHERE pl.ProcStatus=1 AND ap.AptStatus=1 AND DATE(ap.AptDateTime) BETWEEN @FromDate AND @ToDate AND (ISNULL(cp.Status) OR cp.Status=6 /*estimate*/)
GROUP BY DATE(ap.AptDateTime);
UPDATE temp1,temp2 SET temp1.$SchedProd=temp2.SchedProd , temp1.$SchdWrtOff=-temp2.SchdWrtOff WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Adj*/
CREATE TABLE temp2
SELECT DATE(a.AdjDate) AS 'Date', SUM(a.AdjAmt) AS 'Adjustments' 
FROM adjustment a WHERE a.AdjDate BETWEEN @FromDate AND @ToDate 
GROUP BY DATE(a.AdjDate);
UPDATE temp1,temp2 SET temp1.$Adjustments=temp2.Adjustments WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*PatInc*/
CREATE TABLE temp2
SELECT DATE(pp.DatePay) AS 'Date', SUM(pp.SplitAmt) AS 'PatIncome' FROM paysplit pp 
WHERE pp.DatePay BETWEEN @FromDate AND @ToDate 
GROUP BY DATE(pp.DatePay);
UPDATE temp1,temp2 SET temp1.$PatIncome=temp2.PatIncome WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Writeoffs*/
CREATE TABLE temp2
SELECT DATE(cp.ProcDate) AS 'Date', SUM(cp.WriteOff) AS 'WriteOffs' 
FROM claimproc cp WHERE (cp.Status=1 OR cp.Status=4 OR cp.Status=0) AND (cp.ProcDate BETWEEN @FromDate AND @ToDate)
GROUP BY DATE(cp.ProcDate);
UPDATE temp1,temp2 SET temp1.$WriteOffs=-temp2.WriteOffs WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*InsIncome*/
CREATE TABLE temp2
SELECT DATE(cpay.CheckDate) AS 'Date', SUM(cp.InsPayAmt) AS 'InsIncome'
FROM claimproc cp INNER JOIN claimpayment cpay ON cpay.ClaimPaymentNum=cp.ClaimPaymentNum 
WHERE cpay.CheckDate BETWEEN @FromDate AND @ToDate AND cp.Status IN(1,4) 
GROUP BY DATE(cpay.CheckDate);
UPDATE temp1,temp2 SET temp1.$InsIncome=temp2.InsIncome WHERE temp1.Date=temp2.Date;
UPDATE temp1 SET $Production=ROUND($Gross+$Adjustments+$WriteOffs,0), $Scheduled=ROUND($SchedProd+$SchdWrtOff,0), $Collection=ROUND($InsIncome+$PatIncome,0);
DROP TABLE IF EXISTS temp2;
ALTER TABLE temp1 DROP $Gross, DROP $WriteOffs, DROP $SchedProd, DROP $SchdWrtOff, DROP $Adjustments, DROP $PatIncome, DROP $InsIncome;
/* NPs */
CREATE TABLE temp2
SELECT DATE(p.DateFirstVisit) AS 'Date', COUNT(DISTINCT p.PatNum) AS 'NPs'
FROM patient p LEFT JOIN procedurelog pl ON p.PatNum=pl.PatNum LEFT JOIN appointment a ON p.PatNum=a.PatNum AND a.AptStatus=1 /*sched*/
WHERE (pl.ProcDate BETWEEN @FromDate AND @ToDate AND pl.ProcStatus=2 AND p.DateFirstVisit >= @FromDate) OR (a.AptDateTime BETWEEN @FromDate AND @ToDate AND p.DateFirstVisit >= @FromDate AND a.AptDateTime >= p.DateFirstVisit)
GROUP BY DATE(p.DateFirstVisit);
UPDATE temp1,temp2 SET temp1.NPs=temp2.NPs WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Reappt Rate*/
CREATE TABLE temp2(Date date,Seen int NOT NULL DEFAULT 0,Sched int NOT NULL DEFAULT 0);
INSERT INTO temp2(Date,Seen)
SELECT DATE(AptDateTime) AS 'Date', COUNT(DISTINCT PatNum) AS 'Seen'
FROM appointment WHERE (DATE(AptDateTime) BETWEEN @FromDate AND @ToDate) AND AptStatus=2
GROUP BY Date;
CREATE TABLE temp3
SELECT DATE(a1.AptDateTime) AS 'Date', COUNT(DISTINCT a2.PatNum) AS 'Sched'
FROM appointment a1 LEFT JOIN appointment a2 ON a1.PatNum=a2.PatNum
WHERE (DATE(a1.AptDateTime) BETWEEN @FromDate AND @ToDate AND a1.AptStatus=2) AND (DATE(a2.AptDateTime)>DATE(a1.AptDateTime) AND a2.AptStatus=1)
GROUP BY Date;
UPDATE temp2,temp3 SET temp2.Sched=temp3.Sched WHERE temp2.Date=temp3.Date;
UPDATE temp1,temp2 SET temp1.Rate=CONCAT(ROUND((temp2.Sched/temp2.Seen*100),0),'%') WHERE temp1.Date=temp2.Date;
UPDATE temp1 SET $Total=$Production+$Scheduled;
SELECT 'Date','$Production','$Scheduled','$Total','$Collect','NPs','Reappt'
UNION
SELECT * FROM temp1
UNION
SELECT 'Total', SUM(A.$Production), SUM(A.$Scheduled), SUM(A.$Total), SUM(A.$Collection), SUM(A.NPs), CONCAT(ROUND((SUM(B.Sched)/SUM(B.Seen)*100),0),'%') FROM temp1 A LEFT JOIN temp2 B ON A.Date=B.Date
INTO OUTFILE "C:\\Reports\\KPIThisWeek.csv" FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';
DROP TABLE IF EXISTS temp1,temp2,temp3;
Code for batch file. Save it as C:\Reports\EmailReport.bat (or something similar). You'll use this file as your scheduled task.

Code: Select all

@ECHO OFF
REM Update File locations below if necessary
"C:\Program Files (x86)\MySQL\MySQL Server 5.5\bin\mysql" -u "root" "opendental" < "C:\Reports\KPIThisWeek.sql"
REM Update email info below
"C:\sendEmail\sendEmail.exe" -f SentFromEmail@SomeDomain.com -t SentToEmail@SomeDomain.com -s smtp.gmail.com:587 -xu USERNAME -xp PASSWORD -u "Email Subject Line" -m "Message body." -a "C:\Reports\KPIThisWeek.csv" 
del "C:\Reports\KPIThisWeek.csv"
If you'd like to use my report and see the results within Open Dental use the following:

Code: Select all

DROP TABLE IF EXISTS temp1,temp2,temp3;
SET @FromDate=adddate(curdate(), INTERVAL 1-DAYOFWEEK(curdate()) DAY), @ToDate=adddate(curdate(), INTERVAL 7-DAYOFWEEK(curdate()) DAY);
CREATE TABLE temp1( 
Date date,
$Production double NOT NULL DEFAULT 0,
$Scheduled double NOT NULL DEFAULT 0,
$Total double NOT NULL DEFAULT 0,
$Gross double NOT NULL DEFAULT 0,
$WriteOffs double NOT NULL DEFAULT 0,
$SchedProd double NOT NULL DEFAULT 0,
$SchdWrtOff double NOT NULL DEFAULT 0,
$Adjustments double NOT NULL DEFAULT 0,
$PatIncome double NOT NULL DEFAULT 0,
$InsIncome double NOT NULL DEFAULT 0,
$Collection double NOT NULL DEFAULT 0,
NPs double NOT NULL DEFAULT 0,
Rate CHAR(4) NOT NULL DEFAULT 0);
/*Load Dates of Current Week*/
INSERT INTO temp1(Date)
 VALUES (@FromDate),(ADDDATE(@FromDate, INTERVAL 1 DAY)),(ADDDATE(@FromDate, INTERVAL 2 DAY)),(ADDDATE(@FromDate, INTERVAL 3 DAY)),(ADDDATE(@FromDate, INTERVAL 4 DAY)),(ADDDATE(@FromDate, INTERVAL 5 DAY)),(@ToDate);
/*Gross Prod*/
CREATE TABLE temp2
SELECT DATE(pl.ProcDate) AS 'Date', SUM(pl.ProcFee*(pl.UnitQty+pl.BaseUnits)) AS 'Gross'
FROM procedurelog pl WHERE pl.ProcStatus=2 AND DATE(pl.ProcDate) BETWEEN @FromDate AND @ToDate
GROUP BY DATE(pl.ProcDate);
UPDATE temp1,temp2 SET temp1.$Gross=temp2.Gross WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Sched Prod and writeoffs*/
CREATE TABLE temp2
SELECT DATE(ap.AptDateTime) AS 'Date', SUM(pl.procfee) AS 'SchedProd', SUM(IF((cp.WriteOffEstOverride=-1),IF((cp.WriteOffEst=-1),0,cp.WriteOffEst), cp.WriteOffEstOverride)) AS 'SchdWrtOff'
FROM appointment ap INNER JOIN procedurelog pl ON pl.AptNum=ap.AptNum LEFT JOIN claimproc cp ON cp.ProcNum=pl.ProcNum
WHERE pl.ProcStatus=1 AND ap.AptStatus=1 AND DATE(ap.AptDateTime) BETWEEN @FromDate AND @ToDate AND (ISNULL(cp.Status) OR cp.Status=6 /*estimate*/)
GROUP BY DATE(ap.AptDateTime);
UPDATE temp1,temp2 SET temp1.$SchedProd=temp2.SchedProd , temp1.$SchdWrtOff=-temp2.SchdWrtOff WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Adj*/
CREATE TABLE temp2
SELECT DATE(a.AdjDate) AS 'Date', SUM(a.AdjAmt) AS 'Adjustments' 
FROM adjustment a WHERE a.AdjDate BETWEEN @FromDate AND @ToDate 
GROUP BY DATE(a.AdjDate);
UPDATE temp1,temp2 SET temp1.$Adjustments=temp2.Adjustments WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*PatInc*/
CREATE TABLE temp2
SELECT DATE(pp.DatePay) AS 'Date', SUM(pp.SplitAmt) AS 'PatIncome' FROM paysplit pp 
WHERE pp.DatePay BETWEEN @FromDate AND @ToDate 
GROUP BY DATE(pp.DatePay);
UPDATE temp1,temp2 SET temp1.$PatIncome=temp2.PatIncome WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Writeoffs*/
CREATE TABLE temp2
SELECT DATE(cp.ProcDate) AS 'Date', SUM(cp.WriteOff) AS 'WriteOffs' 
FROM claimproc cp WHERE (cp.Status=1 OR cp.Status=4 OR cp.Status=0) AND (cp.ProcDate BETWEEN @FromDate AND @ToDate)
GROUP BY DATE(cp.ProcDate);
UPDATE temp1,temp2 SET temp1.$WriteOffs=-temp2.WriteOffs WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*InsIncome*/
CREATE TABLE temp2
SELECT DATE(cpay.CheckDate) AS 'Date', SUM(cp.InsPayAmt) AS 'InsIncome'
FROM claimproc cp INNER JOIN claimpayment cpay ON cpay.ClaimPaymentNum=cp.ClaimPaymentNum 
WHERE cpay.CheckDate BETWEEN @FromDate AND @ToDate AND cp.Status IN(1,4) 
GROUP BY DATE(cpay.CheckDate);
UPDATE temp1,temp2 SET temp1.$InsIncome=temp2.InsIncome WHERE temp1.Date=temp2.Date;
UPDATE temp1 SET $Production=ROUND($Gross+$Adjustments+$WriteOffs,0), $Scheduled=ROUND($SchedProd+$SchdWrtOff,0), $Collection=ROUND($InsIncome+$PatIncome,0);
DROP TABLE IF EXISTS temp2;
ALTER TABLE temp1 DROP $Gross, DROP $WriteOffs, DROP $SchedProd, DROP $SchdWrtOff, DROP $Adjustments, DROP $PatIncome, DROP $InsIncome;
/* NPs */
CREATE TABLE temp2
SELECT DATE(p.DateFirstVisit) AS 'Date', COUNT(DISTINCT p.PatNum) AS 'NPs'
FROM patient p LEFT JOIN procedurelog pl ON p.PatNum=pl.PatNum LEFT JOIN appointment a ON p.PatNum=a.PatNum AND a.AptStatus=1 /*sched*/
WHERE (pl.ProcDate BETWEEN @FromDate AND @ToDate AND pl.ProcStatus=2 AND p.DateFirstVisit >= @FromDate) OR (a.AptDateTime BETWEEN @FromDate AND @ToDate AND p.DateFirstVisit >= @FromDate AND a.AptDateTime >= p.DateFirstVisit)
GROUP BY DATE(p.DateFirstVisit);
UPDATE temp1,temp2 SET temp1.NPs=temp2.NPs WHERE temp1.Date=temp2.Date;
DROP TABLE IF EXISTS temp2;
/*Reappt Rate*/
CREATE TABLE temp2(Date date,Seen int NOT NULL DEFAULT 0,Sched int NOT NULL DEFAULT 0);
INSERT INTO temp2(Date,Seen)
SELECT DATE(AptDateTime) AS 'Date', COUNT(DISTINCT PatNum) AS 'Seen'
FROM appointment WHERE (DATE(AptDateTime) BETWEEN @FromDate AND @ToDate) AND AptStatus=2
GROUP BY Date;
CREATE TABLE temp3
SELECT DATE(a1.AptDateTime) AS 'Date', COUNT(DISTINCT a2.PatNum) AS 'Sched'
FROM appointment a1 LEFT JOIN appointment a2 ON a1.PatNum=a2.PatNum
WHERE (DATE(a1.AptDateTime) BETWEEN @FromDate AND @ToDate AND a1.AptStatus=2) AND (DATE(a2.AptDateTime)>DATE(a1.AptDateTime) AND a2.AptStatus=1)
GROUP BY Date;
UPDATE temp2,temp3 SET temp2.Sched=temp3.Sched WHERE temp2.Date=temp3.Date;
UPDATE temp1,temp2 SET temp1.Rate=CONCAT(ROUND((temp2.Sched/temp2.Seen*100),0),'%') WHERE temp1.Date=temp2.Date;
UPDATE temp1 SET $Total=$Production+$Scheduled;
SELECT * FROM temp1
UNION
SELECT 'Total', SUM(A.$Production), SUM(A.$Scheduled), SUM(A.$Total), SUM(A.$Collection), SUM(A.NPs), CONCAT(ROUND((SUM(B.Sched)/SUM(B.Seen)*100),0),'%') FROM temp1 A LEFT JOIN temp2 B ON A.Date=B.Date;
DROP TABLE IF EXISTS temp1,temp2,temp3;
Again, please let me know if you have any questions.
Kevin Rossen
Office Manager, Rossen Dental
Founder, DivergentDental.com
Image

kellyb
Posts: 22
Joined: Thu Jul 10, 2014 5:15 pm

Re: How to setup automated reports by email

Post by kellyb » Thu Jul 10, 2014 5:23 pm

Great write-up - thanks! What I did was automate the generation of the timecard summary report; this was pretty easy since the timecard "Manage" button generates the base query, which I modified per your instructions. Instead of using sendmail I save it into a local folder; I will be changing that soon to a local "synced" folder with a private cloud device I will be deploying which will let me securely access the timesheet summary if I am traveling and need to access it.

I also found the report you created from scratch interesting; will be playing with that one too.

Thanks again!
Kelly

User avatar
Justin Shafer
Posts: 596
Joined: Sat Jul 28, 2007 7:34 pm
Location: Fort Worth, TX.

Re: How to setup automated reports by email

Post by Justin Shafer » Fri Jul 11, 2014 1:36 am

Very nice! Good job!

Post Reply