Missing Order Activities

Vote:
 

Hello,

We're in the process of both upgrading from EPiServer 10 to EPiServer 11 and also rewriting from webforms to MVC.  I was having an issue with the cost for the shipment method selected by the user was not being added to the appropriate tables and came back as 0.  I went through some breakpoints and checked the process in both the old project and new project and found that the order activity for the shipping method wasn't being hit like it was previously.  I went into the Commerce Manager to look at the activity and found that it along with the other order activities that we had were not showing up.  I checked the database and they are still set up there, just not showing up and being run when they should be.

I went searching about this and found that there is a new workflow process and am under the assumption that is probably what is causing issues with our order activities.  I saw in that article that it had a refernce to how to supposedly disable the new feature with the below code added to the ecf.app.config file, but it didn't work (I added it to all projects in the solution that had an ecf.app.config file)


    

I'm at a loss now as to where to go from here.  I'm unsure if the entire order activity set up that we previously had is going to need to be reworked or if there is another setting that I'm missing.  Below is some code examples of what we currently have to set up the shipping method that I mentioned (note that a lot if not all the activities were developed back in EPiServer 7.5).

IOrderActivity.cs

public interface IOrderActivity
{
	Guid OrderActivityId { get; set; }
	IDictionary Settings { get; set; }

	/// 
	/// Process order activity
	/// 
	bool ProcessActivity(OrderGroup orderGroup);
}

ShippingCalculateOrderActivity.cs

class ShippingCalculateOrderActivity : IOrderActivity
{
	public Guid OrderActivityId { get; set; }
	public IDictionary Settings { get; set; }

	public bool ProcessActivity(Mediachase.Commerce.Orders.OrderGroup orderGroup)
	{
		// Code to create/modify/update the shipment for the order
		// Price is set to the shipment in here as well

		return true;
	}
}

OrderActivityManager.cs

public static class OrderActivityManager
{
	// Additional Code
	public static OrderActivityDto GetOrderActivities(string categoryName, bool returnInactive)
	{
		Guid applicationId = OrderConfiguration.Instance.ApplicationId;
		string[] keys = new string[] { "order-activities", categoryName, returnInactive.ToString(), applicationId.ToString() };
		string cacheKey = OrderCache.CreateCacheKey(keys);
		OrderActivityDto dataSet = null;
		object cacheObject = OrderCache.Get(cacheKey);
		if (cacheObject != null)
			dataSet = (OrderActivityDto)cacheObject;
		if (dataSet == null)
		{
			DataCommand dataCommand = OrderDataHelper.CreateConfigDataCommand();
			dataCommand.CommandText = "[ecf_OrderActivity_CategorySystemName]";
			dataCommand.Parameters = new DataParameters();
			dataCommand.Parameters.Add(new DataParameter("ApplicationId", (object)OrderConfiguration.Instance.ApplicationId, DataParameterType.UniqueIdentifier));
			DataParameters parameters = dataCommand.Parameters;
			object obj = null;
			if (!string.IsNullOrEmpty(categoryName))
				obj = categoryName;
			parameters.Add(new DataParameter("SystemName", obj, DataParameterType.NVarChar, 128));
			dataCommand.Parameters.Add(new DataParameter("ReturnInactive", (object)returnInactive, DataParameterType.Bit));
			dataCommand.DataSet = new OrderActivityDto();
			string[] strArrays = new string[] { "OrderActivity", "OrderActivityParameter" };
			dataCommand.TableMapping = DataHelper.MapTables(strArrays);
			DataResult dataResult = DataService.LoadDataSet(dataCommand);
			dataSet = (OrderActivityDto)dataResult.DataSet;
			OrderCache.Insert(cacheKey, dataSet, OrderConfiguration.Instance.Cache.PaymentCollectionTimeout);
		}
		return dataSet;
	}

	public static OrderActivityDto GetOrderActivities(string categoryName)
	{
		return OrderActivityManager.GetOrderActivities(categoryName, false);
	}

	public static void processActivities(OrderGroup orderGroup, string categoryName)
	{
		OrderActivityDto orderActivities = OrderActivityManager.GetOrderActivities(/*Thread.CurrentThread.CurrentCulture.Name*/ categoryName);
		foreach (OrderActivityDto.OrderActivityRow orderActivity in orderActivities.OrderActivity)
		{
			Type type = Type.GetType(orderActivity.ClassName);
			if (type == null)
				throw new TypeLoadException(String.Format("Specified order activity method class \"{0}\" can not be created.", orderActivity.ClassName));

			IOrderActivity activity = (IOrderActivity)Activator.CreateInstance(type);
			activity.OrderActivityId = orderActivity.OrderActivityId;
			activity.Settings = OrderActivityManager.getSettings(orderActivity);
			activity.Settings.Add(new KeyValuePair("InstanceName", orderActivity.SystemKeyword));
			bool activityStatus = true;
			try
			{
				activityStatus = activity.ProcessActivity(orderGroup);
			}
			catch (Exception ex)
			{
				if (!orderActivity.ContinueOnError)
					throw (ex);
			}
			if (!orderActivity.ContinueOnError && activityStatus == false)
				throw (new OrderActivityException(OrderActivityException.ErrorType.ActivityError, "1000", string.Format("{0} returned failure status", orderActivity.SystemKeyword)));
		}
	}
	// Additional Code
}

