Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate multiple orders from cart in Hybris storefront?

I'm working with Hybris 6.3 we generated a module for a B2B implementation and I have the requirement of splitting the cart contents into different orders depending on the products in the cart, the billing address, payment, delivery, etc must share the same parameters.

From the requirements we've gathered from our client, we concluded applying consignments strategies aren't what we requiere here, since that handles one order and splits it for shipping and delivery purposes, instead our client's needs are that the different products in the cart to be grouped by a set of shared attributes and generate an order for each group of products.

So far I have identified the class in charge of placing the order in the storefront extension, DefaultCheckoutFacade, which contains the following method:

 @Override
 public OrderData placeOrder() throws InvalidCartException
 {
     final CartModel cartModel = getCart();
     if (cartModel != null)
     {
         if (cartModel.getUser().equals(getCurrentUserForCheckout()) || getCheckoutCustomerStrategy().isAnonymousCheckout())
         {
             beforePlaceOrder(cartModel);
             final OrderModel orderModel = placeOrder(cartModel);
             afterPlaceOrder(cartModel, orderModel);
             if (orderModel != null)
             {
                 return getOrderConverter().convert(orderModel);
             }
         }
     }
     return null;
 }

From what I understand, I should override this method to generate a different CartModel accordingly to the criteria set in my requirements and place an order for each CartModel. I revised the CartModel class, and it's superclass AbstractOrderModel, I'm guessing after the getCart() call in the method above, I just need to modify the entries in the CartModel and make a call to the method placeOrder() for each order I need to place, but is it allowed to modify the CartModel object in this way?

Or if I need to split my cart into different orders, should I approach this in a different way? Is something like having multiple carts but showing them as one something that should be done? Does this kind of modification should be done on the OOTB commercefacades extension? Is there a way to extend it somewhere in our module?

EDIT

I started to read about Multiple Carts and it seems to be what I need, but I haven't found how to save entries to a saved cart. In this link I can see how to save the cart, the method saveCart returns a CommerceSaveCartResultData object, both saveCart and the method getCartForCodeAndCurrentUser receive a CommerceSaveCartParameter which must contain a CartModel, how can I generate an instance of this object to set in a CommerceSaveCartParameter?

like image 839
Uriel Arvizu Avatar asked Dec 24 '22 12:12

Uriel Arvizu


2 Answers

The common approach for this functionality it's using OOTB Order Splitting feature (DefaultOrderSplittingService) for split your order into OrderEntryGroup. Example from Hybris documentation:

<bean id="orderSplittingService" class="de.hybris.platform.ordersplitting.impl.DefaultOrderSplittingService">
<property name="modelService" ref="modelService"/>
<property name="consignmentService" ref="consignmentService"/>
<property name="strategiesList">
    <list>
        <ref bean="splitByDeliveryMode"/>
    </list>
</property>

Strategy implementation:

    public class SplitByDeliveryMode extends AbstractSplittingStrategy
{

    @Override
    public Object getGroupingObject(final AbstractOrderEntryModel orderEntry)
    {
        return orderEntry.getDeliveryMode();
    }

    @Override
    public void afterSplitting(final Object groupingObject, final ConsignmentModel createdOne)
    {
        createdOne.setDeliveryMode((DeliveryModeModel) groupingObject);

    }

}

If you really need to clone Orders you can customize SubmitOrderStrategy, DefaultOrderService and use CloneAbstractOrderStrategy

like image 138
Vitaliy Avatar answered Apr 07 '23 01:04

Vitaliy


In the end I identified that my approach was completely wrong from the beginning, the order splitting shouldn't be done neither generating multiple carts nor using consignments, both were the wrong choices all along.

Since the requirement is for multiple orders to be sent to ERP, and DataHub is the one in charge in doing it in my client's case, the two extensions that required to be extended were: saporderexchange, saporderexchangeb2b and ysaporderfullfillment.

First I created a customsaporderfullfillment extension from the ysaporderfullfillment template, then I had to change the executeAction method in SendOrderToDataHubAction to split the Order generated from Hybris, accordingly to the criteria set:

