/*
* #%L
* BroadleafCommerce Framework
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.broadleafcommerce.core.order.service;
import org.apache.commons.lang.StringUtils;
import org.broadleafcommerce.core.order.domain.BundleOrderItem;
import org.broadleafcommerce.core.order.domain.DiscreteOrderItem;
import org.broadleafcommerce.core.order.domain.GiftWrapOrderItem;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.domain.OrderItem;
import org.broadleafcommerce.core.order.service.call.MergeCartResponse;
import org.broadleafcommerce.core.order.service.call.ReconstructCartResponse;
import org.broadleafcommerce.core.order.service.exception.RemoveFromCartException;
import org.broadleafcommerce.core.order.service.type.OrderStatus;
import org.broadleafcommerce.core.pricing.service.exception.PricingException;
import org.broadleafcommerce.profile.core.domain.Customer;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.annotation.Resource;
/**
* The 2.0 implementation of merge cart service. Instead of merging items directly from one cart to another, we will
* convert the previous cart to a named order that the customer is able to interact with as they see fit.
*
* @author Andre Azzolini (apazzolini)
*/
@Service("blMergeCartService")
public class MergeCartServiceImpl implements MergeCartService {
@Resource(name = "blOrderService")
protected OrderService orderService;
@Resource(name = "blOrderItemService")
protected OrderItemService orderItemService;
@Resource(name = "blFulfillmentGroupService")
protected FulfillmentGroupService fulfillmentGroupService;
@Resource(name = "blMergeCartServiceExtensionManager")
protected MergeCartServiceExtensionManager extensionManager;
@Override
public MergeCartResponse mergeCart(Customer customer, Order anonymousCart)
throws PricingException, RemoveFromCartException {
return mergeCart(customer, anonymousCart, true);
}
@Override
public ReconstructCartResponse reconstructCart(Customer customer) throws PricingException, RemoveFromCartException {
return reconstructCart(customer, true);
}
@Override
public MergeCartResponse mergeCart(Customer customer, Order anonymousCart, boolean priceOrder)
throws PricingException, RemoveFromCartException {
MergeCartResponse mergeCartResponse = new MergeCartResponse();
mergeCartResponse.setMerged(false); // We no longer merge items, only transition cart states
// We need to make sure that the old, saved customer cart is reconstructed with availability concerns in mind
ReconstructCartResponse reconstructCartResponse = reconstructCart(customer, false);
mergeCartResponse.setRemovedItems(reconstructCartResponse.getRemovedItems());
Order customerCart = reconstructCartResponse.getOrder();
if (anonymousCart != null && customerCart != null && anonymousCart.equals(customerCart)) {
// The carts are the same, use either ensuring it's owned by the current customer
setNewCartOwnership(anonymousCart, customer);
mergeCartResponse.setOrder(anonymousCart);
} else if (anonymousCart == null || anonymousCart.getOrderItems().size() == 0) {
// The anonymous cart is of no use, use the customer cart
mergeCartResponse.setOrder(customerCart);
// The anonymous cart is owned by a different customer, so there is no chance for a single customer to have
// multiple IN_PROCESS carts. We can go ahead and clean up this empty cart anyway since it's empty
if (anonymousCart != null) {
orderService.cancelOrder(anonymousCart);
}
} else if (customerCart == null || customerCart.getOrderItems().size() == 0) {
// Delete the saved customer order since it is completely empty anyway. We do not want 2 IN_PROCESS orders
// hanging around
if (customerCart != null) {
orderService.cancelOrder(customerCart);
}
// The customer cart is of no use, use the anonymous cart
setNewCartOwnership(anonymousCart, customer);
mergeCartResponse.setOrder(anonymousCart);
} else {
// Both carts have some items. The anonymous cart will always be the more recent one by definition
// Save off the old customer cart and use the anonymous cart
setSavedCartAttributes(customerCart);
orderService.save(customerCart, false);
setNewCartOwnership(anonymousCart, customer);
mergeCartResponse.setOrder(anonymousCart);
}
if (mergeCartResponse.getOrder() != null) {
Order savedCart = orderService.save(mergeCartResponse.getOrder(), priceOrder, priceOrder);
mergeCartResponse.setOrder(savedCart);
}
return mergeCartResponse;
}
@Override
public ReconstructCartResponse reconstructCart(Customer customer, boolean priceOrder)
throws PricingException, RemoveFromCartException {
ReconstructCartResponse reconstructCartResponse = new ReconstructCartResponse();
Order customerCart = orderService.findCartForCustomer(customer);
if (customerCart != null) {
List<OrderItem> itemsToRemove = new ArrayList<OrderItem>();
for (OrderItem orderItem : customerCart.getOrderItems()) {
if (orderItem instanceof DiscreteOrderItem) {
DiscreteOrderItem doi = (DiscreteOrderItem) orderItem;
if (!checkActive(doi) || !checkInventory(doi) || !checkOtherValidity(orderItem)) {
itemsToRemove.add(orderItem);
}
} else if (orderItem instanceof BundleOrderItem) {
BundleOrderItem bundleOrderItem = (BundleOrderItem) orderItem;
for (DiscreteOrderItem doi : bundleOrderItem.getDiscreteOrderItems()) {
if (!checkActive(doi) || !checkInventory(doi) || !checkOtherValidity(orderItem)) {
itemsToRemove.add(doi.getBundleOrderItem());
}
}
}
}
//Remove any giftwrap items who have one or more wrapped item members that have been removed
for (OrderItem orderItem : customerCart.getOrderItems()) {
if (orderItem instanceof GiftWrapOrderItem) {
for (OrderItem wrappedItem : ((GiftWrapOrderItem) orderItem).getWrappedItems()) {
if (itemsToRemove.contains(wrappedItem)) {
itemsToRemove.add(orderItem);
break;
}
}
}
}
for (OrderItem item : itemsToRemove) {
orderService.removeItem(customerCart.getId(), item.getId(), false);
}
reconstructCartResponse.setRemovedItems(itemsToRemove);
customerCart = orderService.save(customerCart, priceOrder);
}
reconstructCartResponse.setOrder(customerCart);
return reconstructCartResponse;
}
protected void setSavedCartAttributes(Order cart) {
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, ''yy");
Date cartLastUpdated = cart.getAuditable().getDateUpdated();
cart.setName("Previously saved cart - " + sdf.format(cartLastUpdated));
cart.setStatus(OrderStatus.NAMED);
}
protected void setNewCartOwnership(Order cart, Customer customer) {
cart.setCustomer(customer);
// copy the customer's email to this order, overriding any previously set email
if (cart != null && StringUtils.isNotBlank(customer.getEmailAddress())) {
cart.setEmailAddress(customer.getEmailAddress());
}
extensionManager.getProxy().setNewCartOwnership(cart, customer);
}
/**
* @param orderItem
* @return whether or not the discrete order item's sku is active
*/
protected boolean checkActive(DiscreteOrderItem orderItem) {
return orderItem.getSku().isActive(orderItem.getProduct(), orderItem.getCategory());
}
/**
* By default, Broadleaf does not provide an inventory check. This is set up as an extension point if your
* application needs it.
*
* @param orderItem
* @return whether or not the item is in stock
*/
protected boolean checkInventory(DiscreteOrderItem orderItem) {
return true;
}
/**
* By default, Broadleaf does not provide additional validity checks. This is set up as an extension point if your
* application needs it.
*
* @param orderItem
* @return whether or not the orderItem is valid
*/
protected boolean checkOtherValidity(OrderItem orderItem) {
return true;
}
}