Validation Call that runs processActivities in OrderActivityManager.cs

Validation Call that runs processActivities in OrderActivityManager.cs

I notice that the ApplicationId comes in as all 0s since it is no longer supported, but in the GetOrderActivities call it is looking for the OrderActivities based on the ApplicationId, which makes me wonder if the old way that it was written is going to have to be completely rewritten.

Any feedback would be appreciated.

Thank you,

Kevin Larsen

#198294
Edited, Oct 24, 2018 19:11
Vote:
 

Hello,

Any feedback or ideas on what might need to be done?

Thank you,

Kevin Larsen

#198357
Oct 25, 2018 23:48
Vote:
 

Still looking for help if anyone has any ideas.

Thank you,

Kevin Larsen

#198524
Oct 30, 2018 14:04
Vote:
 

Order Activities being inside the Commerce Manager is probably before my time so can't help much in getting that feature back.

But what I can add to the discussion is that the Feature element that you looked at is for enabling and disabling versions of the workflows that make use of the new promotion system.

Before Commerce 11 you needed the element you posted above to be set to Enabled, now that's no longer the case as it's enabled by default. So if you're using a system that is older than the current workflows way of dealing with activities, then that won't help you.

How does the code where you want to run the activites look like?

Are you using the new order APIs?

Are you using the new promotion system or the old?

#198565
Oct 31, 2018 10:42
Vote:
 

Hi,

Ok that makes sense why it didn't affect any of the other order activities.  We had code develope to use the old promotions system, though we rarely use the actual feature anymore.

For the new order API, we are using a bit of the new API to add a product to the cart and using some of the old API (CartHelper) to update some additional fields that we need populated that were not when using just the new API.  For the rest of the checkout process it remains using the old API as I have not updated any of that code.

For running the activities, in the page controller for we have an action that looks like below.

SaveForm Action

// Saves the shipping option ShippingMethodName and ShippingMethodId to Shipment
// based on user submitted choice
extCartHelper.AcceptCartChanges();
extCartHelper.ValidateCart();

ValidateCart method from ExtCartHelper.cs

cartHelper.Cart.ProviderId = "FrontEnd";
cartHelper.Cart.AcceptChanges();
OrderActivityManager.processActivities(cartHelper.Cart, "CartValidate");
cartHelper.Cart.AcceptChanges();
return new StringDictionary();

OrderActivityManager.processActivities

public static class OrderActivityManager
{
	// Additional Code
	public static OrderActivityDto GetOrderActivities(string categoryName, bool returnInactive)
	{
		Guid applicationId = OrderConfiguration.Instance.ApplicationId;
		string[] keys = new string[] { "order-activities", categoryName, returnInactive.ToString(), applicationId.ToString() };
		string cacheKey = OrderCache.CreateCacheKey(keys);
		OrderActivityDto dataSet = null;
		object cacheObject = OrderCache.Get(cacheKey);
		if (cacheObject != null)
			dataSet = (OrderActivityDto)cacheObject;
		if (dataSet == null)
		{
			DataCommand dataCommand = OrderDataHelper.CreateConfigDataCommand();
			dataCommand.CommandText = "[ecf_OrderActivity_CategorySystemName]";
			dataCommand.Parameters = new DataParameters();
			dataCommand.Parameters.Add(new DataParameter("ApplicationId", (object)OrderConfiguration.Instance.ApplicationId, DataParameterType.UniqueIdentifier));
			DataParameters parameters = dataCommand.Parameters;
			object obj = null;
			if (!string.IsNullOrEmpty(categoryName))
				obj = categoryName;
			parameters.Add(new DataParameter("SystemName", obj, DataParameterType.NVarChar, 128));
			dataCommand.Parameters.Add(new DataParameter("ReturnInactive", (object)returnInactive, DataParameterType.Bit));
			dataCommand.DataSet = new OrderActivityDto();
			string[] strArrays = new string[] { "OrderActivity", "OrderActivityParameter" };
			dataCommand.TableMapping = DataHelper.MapTables(strArrays);
			DataResult dataResult = DataService.LoadDataSet(dataCommand);
			dataSet = (OrderActivityDto)dataResult.DataSet;
			OrderCache.Insert(cacheKey, dataSet, OrderConfiguration.Instance.Cache.PaymentCollectionTimeout);
		}
		return dataSet;
	}

	public static OrderActivityDto GetOrderActivities(string categoryName)
	{
		return OrderActivityManager.GetOrderActivities(categoryName, false);
	}