@Override
public Transition executeAction(final OrderProcessModel process) throws RetryLaterException
{
    LOG.info("Executing Send Order To DataHub Action");
    final OrderModel order = process.getOrder();
    List<SendToDataHubResult> results = new ArrayList<>();
    List<AbstractOrderEntryModel> entries = order.getEntries();
    Map<String, List<AbstractOrderEntryModel>> sortedEntries = sortEntries(entries);
    final Object[] keyArray = sortedEntries.keySet().toArray();
    LOG.info("Number of Orders to generate: " + keyArray.length);

    for (int i = 0; i < keyArray.length; i++)
    {
        // Clone the order
        String newOrderCode = generateOrderCode();
        LOG.info("Generated Order Code " + newOrderCode);
        OrderModel clonedOrder = getOrderService().clone(null, null, order,
                newOrderCode);
        LOG.info("Cloned order.");
        String key = keyArray[i].toString();
        LOG.info("Setting entries for Key: " + key);
        final List<AbstractOrderEntryModel> entriesForCart = sortedEntries.get(key);
        clonedOrder.setEntries(entriesForCart);
        LOG.info("Sending Order to DataHub");
        sendOrder(clonedOrder, results);
    }

    if (!results.isEmpty() && results.stream().allMatch(result -> result.isSuccess())) {
        LOG.info("All Orders were sent successfully.");
        setOrderStatus(order, ExportStatus.EXPORTED);
        resetEndMessage(process);

        return Transition.OK;

    } else {
        LOG.info("Not all Orders were sent successfully.");
        setOrderStatus(order, ExportStatus.NOTEXPORTED);
        handleRetry(process);

        return Transition.NOK;
    }

}

Then I had to extended the saporderexchangeb2b extension with my own customsaporderexchangeb2b extension, I had to create a CustomB2BOrderContributor that would do the mapping into the RawHybrisOrder attributes I required:

@Override
public Set<String> getColumns()
{
    final Set<String> columns = super.getColumns();
    columns.addAll(Arrays.asList(CustomOrderCsvColumns.SALES_ORGANIZATION, CustomOrderCsvColumns.DISTRIBUTION_CHANNEL,
            CustomOrderCsvColumns.DIVISION, CustomOrderCsvColumns.DOCUMENT_TYPE));
    LOG.info("Columns present " + columns.size() + " : " + columns.toString());
    return columns;
}

@Override
public List<Map<String, Object>> createRows(final OrderModel model)
{
    final List<Map<String, Object>> rows = super.createRows(model);
    return enhanceRowsByCustomB2BFields(model, rows);
}

protected List<Map<String, Object>> enhanceRowsByCustomB2BFields(final OrderModel model, final List<Map<String, Object>> rows)
{
    // There is only one row on order level
    final Map<String, Object> row = rows.get(0);
    final String salesArea = ((HelvexProductModel) model.getEntries().get(0).getProduct()).getSalesArea();
    final String[] valuesArray = salesArea.split("_");
    final String salesOrganization = valuesArray[0];
    final String distributionChannel = valuesArray[1];
    final String division = valuesArray[2];
    LOG.info("Enhancing columns of Order " + model.getCode() + " with Sales Organization : " + salesOrganization
            + ", Distribution Channel : " + distributionChannel + ", Division : " + division);
    row.put(CustomOrderCsvColumns.SALES_ORGANIZATION, salesOrganization);
    row.put(CustomOrderCsvColumns.DISTRIBUTION_CHANNEL, distributionChannel);
    row.put(CustomOrderCsvColumns.DIVISION, division);
    row.put(CustomOrderCsvColumns.DOCUMENT_TYPE, Config.getParameter("helvex.org.sales." + salesOrganization));
    row.put(CustomOrderCsvColumns.MOTIVE, sessionService.getAttribute(CustomOrderCsvColumns.MOTIVE));
    LOG.info("Enhanced rows " + row.toString());
    return rows;
}

Since RawHybrisOrder is missing the mapping in saporder-raw, saporder-canonical and saporder-target, I had to add the attributes I added to the rows in raw and canonical, finally I overwrote the target definition of the iDoc to be sent so the attributes I mapped in the canonical were used instead of the standard mappings.

Now you'll say, what about the Hybris order generated? That one is not displayed to the storefront since the Orders we show are retrieved from ERP, not the Hybris database.

like image 39
Uriel Arvizu Avatar answered Apr 07 '23 00:04

Uriel Arvizu