Canadian Tax Receipts

Last Updated on Monday, 17 November 2008 05:47 Written by Steve Monday, 17 November 2008 05:47

Canadian charitable organizations have to give out tax receipts for all donations, and these tax receipts have to have sequential numbers. No numbers can be skipped, and they must be unique.

So I built a way to do this back in the day of S-Controls. It was a big pain. I can’t use autonumbers, because we only want to assign them to real gifts that meet certain criteria.

But with Apex triggers, it’s much easier. I wrote a trigger that when an Opportunity is added or updated and meets certain criteria, it gets the next available tax receipt number. The tax receipt number field is also set to be Unique, so I won’t get duplicate values.

Here’s the code. First the Trigger:

trigger ONEN_Opp_tax_receipt on Opportunity (before insert, before update) {
// Written by Steve Andersen, copyright (c) 2008 ONE/Northwest
// This program is released under the GNU General Public License. http://www.gnu.org/licenses/

	ONEN_RecordTypes rtlookup = new ONEN_RecordTypes ('Opportunity');
	// get opp rectype id of gift from Constants
	Id giftRecordType =  rtlookup.GetRecordTypeId(ONEN_Constants.OPP_DEFAULT_RECTYPE_FORTESTS);
	//list to hold Opps to be receipted
	List<Opportunity> oppsNeedingReceipts = new List<Opportunity>();

	//loop through the trigger set
	for ( Opportunity thisOpp : Trigger.new ) {
		//get a tax receipt if it's a gift, it's won, and it doesn't already have one
		if(thisOpp.RecordTypeId == giftRecordType && thisOpp.IsWon && thisOpp.Tax_Receipt_Id__c==null)	{
			//add to the list for receipting
			oppsNeedingReceipts.add(thisOpp);
		}
	}
	//if we found any that need receipting, process them
	if(oppsNeedingReceipts.size()>0){
		//instantiante the tax receipt class
		ONEN_Tax_Receipting taxReceipt = new ONEN_Tax_Receipting ();
		//loop through all the opps
		for (Opportunity updatingOpp : oppsNeedingReceipts) {
			//assign the next tax receipt id to the custom field
			updatingOpp.Tax_Receipt_Id__c = taxReceipt.getNextAvailableTaxReceipt();
		}
	}
}

And the Class:

public class ONEN_Tax_Receipting {
// Written by Steve Andersen, copyright (c) 2008 ONE/Northwest
// This program is released under the GNU General Public License. http://www.gnu.org/licenses/

	//double to hold the highest tax receipt
	private Double highestUsedTaxReceipt;

	//constructor
	public ONEN_Tax_Receipting() {
		Opportunity[] higestTaxReceiptedOpp = [select Tax_Receipt_Id__c from Opportunity where Tax_Receipt_Id__c!=null order by Tax_Receipt_Id__c desc limit 1];
		//get the higest tax receipt number
		if (higestTaxReceiptedOpp.size()>0) {
			highestUsedTaxReceipt = higestTaxReceiptedOpp[0].Tax_Receipt_Id__c!=null ? higestTaxReceiptedOpp[0].Tax_Receipt_Id__c : 0;
		} else {
			highestUsedTaxReceipt = 0;
		}
	}
	//get the next tax receipt number
	public Double getNextAvailableTaxReceipt() {
		highestUsedTaxReceipt++;
		system.debug(highestUsedTaxReceipt);
		return highestUsedTaxReceipt;
	}

