For an internationally operating store, it is necessary to have a flexible and generally valid shipping component.
It is required to charge shipping costs according to the location to which the items will be shipped. It must be possible to have different shipping methods whose costs may vary depending on the destination and the attributes of the shipment itself.
Different products must be classified according to their shipping requirements, like products that can only be shipped individually. The Web store must be able to identify such products and charge them differently, if necessary.
Furthermore, the customer should be able to have individual line items of his cart shipped to different locations. Such apportionments must be viewable by customers, modifiable and must be included in the shipping charge calculation.
The shop manager needs the ability to provide different shipping costs for certain products, like additional costs or different shipping charges.
Locations need to be known to the Web store, that is, the Web store has to be able to recognize a certain location. There must be a mapping between geographical objects of the "real world" (like a city, a country, a postal code), and objects which the shop manager can manipulate, like defining shipping costs and rules for them.
Due to legal restrictions and certain business needs, the shipping components must provide more flexible rules. While via assigning a shipping method to a destination, you can define positive rules (which enable shipping), the shop manager needs the power to control exceptions. Certain products are forbidden in some countries, some will cost an extra fee, some raise extra shipping costs.
Therefore, a rule handling is needed to override the general shipping configuration and handle such exceptions, because they have huge impact on the business itself.
Have a look at the corresponding cookbook for related common questions.
A shipping engine must handle geographical objects and aggregate them to useful, business-relevant units. Thus, the shipping engine needs two artifacts:
Geo objects represent countries, states, provinces, counties, cities or postal codes (or anything that may reasonably be called a distinguishable location). They are stored once per installation, as they are considered master data that should not differ across multiple stores. There is no UI to manage geo objects, but there is the ability to import/export them at the central administration level (Operations).
A destination region aggregates one-to-many geo objects to a named region. Regions can be mutually exclusive (like the region GB has the excluded region Channel Islands, so the geo objects of the Channel Islands will not be part of the region GB).
As opposed to the geo objects, destination regions are managed at channel level.
A freight class is a classification element to identify and group similar products from a shipping point of view. It specifies how a product is shipped and where it can be shipped.
Shipping methods represent a carrier (abstract). They are used to display the carrier at the service level in the storefront. Each shipping method has at least one shipping charge plan. In the standard implementation, there are five types of charge plans: weight based, (order) value based, item count based, flat rate and flex based (several charge plans, with a certain selector). Charge plans can be copied or shared from other shipping methods.
Shipping Eligibility
The concept of shipping rules allow to freely create rules, not just configuring some predefined restrictions.
Each rule consists of three parts:
The shipping rules created via the UI are stored in the database using the data model of promotion rules. But the shipping rules are actually translated from the data model of bc_ruleengine to the business rule language that can be processed via the rule engine. So this requires some kind of mapping software, the ShippingRuleMapper. This component is configurable via an XML file named shippingruleconfiguration.xml, which can be provided with every cartridge.
Note
When a new configuration is loaded with a new cartridge, the new settings overwrite the existing ones. That is, the last cartridge wins, and overwritten configurations are no longer effective.
The number of selectable conditions is determined by the different instances of condition descriptors registered at the ShippingConditionDescriptorRegistry. For further information of that concept, please refer to the promotions framework description.
The selectable actions are generally hard-coded in the ShippingRuleAction_52 template.
Due to the fact that persistent objects are not transferable and are mostly related to other complex types, there is a mapping to "simpler" business objects, which the rule engine can easily handle. There are some predefined types. The necessary information of the persistent objects (POs) or the business objects (BOs) of the business object framework is mapped to simple, easy-to-handle objects, which will be transferred to the working memory of the rule engine. (The rule engine can also handle the POs or BOs directly, but Intershop strongly recommends to avoid this functionality.)
Shipping rule business objects are always created by their factory. Objects that need to provide the ability for extension extend the AbstractExtensibleObject. Every factory needs to be registered at the ShippingRuleObjectMapper via the component framework.
In the current implementation, the following factories and their according objects are available:
The ShippingRuleExecutor is the component that actually executes rules, that is, the one that processes the in-going information, starts a rule engine session, and processes the results to make them usable in further business processes.
These are classes that implement the AbstractRuleExecutor and an interface for special needs (i.e., the intended input and output for convenience). The mapping, if necessary, is done inside that class as well as the rule execution.
There are different types of ShippingRuleExecutors, distinguished by their purpose (what are the parameters, and what kind of result is expected).
For every shipping rule action, there is a dedicated rule executor instance created via the component framework:
Shipping Action | Shipping Rule Executor Component | Shipping Rule Executor Class |
---|---|---|
Item: Surcharge | shippingSurchargeRuleExecutor | ShippingExtraChargeRuleExecutor |
Item: Import Surcharge | shippingImportSurchargeRuleExecutor | ShippingExtraChargeRuleExecutor |
Item: Geographical Surcharge | shippingGeographicalSurchargeRuleExecutor | ShippingExtraChargeRuleExecutor |
Item: Exclude from Shipping | shippingRestrictionRuleExecutor | RestrictionRuleExecutor |
Item: Override Shipping Charge | shippingOverrideRuleExecutor | ShippingExtraChargeRuleExecutor |
Item: Eligible Shipping Method | shippingEligibilityRuleExecutor | ShippingEligibilityRuleExecutor |
Bucket: Surcharge | shippingBucketSurchargeRuleExecutor | ShippingExtraChargeRuleExecutor |
Bucket: Override Shippping Charge | shippingBucketBucketOverrideRuleExecutor | ShippingExtraChargeRuleExecutor |
There are 5 different types of plans how to spread the shipping costs on the line items. This means also a different tax calculation for them.
Example:
There is a basket with 3 line items.
It is a total basket amount of 100 €.
The shipping costs are set on 10 €. All values are net amounts.
Note
The default splitting of the shipping costs on the line items is the Item Count Based. If the default is not defined the Weight Based is taken.
The 10 € are divided equal on the 3 line items. So there are 3.33 € for each line item. For the first line there is 20% tax, so the shipping tax amount for this line item is 3.33 € * 0.20 = 0.67 €.
For the second line it is 3.33 € * 0.10 = 0.33 €.
And finally for the last there is no tax, so the total shipping tax is 0.67 € + 0.33 € = 1.00 €.
In the Weight Based case the 10 € is divided as followed: total weight/line item weight * shipping amount.
For line item 1 it is 1000g/6000g * 10 € = 10/6 € = 1.67 €.
For line item 2 it is 2000g/6000g * 10 € = 10/3 € = 3.33 €.
For line item 3 it is 3000g/6000g * 10 € = 10/2 € = 5.00 €.
The shipping tax is for line item 1 is 1.67 € * 0.2 = 0.33 €.
The shipping tax is for line item 2 is 3.33 € * 0.1 = 0.33 €.
For the last line item there is no tax, so the total tax is 0.66 €.
In this case the 10 € is divided as followed: total basket amount/line item amount * shipping amount.
For line item 1 it is 50 €/100 € * 10 € = 0.5 * 10 € = 5 €.
For line item 1 it is 30 €/100 € * 10 € = 3/10 * 10 € = 3 €.
For line item 1 it is 20 €/100 € * 10 € = 2/10 * 10 € = 2 €.
The shipping tax is for line item 1 is 5 € * 0.2 = 1 €.
The shipping tax is for line item 2 is 3 € * 0.1 = 0.3 €.
For the last line item there is no tax, so the total tax is 1.3 €.
This scenario is taxation wise calculated identically to Flat Rate scenario. The difference here is only that different total shipping amounts can be defined in respect to the total quantity of the basket items.
In this scenario the total shipping costs and the shipping charge plan can be defined independently. But in the end, the selected shipping charge plan defines the tax calculation.
Note
These rules are also applied to distribute the bucket surcharges (including surcharge tax) to the line items.
A shipping bucket is an aggregation of line items.
In the reference implementation, line items of a shipping bucket have the same destination and the same shipping method (BasketShippingBucketBO
). There is also an BasketShippingMethodSelectionBucketBO
. As this kind of bucket is used for storefront user interaction purposes (like selecting a shipping option for several buckets), the key used for its creation is not only the destination and the selected shipping method, but also the set of shipping methods that is eligible for every line item.
For this the standard comes with two kind of bucket interfaces. Those instances are created by one of the 2 composer that comes with the Intershop standard implementation.
While the basic ShippingBucketComposer
interface represents functionality that is used to do a real composing inside the basket calculation which is saved in an attribute of the basket table.
The BasketShippingMethodSelectionBucketComposer
is used to offer grouping functionality in the shipping method selection step only. Its results are transient.
The Business Object Layer offers 2 extensions for both use cases.
/** * This extension covers all shipping bucket related functionality for the basket * business object. */ public interface BasketBOShippingBucketExtension extends BusinessObjectExtension<BasketBO> { /** * The ID of the created extensions which can be used to get them from the business object later. */ public static final String EXTENSION_ID = "ShippingBucket"; /** * Returns shipping buckets composed from the line items in this line item * container. Composition is done based on: * <ul> * <li>ship-to address * <li>ship-from address * <li>shipping method * <ul> * whereas the ship-from address is optional. A specific line item can be * only assigned to one shipping bucket at the same time. * * @return shipping buckets composed from the line items in this line item * container */ public Collection<BasketShippingBucketBO> getBasketShippingBucketBOs(); /** * Returns a shipping bucket which covers all product line items with the * provided address, shipping method and ship alone flag. * * @param address * the address * @param shippingMethod * the shipping method * @param shipAlone * the isShipAlone flag * @return the shipping bucket */ public BasketShippingBucketBO getBasketShippingBucketBO(AddressBO address, BasketShippingMethodBO shippingMethod, boolean shipAlone); }
/** * This extension covers all shipping method related functionality for the * basket business object. * * @author Frank E. Hofmann * */ public interface BasketBOShippingMethodExtension extends BusinessObjectExtension<BasketBO> { /** * The ID of the created extensions which can be used to get them from the * business object later. */ public static final String EXTENSION_ID = "ShippingMethod"; public Collection<BasketShippingMethodSelectionBucketBO> getBasketShippingMethodSelectionBucketBOs(); /** * Returns a collection of bucket but filtered. This version does not contains digital shipping buckets * like E-Mail-Shipping Buckets. * * @return A collection of buckets. */ public Collection<BasketShippingMethodSelectionBucketBO> getNonDigitalBasketShippingMethodSelectionBucketBOs(); public Set<BasketShippingMethodSelectionBucketBO> getBasketShippingMethodSelectionBucketBOsAsSet(); public Collection<EligibleShippingMethodBO> getEligibleShippingMethodBOs(); /** * Returns a shipping bucket which covers all product line items with the * provided address, shipping method and ship alone flag. * * @param address * the address * @param shippingMethod * the shipping method * @param shipAlone * the isShipAlone flag * @return the shipping bucket */ public BasketShippingMethodSelectionBucketBO getBasketShippingMethodSelectionBucketBO(AddressBO address, BasketShippingMethodBO shippingMethod, boolean shipAlone); }
public interface ShippingBucketComposer { public List<ShippingBucket> composeShippingBuckets(LineItemCtnr lineItemCtnr); public List<ShippingBucket> composeShippingBuckets(Set<LineItem> lineItemSet); }
All shipping-related independent business component implementations, contracts and instances are defined in bc_shipping.
bc_shipping
└───staticfiles
└───cartridge
└───components
└───implementations.component
Hence, the current definition of the shipping bucket composer is:
<components xmlns="http://www.intershop.de/component/2010"> <contract name="ShippingBucketComposer" class="com.intershop.component.shipping.capi.shippingbucketcomposer.ShippingBucketComposer" /> </components>
The calculation is split into several steps, which correspond to the different charges that may apply to a bucket or a ProductLineItem, and the hierarchy of the different charges between each other.
The entire calculation is done via the following rules, which are a part of the calculation rule set.
Rule | Description |
---|---|
ShippingBucketCreationRule | Creates the buckets used for calculation |
ShippingExtraChargeCalculatorRule | Determines all additional charges that may be applied |
ShippingPLIChargeCalculatorRule | Does the individual calculation of the bucket costs according to the line items, and splits the bucket costs down to PLI costs |
ShippingGroupedChargeCalculatorRule | Sums surcharges grouped by the identifier of the rule that adds the surcharge |
CalculateTaxSumsPerRateRule | Separates all taxes by its tax rates |
BucketCostDistributionRule | Distributes the amount of surcharges |
AddMoneyRule | Determines the total shipping costs finally |
RoundMoneyRule | Rounds the given money items |
MultiplyMoneyWithPercentageRule | Multiplies prices with an percentage value |
Note
A bucket depends from service line Items that are created within the SetSelectedShippingMethod
pipelet by the class ShippingMethodHandler
.