Shipment Construction

Prev Next

Creating a Custom Shipment Constructor

This guide is for Salesforce administrators and developers who want to implement custom shipment construction logic in OrderCentral.


Overview

OrderCentral Commerce uses a plugin architecture for shipment construction. The system dynamically selects a shipment constructor class based on the plugin definition in Salesforce custom metadata. This allows you to override or extend the default shipment construction logic without modifying core code.

Steps to Create and Register a Custom Shipment Constructor

1. Implement Your Custom Constructor Class

  • Create an Apex class that implements the IConstructShipments interface or extends the DefaultShipmentsConstructor.

  • Example class skeleton:

public with sharing class MyCustomShipmentsConstructor implements IConstructShipments {
    public List<ShipmentData> construct(ConstructShipmentsContext context) {
        // Your custom logic here
    }
}

2. Register Your Class as the Plugin

  • Go to Setup > Custom Metadata Types in Salesforce.

  • Find or create a record for Plugin.IConstructShipments.

  • Set the Default_Class_Name__c field to the name of your custom class (e.g., MyCustomShipmentsConstructor).

  • Set the Type__c field to ConstructShipments.

Alternatively, update the metadata file directly if you manage metadata in source control:

<values>
    <field>Default_Class_Name__c</field>
    <value xsi:type="xsd:string">MyCustomShipmentsConstructor</value>
</values>
<values>
    <field>Type__c</field>
    <value xsi:type="xsd:string">ConstructShipments</value>
</values>

3. How the System Uses Your Constructor

  • When the checkout is entered the constructShipments() method on the shipments constructor is called.

  • Your custom logic is executed to construct shipments for the shopping cart.

How Shipment Constructors Are Selected (Admin UI Steps)

As a Salesforce administrator, you can control which shipment constructor logic is used by updating a custom metadata record in the Salesforce Setup UI:

  1. Go to Setup in Salesforce.

  2. In the Quick Find box, type Custom Metadata Types and select it.

  3. Find and click on Manage Records next to Plugin Registration.

  4. Click on New (or edit to change an existing one).

  5. In the Class Name field, enter the name of the Apex class you want to use for shipment construction (for example, MyCustomShipmentsConstructor).

  6. Make sure to select Construct Shipmentsin the Plugin field.

  7. Save your changes.

The system will now use the specified Apex class for constructing shipments whenever the process runs. No code deployment is needed for this change—just update the metadata record in Setup.

Best Practices

  • Always implement the required interface (IConstructShipments).

  • Place your class in the correct domain folder for maintainability.

  • Test your constructor thoroughly with different cart scenarios.

  • Use dependency injection and avoid direct DML/SOQL in your constructor logic—leverage selectors and unit of work patterns as per project standards.

Troubleshooting

  • If your custom logic is not being called, verify the Default_Class_Name__c value in the plugin metadata.

  • Ensure your class is deployed and visible in the org.

  • Check for errors in the ShippingService logic or plugin instantiation.


By following these steps, you can safely extend or override shipment construction logic in OrderCentral Commerce without modifying core code, ensuring maintainability and upgradability.

Example Shipments Constructor

global class ExampleShipmentsConstructor extends welisacommerce.DefaultShipmentsConstructor {
    //override the default constructor to create new shipments
    //based on the shopping cart and its items
    global override List<welisacommerce.ShipmentData> doConstruct(welisacommerce.ConstructShipmentsContext context) {
        //Create two example shipments
        welisacommerce.ShipmentData shipment1 = new welisacommerce.ShipmentData();
        shipment1.record = new welisacommerce__Shipment__c(
            Name = 'Example shipment 1');
        shipment1.items = new List<welisacommerce.ShoppingCartData.Item> { context.shoppingCart.items[0] };

        welisacommerce.ShipmentData shipment2 = new welisacommerce.ShipmentData();
        shipment2.record = new welisacommerce__Shipment__c(
            Name = 'Example shipment 2');
        shipment2.items = new List<welisacommerce.ShoppingCartData.Item> { context.shoppingCart.items[1] };

        return new List<welisacommerce.ShipmentData> { shipment1, shipment2 };
    }

    global override void populateDefaultValues(welisacommerce.ShipmentData shipment) {
        //optional override to implement custom logic for populating default shipment values
        shipment.record.welisacommerce__Delivery_Method__c = 'Shipping';
        shipment.record.welisacommerce__Shipping_City__c = 'Default City';
        shipment.record.welisacommerce__Shipping_Country__c = 'US';
        shipment.record.welisacommerce__Shipping_Postal_Code__c = '12345';
        shipment.record.welisacommerce__Shipping_State__c = 'US-AL';
        shipment.record.welisacommerce__Shipping_Street__c = 'Default Street';
    }
}