	public static void processActivities(OrderGroup orderGroup, string categoryName)
	{
		OrderActivityDto orderActivities = OrderActivityManager.GetOrderActivities(/*Thread.CurrentThread.CurrentCulture.Name*/ categoryName);
		foreach (OrderActivityDto.OrderActivityRow orderActivity in orderActivities.OrderActivity)
		{
			Type type = Type.GetType(orderActivity.ClassName);
			if (type == null)
				throw new TypeLoadException(String.Format("Specified order activity method class \"{0}\" can not be created.", orderActivity.ClassName));

			IOrderActivity activity = (IOrderActivity)Activator.CreateInstance(type);
			activity.OrderActivityId = orderActivity.OrderActivityId;
			activity.Settings = OrderActivityManager.getSettings(orderActivity);
			activity.Settings.Add(new KeyValuePair<string, string>("InstanceName", orderActivity.SystemKeyword));
			bool activityStatus = true;
			try
			{
				activityStatus = activity.ProcessActivity(orderGroup);
			}
			catch (Exception ex)
			{
				if (!orderActivity.ContinueOnError)
					throw (ex);
			}
			if (!orderActivity.ContinueOnError && activityStatus == false)
				throw (new OrderActivityException(OrderActivityException.ErrorType.ActivityError, "1000", string.Format("{0} returned failure status", orderActivity.SystemKeyword)));
		}
	}
	// Additional Code
}

The issue that I know is that it hits the GetOrderActivities call and the ApplicationId is no longer supported in EPiServer so when it sends the ApplicationId in the below line it's just sending 0s, which returns nothing so it never returns anything to run causing it to just skip all order activities.

dataCommand.Parameters.Add(new DataParameter("ApplicationId", (object)OrderConfiguration.Instance.ApplicationId, DataParameterType.UniqueIdentifier));

Also, I'm a bit confused by your comment about the order activities being in the Commerce Manager, unless they were removed in 11, but there is (still) a section in the Commerce Manager under Administration -> Order System -> Order Activity -> Order Activities, unless this entire section is costum to us and I did not know that.

Thank you,

Kevin Larsen

#198594
Oct 31, 2018 14:51
Vote:
 

I started working with episerver at around version 9 and I can't remember ever seeing those menu options. I can't find them in QuickSilver nor in other projects.

I can't be a hundred percent sure, but there are some things that make it look like it's custom.

  1. new OrderActivityDto - I can't find a reference to this in any of my projects.
  2. That you have your own interface, IOrderActivity, and OrderActivityManager
  3. I can't find a reference in the database to ecf_OrderActivity_CategorySystemName

But again, it could just be so old that these things no longer exist. But if they still do for you, even after upgrading, it could mean that they're custom.

Have you tried switching over to using the standard workflows? They should be available in your project if you've upgraded.

var result = OrderGroupWorkflowManager.RunWorkflow(cart, OrderGroupWorkflowManager.CartValidateWorkflowName, false);

Or even better yet, use the new APIs, since you've started using some of them anyway:

https://world.episerver.com/documentation/developer-guides/commerce/orders/order-processing/

If you need to port your activities from the potentially super legacy stuff to something new, you might as well go with the newest.

Also, keep in mind that CartHelper will be removed in the near future so porting over things but still keeping that kind of limits you upgrade path in the future:

https://world.episerver.com/blogs/Quan-Mai/Dates/2018/1/carthelper-is-dead-long-live-iorderrepository-/

So I'd say that it's probably easier in the long term (and given your current predicament in the shorter term as well) to switch over to using the new stuff entirely.

#198604
Edited, Oct 31, 2018 17:54
Vote:
 

Hello,

Thank you for all your help with this.  After looking at the Quicksilver site and our site I now see how custom our order activity process is.

I did some looking through what you posted and after doing some searching on the new workflows, I wasn't able to find if there was an ability to set custom settings such as an additional email to send the order confirmation to within the CMS or Commerce Manager.  Is this something that is possible with the new workflow?

We would like to be able to edit them like we were in either the Commerce Manager or even the CMS, but don't know if that's possible or if the effort to accomplish it on our side is possible at the moment.  We also thought about having those settings set in the <appSettings> of the web.config and then transform the settings based on environment.

Thanks again for your help!

Kevin Larsen

#198641
Nov 01, 2018 22:07
Vote:
 

Its pretty difficult for me to comment on custom functionality in general.

But to me it sounds strange inputting email adresses to send order confirmations to in the appSettings or web.config. Sounds more like something that is different per order and so it should be stored on the order? Again difficult to comment on completely custom stuff. 

But in terms of dealing with settings in general you do have several tools at your disposal.

  • A specific CMS page type for settings. Settings that you store here should be ones that are supposed to be easy to change for editors.
  • Web.config / appSettings for settings that are enviroment specific and/or things that shouldn't be easy to change for editors. But still decently easy for technical staff.
  • Custom Admin page under CMS -> Admin, backed by Entity Framework tables in the database. The sky is the limit here. 
  • Metafields on the various order components, taking your email as an example you could add a "CcEmail" metafield to your OrderAddress metaclass. Then when you have that situation, populate that field instead of setting it on a unrelated settings page.

Both the (now) old Workflows and the new Order Processing API are customizable so its only a matter of adding your functionality and loading the settings from wherever you decide to store them.

#198642
Edited, Nov 01, 2018 23:21
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.