	//this opp fits all the criteria and should get a tax receipt
	static testmethod void testNewOppGetsTaxReceipt () {

		ONEN_RecordTypes rtlookup = new ONEN_RecordTypes ('Opportunity');

		Opportunity opp1 = new Opportunity(
			Name = 'test opp1',
			RecordTypeId = rtlookup.GetRecordTypeId('Gift'),
			CloseDate = system.Today(),
			Amount = 1,
			StageName = 'Closed Won'

		);
		insert opp1;
		Opportunity testOpp1 = [select Tax_Receipt_Id__c from Opportunity where id = :o pp1.Id limit 1];

		System.AssertNotEquals ( null,testOpp1.Tax_Receipt_Id__c );
	}
	//this opp already has a tax receipt and it shouldn't be overwritten. The next opp should get the next number.
	static testmethod void testNewOppAlreadyHasTaxReceipt () {

		ONEN_RecordTypes rtlookup = new ONEN_RecordTypes ('Opportunity');

		Opportunity opp1 = new Opportunity(
			Name = 'test opp1',
			RecordTypeId = rtlookup.GetRecordTypeId('Gift'),
			CloseDate = system.Today(),
			Amount = 1,
			StageName = 'Closed Won',
			Tax_Receipt_Id__c = 99999

		);
		insert opp1;
		Opportunity testOpp1 = [select Tax_Receipt_Id__c from Opportunity where id = :o pp1.Id limit 1];

		System.AssertEquals ( 99999,testOpp1.Tax_Receipt_Id__c );

		Opportunity opp2 = new Opportunity(
			Name = 'test opp2',
			RecordTypeId = rtlookup.GetRecordTypeId('Gift'),
			CloseDate = system.Today(),
			Amount = 1,
			StageName = 'Closed Won'

		);
		insert opp2;
		Opportunity testOpp2 = [select Tax_Receipt_Id__c from Opportunity where id = :o pp2.Id limit 1];

		System.AssertEquals ( 100000,testOpp2.Tax_Receipt_Id__c );
	}
	//not won opp shouldn't get tax receipt
	static testmethod void testNewOppNotWon () {

		ONEN_RecordTypes rtlookup = new ONEN_RecordTypes ('Opportunity');

		Opportunity opp1 = new Opportunity(
			Name = 'test opp1',
			RecordTypeId = rtlookup.GetRecordTypeId('Gift'),
			CloseDate = system.Today(),
			Amount = 1,
			StageName = 'Closed Lost'

		);
		insert opp1;
		Opportunity testOpp1 = [select Tax_Receipt_Id__c from Opportunity where id = :o pp1.Id limit 1];

		System.AssertEquals ( null,testOpp1.Tax_Receipt_Id__c );
	}
	//non-gift opp shouldn't get tax receipt
	static testmethod void testNewOppNotGift () {

		ONEN_RecordTypes rtlookup = new ONEN_RecordTypes ('Opportunity');

		Opportunity opp1 = new Opportunity(
			Name = 'test opp1',
			RecordTypeId = rtlookup.GetRecordTypeId('Grant'),
			CloseDate = system.Today(),
			Amount = 1,
			StageName = 'Closed Lost'

		);
		insert opp1;
		Opportunity testOpp1 = [select Tax_Receipt_Id__c from Opportunity where id = :o pp1.Id limit 1];

		System.AssertEquals ( null,testOpp1.Tax_Receipt_Id__c );
	}

}

It’s not bomb-proof. If multiple people are assigning tax receipts at the same time I might have problems. But it’s simple, and it’s not a high volume group. Any feedback is welcome!

Learn More

Race Report: Spirit of Racine Half Ironman

Last Updated on Sunday, 16 November 2008 10:32 Written by Steve Sunday, 16 November 2008 10:32

Spirit of Racine Half Ironman Triathlon, Racine, Wisconsin

Better late than never…this is a race I did almost 4 months ago.

To cap of my trip to Wisconsin, I took part in the Spirit of Racine Half Ironman with 2000 random midwesterners willing to get up at 5 am and jump in Lake Michigan.

I did one Half Ironman last year, and it was a painful experience. I ran out of fuel about 5 hours in and struggled to the finish. My goal was to finish much more gracefully, and beat my time from last year. I was successful in those two goals and had a great race.

Spirit of Racine Half Ironman 2008
Death march up the beach…

A cool aspect about this race is that it was a point-to-point swim, so not curving around buoys and swimming laps in a lake. But that mean you have to walk to the start–in this case 1.2 miles up the beach. It was a beautiful beach, though, and it was cool walking to the foggy start with 2,500 people.

Spirit of Racine Half Ironman 2008
Game face…3 minutes to start.

The start was delayed because of the fog. Not affecting the start time was the 55 degree water temperature. The beach was abuzz with conversation about how cold the water was. And it was really, really, cold. I saw a few people with dish-washing gloves on to keep their hands warm!

Spirit of Racine Half Ironman 2008
Hey, that wasn’t so bad!

There were three aspects of the swim that made it the best open water swim I’ve ever done:

  1. Swimming stright is just plain more fun than swimming in circles.
  2. When you swim point-to-point, there is no bunching up at the corners!
  3. The water was so clear I could see the sand bottom–I just lined myself up with the ripples and used them like lane lines.

The swim was my best leg relative to the field. I really enjoyed it!

I hit the bike and ate and drank like crazy. I was determined to fuel up properly and did a good job staying on my eating schedule. The course was pretty good–through the city of Racine and then out on rural highways. But Wisconsin gets cold in the winter, and years of freezing and thawing causes concrete roads to develop big cracks every 20 feet or so. It was no fun biking 56 miles with that periodic jarring. Ugh. I was glad when it was over!

