Closing an MS CRM 3.0 Activity via SDK doesn’t set the ActualEndTime

I’m finding the activities in MS CRM very frustrating to work with, especially the way the states are dealt with.

We just did our first mass-mailing from MS CRM and for the life of me couldn’t work out how to get it to record the activities as closed. Basically we setup a campaign, a marketing list, a campaign activity and then hit “Distribute Campaign Activity”.

However, this just creates me thousands of open activities which effectively show us “to-do” items for the owners of each account (or worse…me!). I googled for ages for a solution to this and only found some piss-poor excuses about it’s up to each Sales person to ‘own’ and deploy the items – ridiculous ! We just sent postal mailers to 1500 people and over 5,000 e-mails!! Sales people are here to sell, not stuff envelopes!

Anyway, having used the MS CRM UI to create the activities I need the activities closed so if a sales guy goes in to the account they can say with confidence “did you receive the mailer we sent you on 15th November?”. My first stab at this basically consisted of:

  1. Creating a dataset and retrieving all of the activities that need altering by querying the filtered views, something like (alter to your needs !!!):
    SELECT     FilteredActivityPointer.activityid, 
    [FilteredActivityPointer].[statecodename] 
    FROM         FilteredCampaignActivity 
    INNER JOIN FilteredCampaignActivityItem 
              ON FilteredCampaignActivity.activityid = FilteredCampaignActivityItem.campaignactivityid 
    INNER JOIN FilteredActivityPointer 
              ON FilteredActivityPointer.regardingobjectid = FilteredCampaignActivity.activityid 
                  AND FilteredCampaignActivity.channeltypecodename = FilteredActivityPointer.activitytypecodename 
    WHERE FilteredActivityPointer.[statecodename]='Open'  -- Only open account/contact activities 
         AND [FilteredCampaignActivity].[subject] LIKE 'Mailer%' -- Name of the Campaign activity (ideally use a Guid instead)
  2. This gives us a list of ActivityIds that need to be ‘closed’, I simply call the GetData method on my dataset to give me a list I can foreach through.
  3. Thus for each open Activity I must iterate through, set the actualendtime and then set the state to close (some helper snippets by David Padbury):
    public static DoClose() 
    { 
    	// Have a datatable called activitiesToBeChanged already populated with rows of type ActivitiesFromCampaignRow 
        foreach (ActivitiesFromCampaignRow row in activitiesToBeChanged.Rows) 
         { 
             activitypointer thisActivity = CrmHelper.RetrieveSingleEntity<activitypointer>(row.activityid, 
                     CreateColumnSet("activityid", "activitytypecode", "statecode")); 
             CloseLetter(thisActivity, ActualEndTime); 
         } 
    } 
    public static ColumnSet CreateColumnSet(params string[] columns) 
    { 
        #region Argument Validation 
        if (columns == null) 
             throw new ArgumentNullException("columns"); 
        if (columns.Length == 0) 
             throw new ArgumentException("There should be at least one column specified.", "columns"); 
        #endregion 
        ColumnSet columnSet = new ColumnSet(); 
        columnSet.Attributes = columns; 
        return columnSet; 
    } 
    public static T RetrieveSingleEntity<T>(Guid id, ColumnSetBase columnSet) 
         where T: BusinessEntity 
    { 
        EntityName entityName = GetEntityNameFromEntityType<T>(); 
        CrmService service = CrmServiceFactory.CreateService(); 
        BusinessEntity entity = service.Retrieve(entityName.ToString(), id, columnSet); 
        return (T)entity; 
    } 
    private static void CloseLetter(activitypointer activity, DateTime ActualEndTime) 
    { 
         CrmService service = CrmServiceFactory.CreateService(); 
        letter thisLetter = RetrieveSingleEntity<letter>(activity.activityid.Value, 
             CreateColumnSet("activityid", "actualend", "subject", "to"));
    
        SetStateLetterRequest setStateRequest = new SetStateLetterRequest() 
        SetStateRequest.EntityId = activity.activityid.Value; 
        TargetUpdateLetter target = new TargetUpdateLetter(); 
        target.Letter = thisLetter;     // Closing the activity doesn't set the end time, so we must do this first 
        if (target.Letter.actualend == null) 
        { 
             target.Letter.actualend = new CrmDateTime();          if (ActualEndTime.Ticks == 0 || ActualEndTime == null) 
                 ActualEndTime = DateTime.Now; 	 target.Letter.actualend.Value = ActualEndTime.ToString("yyyy/MM/ddTHH:mm:ss"); 
    	// Beware SDK is wrong on the formatting here!     } 	UpdateRequest update = new UpdateRequest(); 
    	update.Target = target; 
    	// Now set the time. 
    	UpdateResponse updated = (UpdateResponse)service.Execute(update); 
    
    	public static DoClose() 
    	{ 
    		// Have a datatable called activitiesToBeChanged already populated with rows of type ActivitiesFromCampaignRow 
        	     foreach (ActivitiesFromCampaignRow row in activitiesToBeChanged.Rows) 
    		{ 
    		         activitypointer thisActivity = CrmHelper.RetrieveSingleEntity<activitypointer>(row.activityid,                 CreateColumnSet("activityid", "activitytypecode", "statecode")); 
    			CloseLetter(thisActivity, ActualEndTime); 
    		} 
    	} 
    
    	public static ColumnSet CreateColumnSet(params string[] columns) 
    	{ 
    		#region Argument Validation 
        		if (columns == null) 
    			 throw new ArgumentNullException("columns"); 
    	        if (columns.Length == 0) 
    		throw new ArgumentException("There should be at least one column specified.", "columns"); 
    	    #endregion 
        		ColumnSet columnSet = new ColumnSet(); 
    		    columnSet.Attributes = columns; 
    	    return columnSet; 
    		} 
    public static T RetrieveSingleEntity<T>(Guid id, ColumnSetBase columnSet)
         where T: BusinessEntity 
    {
         EntityName entityName = GetEntityNameFromEntityType<T>(); 
        CrmService service = CrmServiceFactory.CreateService(); 
        BusinessEntity entity = service.Retrieve(entityName.ToString(), id, columnSet); 
        return (T)entity;
     } 
     private static void CloseLetter(activitypointer activity, DateTime ActualEndTime) 
    {
         CrmService service = CrmServiceFactory.CreateService(); 
        letter thisLetter = RetrieveSingleEntity<letter>(activity.activityid.Value,
             CreateColumnSet("activityid", "actualend", "subject", "to")); 
        SetStateLetterRequest setStateRequest = new SetStateLetterRequest();
         setStateRequest.EntityId = activity.activityid.Value;
        TargetUpdateLetter target = new TargetUpdateLetter();
         target.Letter = thisLetter; 
         // Closing the activity doesn't set the end time, so we must do this first
         if (target.Letter.actualend == null)
         {
             target.Letter.actualend = new CrmDateTime(); 
            if (ActualEndTime.Ticks == 0 || ActualEndTime == null)
                 ActualEndTime = DateTime.Now; 
            target.Letter.actualend.Value = ActualEndTime.ToString("yyyy/MM/ddTHH:mm:ss"); // Beware SDK is wrong on the formatting here!
         } 
        UpdateRequest update = new UpdateRequest();
         update.Target = target; 
        // Now set the time.     
        UpdateResponse updated = (UpdateResponse)service.Execute(update); 
        // Now close the activity
         setStateRequest.LetterState = LetterState.Completed;                                // Have to flag as Sent etc.     
    	// from the SDK " Set this field to -1 to have the platform set the appropriate value for the Microsoft CRM application. "
             // Alternatively do something like SELECT * FROM [FilteredStatusMap] WHERE [FilteredViewName] = 'FilteredLetter' to see valid statuses to states
         setStateRequest.LetterStatus = -1; 
        service.Execute(setStateRequest); 
    }

This isn’t meant to be a complete solution, but hopefully will give you enough pointers to be useful, especially with regard to other entity types. I have to say the MS CRM SDK is pretty useless, and the classes are pretty poorly documented

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s