Recently had a need to create excel reports from a coldfusion online store application. There are basically two ways to do this, a simple way and a not so simple way. Today I'm going to show you the simple way.
Below is all the query, code and javascript you need to have a working orders recap pushed from coldfusion to excel. Hosted on a Windows 2003 Webserver using MySQL 5.x and Railo 3.0.x. Average daily web load including bots is 10K plus with no time to load degradation worth mentioning.
Let's start with the query used to populate the data. Its important to get this correct because empty, null or undefined fields have a tendency to muck up the cf landscape with regard to exports... lesson learned from xml transforms!
Query file named: qryOrdersRecap4XCL.cfm
**** The following query pulls data into an html table which will translate into an excel spreadsheet. The query is for all sales in a given date range including affiliate commissions ordered from first to last of date period.
<cfquery name = "qryOrdersRecap4XCL" datasource="#request.dsn#">
SELECT o.ORDERNUMBER, o.DateOfOrder, o.CRTQUANTITY, o.CrtPrice, o.QuotedShipping, o.mc_fee, o.DiscountAmount, o.PromoCode, c.FirstName AS OrderFirstName, c.LastName AS OrderLastName,
IFNULL( aflt.affiliateid, "-" ) AS affiliateid, IFNULL( aflt.commission, "0" ) AS commission
FROM orders o
INNER JOIN customerhistory c
ON o.customerid=c.customerid
LEFT OUTER JOIN afl_transactions aflt
ON o.OrderNumber = aflt.OrderNumber
WHERE 1 = 1
AND o.paymentstatus = 'Completed'
AND o.DateOfOrder between '#dateformat( form.BegDate, "yyyy-mm-dd" )#' and '#dateformat( form.EndDate, "yyyy-mm-dd" )#'
ORDER BY OrderID;
</cfquery>
CF display and action code page named: dspOrdersRecap4XCL.cfm
**** Need to instantiate some beginning and ending dates so our initial load doesn't putz on us. You can handle this anyway you want, this is how i chose to do so.
<cfset TempBegDate = now() - 7>
<cfset TempEndDate = now()>
<cfparam name = "BegDate" default='#Dateformat( TempBegDate, "m/dd/yyyy" )#'>
<cfparam name = "EndDate" default='#Dateformat( TempEndDate, "m/dd/yyyy" )#'>
**** Form to allow choice of Beginning and Ending dates. You can download the calendar and maketable javascript and html display page in the form tags below by right clicking and save as here.
<cfform name="ORDER2" method="post" action="#CGI.script_name#?#CGI.QUERY_STRING#">
<table width="650" border="0" cellspacing="0" cellpadding="1">
<tr>
<td>Beginning Date:
<cfinput name="BegDate" type="text" id="BegDate" size="13" Value='#BegDate#' required="Yes" message="You must provide a valid beginning date for this Report">
<a href = "Calendar Control" onClick="JavaScript:window.open( 'calendar.html?form=ORDER2&field=BegDate&format=S&bgcolor=Yellow&txtcolor=Blue&hdrcolor=
CornFlowerBlue&todaycolor=White&offset=0','cal','noresize,width=225,height=160' );return false"><img border="0" src="icons/calendar.gif" width="16" height="16" alt="Select Beginning Date from Calendar"></a></td>
<td>Ending Date:
<cfinput name="EndDate" type="text" id="EndDate" size="13" Value='#EndDate#' required="Yes" message="You must provide a valid beginning date for this Report">
<a href = "Calendar Control" onClick="JavaScript:window.open( 'calendar.html?form=ORDER2&field=EndDate&format=S&bgcolor=Yellow&txtcolor=Blue&hdrcolor=
CornFlowerBlue&todaycolor=White&offset=0','cal','noresize,width=225,height=160' );return false"><img border="0" src="icons/calendar.gif" width="16" height="16" alt="Select Ending Date from Calendar"></a></td>
<td>
<input type="hidden" value="2" name="process">
<input type="submit" value="Generate Report" name="ORDER2">
</td>
</tr>
</table>
</cfform>
**** Below is the code that creates the simple excel tables and then pushes them to a save dialog. I used if statement to see if the form above had been submitted which if it has not then the below code doesn't run.
<cfif isdefined( 'form.process' )>
**** Call the above query.
<!--- Query orders data from db --->
<cfinclude template="../queries/qryOrdersRecap4XCL.cfm">
**** i use the precalcs for cell positioning and cell specific calcs with formulas.
<!--- pre calc the cell row location for reference total rollup. --->
<cfset CellRowCount = qryOrdersRecap4XCL.recordcount + 3>
<cfset CellRowReferenceOneLess = qryOrdersRecap4XCL.recordcount + 9>
<cfset CellRowReference = qryOrdersRecap4XCL.recordcount + 10>
<cfset CellIndex = 3>
<!--- Use cfsetting to block output of HTML
outside of cfoutput tags. --->
<cfsetting enablecfoutputonly="Yes">
**** I programmatically build the name of the file that is created and pushed to a save as dialog. Notice that I'm using cfheader to determine the disposition of the content (the data in the excel table) and I'm using the inline command (I probably should have used the 'attachment' type but it kept failing, you can also use 'filename' instead) to force the save as dialog. More on types here.
<!--- Suggest default name for XLS file. --->
<!--- "Content-Disposition" in cfheader also ensures
relatively correct Internet Explorer behavior. --->
<cfheader name="Content-Disposition" value="inline; filename=Orders_Recap_From_#DateFormat(BegDate)#_To_#DateFormat(EndDate)#_GeneratedOn_#DateFormat(Now())#.xls">
<!--- Set content type. --->
<cfcontent type="application/vnd.ms-excel">
<!--- Format data using cfoutput and a table.
Excel converts the table to a spreadsheet.
The cfoutput tags around the table tags force output of the HTML when
using cfsetting enablecfoutputonly="Yes" --->
<cfoutput>
**** I use table headers because it allows certain formatting to occur. I tried getting the code to format colors, etc., but it would not render in open office. I'm not sure about ms excel.
<table cols="23">
<thead>
<tr>
<th align="left" colspan="23">Income Recap</th>
</tr>
<tr>
<th align="left" colspan="23">Created: #DateFormat(Now())# #TimeFormat(Now())#</th>
</tr>
<tr>
<th align="left">Date</th>
<th align="left">Order Number</th>
<th align="left">First Name</th>
<th align="left">Last Name</th>
<th align="left">## Units</th>
<th align="left">Unit Price</th>
<th align="left">Order Total</th>
<th align="left">Shipping</th>
<th align="left">Total Income</th>
<th align="left">Fees</th>
<th align="left">Amt Pd SFP</th>
<th align="left">Affiliate Com</th>
<th align="left">Affil Com Adj.</th>
<th align="left">Coupon</th>
<th align="left">Affiliate Number</th>
<th align="left">Coupon Number</th>
<th align="left">Total</th>
<th align="left">Check ##</th>
<th align="left">Date Paid</th>
<th align="left">Refunds</th>
<th align="left">Fee Refunds</th>
<th align="left">Gross Net</th>
<th align="left">Fees</th>
<th align="left">Net Profit</th>
</tr>
</thead>
**** The table body section is where i dump all the data using a loop. I found the cfparams important otherwise you get variable not defined errors. cellindex is initially set to the last header row count in this case the 3 row. it increments from there so you can programmatically perform calculations at each row if necessary.
<tbody>
<cfloop query="qryOrdersRecap4XCL">
<cfparam name="CRTQUANTITY" default="0">
<cfparam name="QuotedShipping" default="0">
<cfparam name="calcOrderTotal" default="0">
<cfparam name="mc_fee" default="0">
<cfparam name="calcAmtPaidSFP" default="0">
<cfparam name="Commission" default="0">
<cfparam name="DiscountAmount" default="0">
<cfset CellIndex = CellIndex + 1>
<cfset calcUnitPrice = '' >
<cfset calcUnitPrice = #CrtPrice# / #CRTQUANTITY# >
<cfset calcOrderTotal = '' >
<cfset calcOrderTotal = #CrtPrice# + #QuotedShipping# ><!--- dollarFormat(calcProfit) --->
<cfset calcAmtPaidSFP = '' >
<cfif QuotedShipping eq 0>
<cfset calcAmtPaidSF = (#CRTQUANTITY# * 1.9) + #QuotedShipping#>
<cfelse>
<cfset calcAmtPaidSF = (#CRTQUANTITY# * 1.9) + #QuotedShipping# - 1>
</cfif>
<cfset calcProfit = '' >
<cfset calcProfit = #calcOrderTotal# - #mc_fee# - #calcAmtPaidSFP# - #Commission# >
<tr>
<td align="left">#DateFormat( DATEOFORDER,'mm/dd/yyyy' )#</td>
<td align="left">="#ORDERNUMBER#"</td>
<td align="left">#ORDERFIRSTNAME#</td>
<td align="left">#ORDERLASTNAME#</td>
<td align="left">#CRTQUANTITY#</td>
<td align="left">#dollarFormat(calcUNITPRICE)#</td>
<td align="left">#dollarFormat(CrtPrice)#</td>
<td align="left">#dollarFormat(QuotedShipping)#</td>
<td align="left">#dollarFormat(calcOrderTotal)#</td>
<td align="left">#dollarFormat(mc_fee)#</td>
<td align="left">#dollarFormat(calcAmtPaidSFP)#</td>
<td align="left">#dollarFormat(Commission)#</td>
<th align="left">#dollarFormat(0)#</th>
<td align="left">#dollarFormat( DiscountAmount )#</td>
<td align="left">#affiliateid#</td>
<td align="left">#PromoCode#</td>
<td align="left">=I#CellIndex#-J#CellIndex#-K#CellIndex#-L#CellIndex#-M#CellIndex#-N#CellIndex#</td>
<th align="left"> </th>
<th align="left"> </th>
<th align="left"> </th>
<th align="left"> </th>
<th align="left"> </th>
<th align="left"> </th>
<th align="left"> </th>
</tr>
</cfloop>
</tbody>
**** This is the totals row after all the data above is processed. Notice how i've precalculated the cellrowcount. I added the query record count to 3 (the number of header rows) to get the last line for totals. Notice how i used non-breaking spaces entities for empty cells. column span works great also.
<tr>
<td style="border-top: 2px solid ##000000" align="right" colspan="4">Totals:</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(E4:E#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left"> </td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(G4:G#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(H4:H#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(I4:I#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(J4:J#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(K4:K#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(L4:L#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(M4:M#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(N4:N#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="left"> </td>
<td style="border-top: 2px solid ##000000" align="left"> </td>
<td style="border-top: 2px solid ##000000" align="left">=SUM(Q4:Q#cellrowcount#)</td>
<td style="border-top: 2px solid ##000000" align="right" colspan="7"> </td>
</tr>
</table>
</cfoutput>
</cfif>
So, that is all there is too it. These recaps originally were done by hand and took upwards of 2-3 hours per day. Now the recap can be done at the end of the month and it runs in less than 4 secs. I added a column for adjustments which if all the orders need adjustment, total recap finish time is around 3-4 hours for a times saving of 80 plus hours per month!
Technorati Tags:
export
coldfusion
excel
html table method
simple
railo
mysql