Add To Cart Workflow For Heat Clinic Tutorial
As a developer, you may be presented with interesting requirements in your implementation of Broadleaf Commerce. For example, a restriction preventing adding a hot sauce with heat range 1 into a cart that already has a hot sauce with heat range 5 might be desirable. Let's hook into the Add to Cart Workflow to enforce this!
You may notice that this tutorial is very similar to the other tutorial on modifying the order submit workflow
Hooking into the add item workflow
The first thing we need to do to override a Broadleaf Workflow is to place the default configuration into our possession. This is very easy -- just add the following to the bottom of your applicationContext.xml
file:
<bean id="blAddItemWorkflow" class="org.broadleafcommerce.core.workflow.SequenceProcessor">
<property name="processContextFactory">
<bean class="org.broadleafcommerce.core.order.service.workflow.CartOperationProcessContextFactory"/>
</property>
<property name="activities">
<list>
<bean class="org.broadleafcommerce.core.order.service.workflow.add.ValidateAddRequestActivity"/>
<bean class="org.broadleafcommerce.core.order.service.workflow.CheckAvailabilityActivity"/>
<bean class="org.broadleafcommerce.core.order.service.workflow.add.AddOrderItemActivity"/>
<bean class="org.broadleafcommerce.core.order.service.workflow.add.AddFulfillmentGroupItemActivity"/>
<bean class="org.broadleafcommerce.core.order.service.workflow.VerifyFulfillmentGroupItemsActivity"/>
</list>
</property>
<property name="defaultErrorHandler" ref="blSilentErrorHandler"/>
</bean>
This is a copy of the default workflow. You can find the default workflows in the following place in the Broadleaf codebase:
core/broadleaf-framework/src/main/resources/bl-framework-applicationContext-workflow.xml
When Broadleaf finds the blAddItemWorkflow
bean definition in your application context, it will use it to override the default one provided by Broadleaf.
Create a new activity
We need to add an activity that checks for the heat range restriction. In the site
project, we'll create ValidateHeatRangeRestrictionActivity.java
in the com.mycompany.order.service.workflow.add
package with the following contents:
public class ValidateHeatRangeRestrictionActivity extends BaseActivity {
private static final Log LOG = LogFactory.getLog(ValidateHeatRangeRestrictionActivity.class);
@Resource(name = "blCatalogService")
protected CatalogService catalogService;
@Override
public ProcessContext execute(ProcessContext context) throws Exception {
// Get our seed data
CartOperationRequest request = (CartOperationRequest) context.getSeedData();
Long skuId = request.getItemRequest().getSkuId();
Order cart = request.getOrder();
Sku sku = catalogService.findSkuById(skuId);
Product product = sku.getProduct();
Integer heatRange = getHeatRange(product);
// Heat Range will be null for non-hot sauce products
if (heatRange != null && heatRange == 1) {
for (DiscreteOrderItem doi : cart.getDiscreteOrderItems()) {
Integer doiHeatRange = getHeatRange(doi.getProduct());
if (doiHeatRange != null && doiHeatRange == 5) {
throw new InvalidSauceHeatRangeException("Trying to add heat range 1 when heat range 5 in cart");
}
}
}
return context;
}
/**
* @return the heatRange attribute of a product if it exists -- null otherwise
*/
protected Integer getHeatRange(Product product) {
ProductAttribute heatRangeAttr = product.getProductAttributes().get("heatRange");
// Heat Range will be null for non-hot sauce products
if (heatRangeAttr != null) {
try {
return Integer.parseInt(heatRangeAttr.getValue());
} catch (Exception e) {
// All The Exceptions!! (We'll return null, do nothing)
}
}
return null;
}
}
We also need the exception that gets thrown if the heat range isn't high enough:
public class InvalidSauceHeatRangeException extends RuntimeException {
private static final long serialVersionUID = -8828778897245219116L;
public InvalidSauceHeatRangeException() {
super();
}
public InvalidSauceHeatRangeException(String message, Throwable cause) {
super(message, cause);
}
public InvalidSauceHeatRangeException(String message) {
super(message);
}
public InvalidSauceHeatRangeException(Throwable cause) {
super(cause);
}
}
Add our activity to the workflow
This is a one-liner:
<bean class="com.mycompany.order.service.workflow.add.ValidateHeatRangeRestrictionActivity"/>
Just add that line between the ValidateAddRequestActivity
and the CheckAvailabilityActivity
like so:
<bean class="org.broadleafcommerce.core.order.service.workflow.add.ValidateAddRequestActivity"/>
<bean class="com.mycompany.order.service.workflow.add.ValidateHeatRangeRestrictionActivity"/>
<bean class="org.broadleafcommerce.core.order.service.workflow.CheckAvailabilityActivity"/>
and our add item restriction is in place.
Handle the error scenario
We want to let the customer know about this restriction if their add was unsuccessful. We'll modify the catch block in the addJson
method in CartController
to handle this exception.
...
} catch (AddToCartException e) {
if (e.getCause() instanceof RequiredAttributeNotProvidedException) {
responseMap.put("error", "allOptionsRequired");
} else if (e.getCause() instanceof InvalidSauceHeatRangeException) {
responseMap.put("error", "invalidHeatRange");
} else {
throw e;
}
}
...
and then we'll handle it in the JavaScript as well. We'll want to look at cartOperations.js
in the handler that is bound to input.addToCart
and input.addToWishlist
. Change this chunk:
if (data.error == 'allOptionsRequired') {
$errorSpan.css('display', 'block');
$errorSpan.effect('highlight', {}, 1000);
} else {
HC.showNotification("Error adding to cart");
}
to this:
if (data.error == 'allOptionsRequired') {
$errorSpan.css('display', 'block');
$errorSpan.effect('highlight', {}, 1000);
} else if (data.error == 'invalidHeatRange') {
HC.showNotification("This hot sauce isn't hot enough!");
} else {
HC.showNotification("Error adding to cart");
}
And that's it! You've now completely added the heat range restriction! Congratulations on alienating your customers and selling less hot sauce!