In Magento there is a functionality where you can define the order of total calculation by specifing before and after which totals a total should be run.
I added a custom total and if I add the following lines to the config.xml, the sorting is wrong. Wrong means: tax_shipping
comes before shipping
.
This causes the tax for the shipping cost to be added twice.
But this violates the condition
tax_shipping
after: shipping
My guess: There must be some contradiction in the full set of rules. But how can I find it?
This is the only rule I add. Without this rule, tax_shipping
is sorted after shipping
.
<shippingprotectiontax>
<class>n98_shippingprotection/quote_address_total_shippingprotectionTax</class>
<after>subtotal,discount,shipping,tax</after>
<before>grand_total</before>
</shippingprotectiontax>
Below I paste the sorted array that is returned by the usort call in Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes()
For those who do not have a Magento installation, the code is like this:
/**
* uasort callback function
*
* @param array $a
* @param array $b
* @return int
*/
protected function _compareTotals($a, $b)
{
$aCode = $a['_code'];
$bCode = $b['_code'];
if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {
$res = -1;
} elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {
$res = 1;
} else {
$res = 0;
}
return $res;
}
protected function _getSortedCollectorCodes()
{
...
uasort($configArray, array($this, '_compareTotals'));
Mage::log('Sorted:');
// this produces the output below
$loginfo = "";
foreach($configArray as $code=>$data) {
$loginfo .= "$code
";
$loginfo .= "after: ".implode(',',$data['after'])."
";
$loginfo .= "before: ".implode(',',$data['before'])."
";
$loginfo .= "
";
}
Mage::log($loginfo);
...
Log output:
nominal
after:
before: subtotal,grand_total
subtotal
after: nominal
before: grand_total,shipping,freeshipping,tax_subtotal,discount,tax,weee,giftwrapping,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
freeshipping
after: subtotal,nominal
before: tax_subtotal,shipping,grand_total,tax,discount
tax_shipping
after: shipping,subtotal,freeshipping,tax_subtotal,nominal
before: tax,discount,grand_total,grand_total
giftwrapping
after: subtotal,nominal
before:
tax_subtotal
after: freeshipping,subtotal,subtotal,nominal
before: tax,discount,shipping,grand_total,weee,customerbalance,giftcardaccount,reward
weee
after: subtotal,tax_subtotal,nominal,freeshipping,subtotal,subtotal,nominal
before: tax,discount,grand_total,grand_total,tax
shipping
after: subtotal,freeshipping,tax_subtotal,nominal
before: grand_total,discount,tax_shipping,tax,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
discount
after: subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee
before: grand_total,tax,customerbalance,giftcardaccount,reward,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
cashondelivery
after: subtotal,discount,shipping,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal
before: tax,grand_total,grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,customerbalance,giftcardaccount,reward
shippingprotection
after: subtotal,discount,shipping,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal
before: tax,grand_total,grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,cashondelivery_tax,customerbalance,giftcardaccount,reward
tax
after: subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery,shippingprotection
before: grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,cashondelivery_tax,shippingprotectiontax
shippingprotectiontax
after: subtotal,discount,shipping,tax,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery,shippingprotection
before: grand_total,customerbalance,giftcardaccount,reward
cashondelivery_tax
after: subtotal,discount,shipping,tax,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery
before: grand_total,customerbalance,giftcardaccount,reward
tax_giftwrapping
after: tax,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee
before: grand_total,customerbalance,giftcardaccount
grand_total
after: subtotal,nominal,shipping,freeshipping,tax_subtotal,discount,tax,tax_giftwrapping,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
before: customerbalance,giftcardaccount,reward
reward
after: wee,discount,tax,tax_subtotal,grand_total,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,freeshipping,subtotal,subtotal,nominal,subtotal,nominal,shipping,freeshipping,tax_subtotal,discount,tax,tax_giftwrapping
before: giftcardaccount,customerbalance,customerbalance
giftcardaccount
after: wee,discount,tax,tax_subtotal,grand_total,reward,subtotal,shipping,nominal,freeshipping,tax_shipping,weee
before: customerbalance
customerbalance
after: wee,discount,tax,tax_subtotal,grand_total,reward,giftcardaccount,subtotal,shipping,nominal,freeshipping,tax_shipping,weee
before:
EDIT:
After Vinai's answer I added more debug code
$fp = fopen('/tmp/dotfile','w');
fwrite($fp,"digraph TotalOrder
");
fwrite($fp,"{
");
foreach($configArray as $code=>$data) {
$_code = $data['_code'];
foreach($data['before'] as $beforeCode) {
fwrite($fp,"$beforeCode -> $_code;
");
}
foreach($data['after'] as $afterCode) {
fwrite($fp,"$_code -> $afterCode;
");
}
}
fwrite($fp,"}
");
fclose($fp);
And visualized it with graphviz: dot -Tpng dotfile > viz.png
. That's the result of the first try. Called after the sorting.
EDIT2:
I think this is pretty useless.
So I made a visualization of the array before merging the after/before entries. (right after $configArray = $this->_modelsConfig;
)
This is it without my shippingprotectiontax
entry:
This is it with my shippingprotectiontax
entry:
I do not see any clear contradictions.
EDIT3:
Config array just before uasort:
array (
'nominal' =>
array (
'class' => 'sales/quote_address_total_nominal',
'before' =>
array (
0 => 'subtotal',
1 => 'grand_total',
),
'renderer' => 'checkout/total_nominal',
'after' =>
array (
),
'_code' => 'nominal',
),
'subtotal' =>
array (
'class' => 'sales/quote_address_total_subtotal',
'after' =>
array (
0 => 'nominal',
),
'before' =>
array (
0 => 'grand_total',
1 => 'shipping',
2 => 'freeshipping',
3 => 'tax_subtotal',
4 => 'discount',
5 => 'tax',
6 => 'weee',
7 => 'giftwrapping',
8 => 'cashondelivery',
9 => 'cashondelivery_tax',
10 => 'shippingprotection',
11 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_subtotal',
'admin_renderer' => 'adminhtml/sales_order_create_totals_subtotal',
'_code' => 'subtotal',
),
'shipping' =>
array (
'class' => 'sales/quote_address_total_shipping',
'after' =>
array (
0 => 'subtotal',
1 => 'freeshipping',
2 => 'tax_subtotal',
3 => 'nominal',
),
'before' =>
array (
0 => 'grand_total',
1 => 'discount',
2 => 'tax_shipping',
3 => 'tax',
4 => 'cashondelivery',
5 => 'cashondelivery_tax',
6 => 'shippingprotection',
7 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_shipping',
'admin_renderer' => 'adminhtml/sales_order_create_totals_shipping',
'_code' => 'shipping',
),
'grand_total' =>
array (
'class' => 'sales/quote_address_total_grand',
'after' =>
array (
0 => 'subtotal',
1 => 'nominal',
2 => 'shipping',
3 => 'freeshipping',
4 => 'tax_subtotal',
5 => 'discount',
6 => 'tax',
7 => 'tax_giftwrapping',
8 => 'cashondelivery',
9 => 'cashondelivery_tax',
10 => 'shippingprotection',
11 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_grandtotal',
'admin_renderer' => 'adminhtml/sales_order_create_totals_grandtotal',
'before' =>
array (
0 => 'customerbalance',
1 => 'giftcardaccount',
2 => 'reward',
),
'_code' => 'grand_total',
),
'freeshipping' =>
array (
'class' => 'salesrule/quote_freeshipping',
'after' =>
array (
0 => 'subtotal',
1 => 'nominal',
),
'before' =>
array (
0 => 'tax_subtotal',
1 => 'shipping',
2 => 'grand_total',
3 => 'tax',
4 => 'discount',
),
'_code' => 'freeshipping',
),
'discount' =>
array (
'class' => 'salesrule/quote_discount',
'after' =>
array (
0 => 'subtotal',
1 => 'shipping',
2 => 'nominal',
3 => 'freeshipping',
4 => 'tax_subtotal',
5 => 'tax_shipping',
6 => 'weee',
),
'before' =>
array (
0 => 'grand_total',
1 => 'tax',
2 => 'customerbalance',
3 => 'giftcardaccount',
4 => 'reward',
5 => 'cashondelivery',
6 => 'cashondelivery_tax',
7 => 'shippingprotection',
8 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_discount',
'admin_renderer' => 'adminhtml/sales_order_create_totals_discount',
'_code' => 'discount',
),
'tax_subtotal' =>
array (
'class' => 'tax/sales_total_quote_subtotal',
'after' =>
array (
0 => 'freeshipping',
1 => 'subtotal',
2 => 'subtotal',
3 => 'nominal',
),
'before' =>
array (
0 => 'tax',
1 => 'discount',
2 => 'shipping',
3 => 'grand_total',
4 => 'weee',
5 => 'customerbalance',
6 => 'giftcardaccount',
7 => 'reward',
),
'_code' => 'tax_subtotal',
),
'tax_shipping' =>
array (
'class' => 'tax/sales_total_quote_shipping',
'after' =>
array (
0 => 'shipping',
1 => 'subtotal',
2 => 'freeshipping',
3 => 'tax_subtotal',
4 => 'nominal',
),
'before' =>
array (
0 => 'tax',
1 => 'discount',
2 => 'grand_total',
3 => 'grand_total',
),
'_code' => 'tax_shipping',