In some cases Broadleaf needs to split up an existing Order Fulfillment, whether it’s being split so that each vendor has its own fulfillment (see Order Submission), or only part of a fulfillment is changing to a new status (see Changing Status).
There are two inputs when splitting an Order Fulfillment:
Original Fulfillment - what is the fulfillment we need to split?
Item Quantities - which items do we want to "split out" from the original?
This is a list, where each element represents one new fulfillment we want to "split out" from the original
Each element contains a map of Order Fulfillment Item IDs to the amount of that item we want to apportion to this particular new fulfillment
All item amounts not specified here will stay in the original fulfillment
Based on that, our output will be a list of Order Fulfillments containing:
A fulfillment which used to be the original, now containing all leftover items not specifically requested to split into new fulfillments
All the new fulfillments, each containing the item quantities requested to be split
In this example, we request to split Fulfillment 1 so that two new fulfillments are created - one with 20 of Item 2 and 10 of Item 3, and another with 10 of Item 3 and 40 of Item 4.
Two new fulfillments, Fulfillment 2 and Fulfillment 3, are created to hold those items as requested. Everything else remains in Fulfillment 1, which was 10 of Item 1, and the leftover 10 of Item 3.
Handling monetary amounts when splitting fulfillments is tricky. Unit amounts, like merchandise unit prices, don’t need to change, but calculated monetary values such as fulfillment charges, totals, and taxes need to be distributed appropriately across the new fulfillments.
At a high level, the process for this splitting is:
Split the items into separate lists based on requested item quantities
If one item has been split up, distribute monetary amounts proportionally based on quantity, but do not round
Create the new split fulfillments
Distribute the original fulfillment-level monetary amounts to each fulfillment, but do not round
Most are distributed based on the fulfillments' item merchandise totals
Fulfillment-level fulfillment (shipping) tax is distributed based on the items' fulfillment (shipping) taxable totals
The splitting proportion is dependent on the band field of the Fulfillment Calculator that was used. Out-of-box, this could be the item price or weight.
The grand totals for the fulfillments are calculated from merchandise, fulfillment, and tax totals
Round all the monetary amounts (more details below)
It’s important that all rounding waits until the end. Rounding each value in the middle of splitting could result in the new fulfillments having the incorrect monetary amounts due to the accumulation of up-rounding or down-rounding.
Even when we wait to round until the end, it can still be tricky.
Consider a scenario where $10.00 needs to be split between four buckets in a ratio of 1 : 2 : 3 : 3. When we split the amount based on this proportion, we have approximately these values and the amounts they normally would round to:
$1.111111… → $1.11
$2.222222… → $2.22
$3.333333… → $3.33
$3.333333… → $3.33
However, this only adds up to $9.99, not $10.00. This is a classic case of a Penny Rounding Issue.
This situation is likely to occur during Order Fulfillment splitting, and is resolved in Broadleaf by this method:
Round all the values down
Calculate the difference from the original total
Sort the split values by their sub-penny remainder
Distribute a penny to the value with the highest sub-penny remainder
Repeat
For our example, this resolves the problem by changing one of the $3.33 to $3.34.
The Penny Rounding Issue becomes doubly complex due when we are splitting values that also sum into some other total.
Consider a different scenario where we want to split a simple fulfillment exactly in half. This fulfillment has a merchandise total of $2.00, a shipping charge of $0.95, and a tax of $0.15. Before splitting, the total cost is $3.10. Since we’re splitting it in half, each new half should have a total cost of $1.55.
The merchandise total splits into halves of $1.00 easily with no need for rounding.
Using the method of rounding described above, the shipping charge gets split into $0.48 for the first half and $0.47 for the second half.
Also using that method of rounding, the tax gets split into $0.08 for the first half and $0.07 for the second half.
Our rounded fulfillments look like this:
Fulfillment 1 | Fulfillment 2 | Total | |
---|---|---|---|
Merchandise Total |
$1.00 |
$1.00 |
$2.00 |
Shipping Charge |
$0.48 |
$0.47 |
$0.95 |
Tax |
$0.08 |
$0.07 |
$0.15 |
Total |
$1.56 |
$1.54 |
$3.10 |
Each value does add up to the amount from the original fulfillment, but the total sum within each fulfillment doesn’t match, even though we should have been able to reach $1.55 for each of these.
To solve this, the Broadleaf solution considers not only the penny distribution within a single split amount, but also across multiple amounts in order to add up to the expected total ($1.55 in this case). We must iterate over each of the values in the matrix above while keeping track of the penny difference between the current value-wise total and its expected amount, as well as the penny difference between the current fulfillment-wise total and its expected amount. Pennies are distributed only until each of those totals matches the expected amount.
The default implementation splits all applicable values when a fulfillment is split. In order to change that behavior, for example to always retain a fulfillment charge even when some items are cancelled, the DefaultFulfillmentSplittingService
and the DefaultFulfillmentSplitRoundingService
can be extended or replaced with a custom implementation.