Spirit of Racine Half Ironman 2008
The last quarter mile was a bit of a haze…

The run was great, if a bit hot. By the time I finished it was 85 degrees. But I kept a steady pace on the two-loop course, and kept in the game by shouting out supportive comments to other runners. I’m not usually very talkative, but I’ve found it’s a great distraction, and I think most people don’t find it too annoying.

The last quarter mile was really hard, but I guess that’s a sign of leaving everything on the course. I was really pleased to have the pain be so short-lived. Much better than last year. I got a PR by 40 minutes, and I’ve never been so pleased to finish in 516th place!

Overall Place: 516
Age Group Place: 89
Overall Time: 05:17:36
Swim: 00:29:52 (554th)
T1: 02:13
Bike: 02:40:59 (652nd)
T2: 01:41
Run: 02:02:50 (650th)

Learn More

Visual Force Email Templates 2

Last Updated on Wednesday, 4 March 2009 05:36 Written by Steve Friday, 14 November 2008 02:41

Using custom components makes lots of stuff possible. Here’s an example of a year-end giving thank you email:

screenshot

This shows all gifts for 2008 and attaches a PDF of the giving history as well. Note that this is done just by connecting to the Contact, so this would work in the Salesforce.com mass mail interface.

Update: You cannot use vf templates in mass email or in the apex outboundemail method in Winter ’09. Maybe next release…

Also note that it’s an ugly HTML email just because I’m not a designer. You can make this as professional as you’re willing to make it.

Here’s the relevant code:

The VisualForce Email Template

<messaging:emailTemplate subject="Thank you for your Support!" recipientType="Contact" >
    <messaging:htmlEmailBody >
        <html>
        <body>
            <p>Hello {!recipient.Household_Greeting__c}--</p>
            <p>Thank you so much for you giving this year. Every gift helps us make a difference in our envrionment...</p>
            <c:thisYearGivingTable ContactId="{!recipient.Id}"/>
            <br/><br/>
            We look forward to seeing you in 2009!
            <br/><br/>
            Best,
            Steve

            </body>
        </html>
    </messaging:htmlEmailBody>
    <messaging:attachment renderas="pdf" filename="{!recipient.Household_Greeting__c}_2008_Giving.pdf">
<html>
<body>
<h3>2008 Giving History for {!recipient.Household_Greeting__c}</h3>
<c:thisYearGivingTable ContactId="{!recipient.Id}"/>

</body>
</html>
</messaging:attachment>
</messaging:emailTemplate>

The VisualForce Component that is included in the Email Template:

<apex:component controller="thisYearGivingTableController" access="global">
	<apex:attribute name="ContactId" description="This is the Contact Id." type="Id" assignTo="{!thisContactId}"/>
	<table border="1">
		<tr>
		    <td><apex:outputText value="Date"/></td>
		    <td><apex:outputText value="Amount"/></td>
		    <td><apex:outputText value="Check Number"/></td>
		    <td><apex:outputText value="Check Date"/></td>
		</tr>
 	<apex:repeat value="{!thisYearOpps}" var="opp" id="theRepeat">
		<tr>
		    <td><apex:outputField value="{!opp.CloseDate}"/></td>
		    <td><apex:outputField value="{!opp.Amount}"/></td>
		    <td><apex:outputField value="{!opp.Check_Number__c}"/></td>
		    <td><apex:outputField value="{!opp.Check_Date__c}"/></td>

		</tr>
	</apex:repeat>
	</table>
</apex:component>

The Apex Controller that powers the logic for the VisualForce Component:

public class thisYearGivingTableController {
	//capture the contact id
	public Id thisContactId {get;set;}
	//a list to hold this year's gifts
	public List<Opportunity> thisYearOpps = new List<Opportunity>();
	//get the gifts into the list
	public List<Opportunity> getThisYearOpps() {
		//criteria for opps
		thisYearOpps = [SELECT Id, Amount,CloseDate, Check_Date__c, Check_Number__c FROM Opportunity
			WHERE IsWon=true AND Year__c=:String.valueOf(system.Today().Year()) AND Id IN
			(SELECT OpportunityId FROM OpportunityContactRole WHERE ContactId = :thisContactId AND Role='Individual Donor')
			ORDER BY CloseDate];
		return thisYearOpps;
	}
}

Kudos to Andrew and the team for making the email template functionality so robust! This is killer stuff!

Learn More
Copyright © 2009 Afterburner - Free GPL Template. All Rights Reserved.
WordPress is Free Software released under the GNU/GPL License.