Example Shipments Constructor Test

@IsTest
private class ExampleShipmentsConstructorTest {
    @IsTest
    private static void itShouldSetShipmentNameDuringConstruction() {
        //GIVEN
        welisacommerce.ConstructShipmentsContext context = generateContext();

        //WHEN
        Test.startTest();
        List<welisacommerce.ShipmentData> constructedShipments = new ExampleShipmentsConstructor().doConstruct(context);
        Test.stopTest();

        //THEN
        Assert.isNotNull(constructedShipments[0].record.Name);
        Assert.isNotNull(constructedShipments[1].record.Name);
    }

    @IsTest
    private static void itShouldAssignAShoppingCartItemPerShipment() {
        //GIVEN
        welisacommerce.ConstructShipmentsContext context = generateContext();

        //WHEN
        Test.startTest();
        List<welisacommerce.ShipmentData> constructedShipments = new ExampleShipmentsConstructor().doConstruct(context);
        Test.stopTest();

        //THEN
        Assert.areEqual(constructedShipments[0].items[0], context.shoppingCart.items[0]);
        Assert.areEqual(constructedShipments[1].items[0], context.shoppingCart.items[1]);
    }

    @IsTest
    private static void itShouldPopulateDefaultValues() {
        //GIVEN
        welisacommerce.ShipmentData shipmentData = generateShipmentData();

        //WHEN
        Test.startTest();
        new ExampleShipmentsConstructor().populateDefaultValues(shipmentData);
        Test.stopTest();

        //THEN
        Assert.isNotNull(shipmentData.record.welisacommerce__Delivery_Method__c);
        Assert.isNotNull(shipmentData.record.welisacommerce__Shipping_City__c);
        Assert.isNotNull(shipmentData.record.welisacommerce__Shipping_Country__c);
        Assert.isNotNull(shipmentData.record.welisacommerce__Shipping_Postal_Code__c);
        Assert.isNotNull(shipmentData.record.welisacommerce__Shipping_State__c);
        Assert.isNotNull(shipmentData.record.welisacommerce__Shipping_Street__c);
    }

    private static welisacommerce.ShipmentData generateShipmentData() {
        welisacommerce.ShipmentData shipmentData = new welisacommerce.ShipmentData();
        shipmentData.record = new welisacommerce__Shipment__c(
            Name = 'Example Shipment 1'
        );
        return shipmentData;
    }

    private static welisacommerce.ConstructShipmentsContext generateContext() {
        welisacommerce.ConstructShipmentsContext context = new welisacommerce.ConstructShipmentsContext();

        context.shoppingCart = new welisacommerce.ShoppingCartData();
        context.shoppingCart.record = new welisacommerce__Shopping_Cart__c(
            Id = generateId(welisacommerce__Shopping_Cart__c.SObjectType)
        );

        welisacommerce.ShoppingCartData.Item item1 = new welisacommerce.ShoppingCartData.Item();
        item1.record = new welisacommerce__Shopping_Cart_Item__c(
            Id = generateId(welisacommerce__Shopping_Cart_Item__c.SObjectType)
        );
        welisacommerce.ShoppingCartData.Item item2 = new welisacommerce.ShoppingCartData.Item();
        item2.record = new welisacommerce__Shopping_Cart_Item__c(
            Id = generateId(welisacommerce__Shopping_Cart_Item__c.SObjectType)
        );

        context.shoppingCart.items = new List<welisacommerce.ShoppingCartData.Item> { item1, item2 };

        return context;
    }

    private static Integer fakeIdCount = 0;
	/**
	 * Generate a fake Salesforce Id for the given SObjectType
	 */
	private static Id generateId(Schema.SObjectType sobjectType)
	{
		String keyPrefix = sobjectType.getDescribe().getKeyPrefix();
		fakeIdCount++;

		String fakeIdPrefix = ('000000000000').substring(0, 12 - String.valueOf(fakeIdCount).length());

		return Id.valueOf(keyPrefix + fakeIdPrefix + fakeIdCount);
	}
}