sortingalgorithm:Magento结帐总计错误sorting导致错误的运输税计算

在Magento中有一个function,你可以定义总计算的顺序,通过指定之前和之后总计应该运行总计。

我添加了一个自定义的总数,如果我将以下行添加到config.xml,sorting是错误的。 错误的意思是: tax_shipping 之前有 tax_shipping 。 这导致运输成本的税收被添加两次。

但这违反了条件

 tax_shipping after: shipping 

我的猜测是: 在整套规则中必然存在一些矛盾。 但是我怎么能find它?

这是我添加的唯一规则。 如果没有这个规则, tax_shipping会在shipping后sorting。

 <shippingprotectiontax> <class>n98_shippingprotection/quote_address_total_shippingprotectionTax</class> <after>subtotal,discount,shipping,tax</after> <before>grand_total</before> </shippingprotectiontax> 

下面我粘贴由Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes()的usort调用返回的sorting数组对于那些没有Magento安装,代码是这样的:

 /** * 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\n"; $loginfo .= "after: ".implode(',',$data['after'])."\n"; $loginfo .= "before: ".implode(',',$data['before'])."\n"; $loginfo .= "\n"; } Mage::log($loginfo); ... 

日志输出:

 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: 

编辑:

在Vinai的回答后,我添加了更多的debugging代码

 $fp = fopen('/tmp/dotfile','w'); fwrite($fp,"digraph TotalOrder\n"); fwrite($fp,"{\n"); foreach($configArray as $code=>$data) { $_code = $data['_code']; foreach($data['before'] as $beforeCode) { fwrite($fp,"$beforeCode -> $_code;\n"); } foreach($data['after'] as $afterCode) { fwrite($fp,"$_code -> $afterCode;\n"); } } fwrite($fp,"}\n"); fclose($fp); 

并用graphviz将其可视化: dot -Tpng dotfile > viz.png 。 这是第一次尝试的结果。 在分类之后调用。

可视化

EDIT2:

我觉得这很无用。

所以我在合并之前/之前的条目之前对数组进行了可视化。 (在$configArray = $this->_modelsConfig;

这是没有我的shippingprotectiontax条目:

在这里输入图像说明

这是我的shippingprotectiontax条目:

在这里输入图像说明

我没有看到任何明显的矛盾。

EDIT3:

在uasort之前configuration数组:

数组
   'nominal'=> 
  数组
     'class'=>'sales / quote_address_total_nominal',
     '之前'=> 
    数组
       0 =>'小计',
       1 =>'grand_total',
     )
     'renderer'=>'checkout / total_nominal',
     '之后'=> 
    数组
     )
     '_code'=>'名义',
   )
   '小计'=> 
  数组
     'class'=>'sales / quote_address_total_subtotal',
     '之后'=> 
    数组
       0 =>'名义',
     )
     '之前'=> 
    数组
       0 =>'grand_total',
       1 =>'运送',
       2 =>'freeshipping',
       3 =>'tax_subtotal',
       4 =>'折扣',
       5 =>“税”,
       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'=>'小计',
   )
   'shipping'=> 
  数组
     'class'=>'sales / quote_address_total_shipping',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'freeshipping',
       2 =>'tax_subtotal',
       3 =>'名义',
     )
     '之前'=> 
    数组
       0 =>'grand_total',
       1 =>'折扣',
       2 =>'tax_shipping',
       3 =>“税”,
       4 =>'cashondelivery',
       5 =>'cashondelivery_tax',
       6 =>'shippingprotection',
       7 =>'shippingprotectiontax',
     )
     'renderer'=>'tax / checkout_shipping',
     'admin_renderer'=>'adminhtml / sales_order_create_totals_shipping',
     '_code'=>'运送',
   )
   'grand_total'=> 
  数组
     'class'=>'sales / quote_address_total_grand',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'名义',
       2 =>'运送',
       3 =>'freeshipping',
       4 =>'tax_subtotal',
       5 =>'折扣',
       6 =>“税”,
       7 =>'tax_giftwrapping',
       8 =>'cashondelivery',
       9 =>'cashondelivery_tax',
       10 =>'shippingprotection',
       11 =>'shippingprotectiontax',
     )
     'renderer'=>'tax / checkout_grandtotal',
     'admin_renderer'=>'adminhtml / sales_order_create_totals_grandtotal',
     '之前'=> 
    数组
       0 =>'customerbalance',
       1 =>'giftcardaccount',
       2 =>'奖励',
     )
     '_code'=>'grand_total',
   )
   'freeshipping'=> 
  数组
     'class'=>'salesrule / quote_freeshipping',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'名义',
     )
     '之前'=> 
    数组
       0 =>'tax_subtotal',
       1 =>'运送',
       2 =>'grand_total',
       3 =>“税”,
       4 =>'折扣',
     )
     '_code'=>'freeshipping',
   )
   'discount'=> 
  数组
     'class'=>'salesrule / quote_discount',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'运送',
       2 =>'名义',
       3 =>'freeshipping',
       4 =>'tax_subtotal',
       5 =>'tax_shipping',
       6 =>'weee',
     )
     '之前'=> 
    数组
       0 =>'grand_total',
       1 =>“税”,
       2 =>'customerbalance',
       3 =>'giftcardaccount',
       4 =>'奖励',
       5 =>'cashondelivery',
       6 =>'cashondelivery_tax',
       7 =>'shippingprotection',
       8 =>'shippingprotectiontax',
     )
     'renderer'=>'tax / checkout_discount',
     'admin_renderer'=>'adminhtml / sales_order_create_totals_discount',
     '_code'=>'折扣',
   )
   'tax_subtotal'=> 
  数组
     'class'=>'tax / sales_total_quote_subtotal',
     '之后'=> 
    数组
       0 =>'freeshipping',
       1 =>'小计',
       2 =>'小计',
       3 =>'名义',
     )
     '之前'=> 
    数组
       0 =>“税”,
       1 =>'折扣',
       2 =>'运送',
       3 =>'grand_total',
       4 =>'weee',
       5 =>'customerbalance',
       6 =>'giftcardaccount',
       7 =>'奖励',
     )
     '_code'=>'tax_subtotal',
   )
   'tax_shipping'=> 
  数组
     'class'=>'tax / sales_total_quote_shipping',
     '之后'=> 
    数组
       0 =>'运送',
       1 =>'小计',
       2 =>'freeshipping',
       3 =>'tax_subtotal',
       4 =>'名义',
     )
     '之前'=> 
    数组
       0 =>“税”,
       1 =>'折扣',
       2 =>'grand_total',
       3 =>'grand_total',
     )
     '_code'=>'tax_shipping',
   )
   'tax'=> 
  数组
     'class'=>'tax / sales_total_quote_tax',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'运送',
       2 =>'折扣',
       3 =>'tax_subtotal',
       4 =>'freeshipping',
       5 =>'tax_shipping',
       6 =>'名义',
       7 =>'weee',
       8 =>'cashondelivery',
       9 =>'shippingprotection',
     )
     '之前'=> 
    数组
       0 =>'grand_total',
       1 =>'customerbalance',
       2 =>'giftcardaccount',
       3 =>'tax_giftwrapping',
       4 =>'奖励',
       5 =>'cashondelivery_tax',
       6 =>'shippingprotectiontax',
     )
     'renderer'=>'tax / checkout_tax',
     'admin_renderer'=>'adminhtml / sales_order_create_totals_tax',
     '_code'=>'税',
   )
   'weee'=> 
  数组
     'class'=>'weee / total_quote_weee',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'tax_subtotal',
       2 =>'名义',
       3 =>'freeshipping',
       4 =>'小计',
       5 =>'小计',
       6 =>'名义',
     )
     '之前'=> 
    数组
       0 =>“税”,
       1 =>'折扣',
       2 =>'grand_total',
       3 =>'grand_total',
       4 =>“税”,
     )
     '_code'=>'weee',
   )
   'customerbalance'=> 
  数组
     'class'=>'enterprise_customerbalance / total_quote_customerbalance',
     '之后'=> 
    数组
       0 =>'小',
       1 =>'折扣',
       2 =>“税”,
       3 =>'tax_subtotal',
       4 =>'grand_total',
       5 =>'奖励',
       6 =>'giftcardaccount',
       7 =>'小计',
       8 =>'运送',
       9 =>'名义',
       10 =>'freeshipping',
       11 =>'tax_shipping',
       12 =>'weee',
     )
     'renderer'=>'enterprise_customerbalance / checkout_total',
     '之前'=> 
    数组
     )
     '_code'=>'customerbalance',
   )
   'giftcardaccount'=> 
  数组
     'class'=>'enterprise_giftcardaccount / total_quote_giftcardaccount',
     '之后'=> 
    数组
       0 =>'小',
       1 =>'折扣',
       2 =>“税”,
       3 =>'tax_subtotal',
       4 =>'grand_total',
       5 =>'奖励',
       6 =>'小计',
       7 =>'运送',
       8 =>'名义',
       9 =>'freeshipping',
       11 =>'tax_shipping',
       12 =>'weee',
     )
     '之前'=> 
    数组
       0 =>'customerbalance',
     )
     'renderer'=>'enterprise_giftcardaccount / checkout_cart_total',
     '_code'=>'giftcardaccount',
   )
   'giftwrapping'=> 
  数组
     'class'=>'enterprise_giftwrapping / total_quote_giftwrapping',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'名义',
     )
     'renderer'=>'enterprise_giftwrapping / checkout_totals',
     '之前'=> 
    数组
     )
     '_code'=>'giftwrapping',
   )
   'tax_giftwrapping'=> 
  数组
     'class'=>'enterprise_giftwrapping / total_quote_tax_giftwrapping',
     '之后'=> 
    数组
       0 =>“税”,
       1 =>'小计',
       2 =>'运送',
       3 =>'折扣',
       4 =>'tax_subtotal',
       5 =>'freeshipping',
       6 =>'tax_shipping',
       7 =>'名义',
       8 =>'weee',
     )
     '之前'=> 
    数组
       0 =>'grand_total',
       1 =>'customerbalance',
       2 =>'giftcardaccount',
     )
     '_code'=>'tax_giftwrapping',
   )
   '奖励'=> 
  数组
     'class'=>'enterprise_reward / total_quote_reward',
     '之后'=> 
    数组
       0 =>'小',
       1 =>'折扣',
       2 =>“税”,
       3 =>'tax_subtotal',
       4 =>'grand_total',
       5 =>'小计',
       6 =>'运送',
       7 =>'名义',
       8 =>'freeshipping',
       9 =>'tax_subtotal',
       10 =>'tax_shipping',
       11 =>'weee',
       12 =>'小计',
       13 =>'运送',
       14 =>'折扣',
       15 =>'tax_subtotal',
       16 =>'freeshipping',
       17 =>'tax_shipping',
       18 =>'名义',
       19 =>'weee',
       20 =>'freeshipping',
       21 =>'小计',
       22 =>'小计',
       23 =>'名义',
       24 =>'小计',
       25 =>'名义',
       26 =>'运送',
       27 =>'freeshipping',
       28 =>'tax_subtotal',
       29 =>'折扣',
       30 =>'税收',
       31 =>'tax_giftwrapping',
     )
     '之前'=> 
    数组
       0 =>'giftcardaccount',
       1 =>'customerbalance',
       2 =>'customerbalance',
     )
     'renderer'=>'enterprise_reward / checkout_total',
     '_code'=>'奖励',
   )
   'cashondelivery'=> 
  数组
     'class'=>'cashondelivery / quote_total',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'折扣',
       2 =>'运送',
       3 =>'名义',
       4 =>'小计',
       5 =>'运送',
       6 =>'名义',
       7 =>'freeshipping',
       8 =>'tax_subtotal',
       9 =>'tax_shipping',
       10 =>'weee',
       11 =>'小计',
       12 =>'freeshipping',
       13 =>'tax_subtotal',
       14 =>'名义',
     )
     '之前'=> 
    数组
       0 =>“税”,
       1 =>'grand_total',
       2 =>'grand_total',
       3 =>'customerbalance',
       4 =>'giftcardaccount',
       5 =>'tax_giftwrapping',
       6 =>'奖励',
       7 =>'customerbalance',
       8 =>'giftcardaccount',
       9 =>'奖励',
     )
     'renderer'=>'cashondelivery / checkout_cod',
     'admin_renderer'=>'cashondelivery / adminhtml_sales_order_create_totals_cod',
     '_code'=>'cashondelivery',
   )
   'cashondelivery_tax'=> 
  数组
     'class'=>'cashondelivery / quote_taxTotal',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'折扣',
       2 =>'运送',
       3 =>“税”,
       4 =>'名义',
       5 =>'小计',
       6 =>'运送',
       7 =>'名义',
       8 =>'freeshipping',
       9 =>'tax_subtotal',
       10 =>'tax_shipping',
       11 =>'weee',
       12 =>'小计',
       13 =>'freeshipping',
       14 =>'tax_subtotal',
       15 =>'名义',
       16 =>'小计',
       17 =>'运送',
       18 =>'折扣',
       19 =>'tax_subtotal',
       20 =>'freeshipping',
       21 =>'tax_shipping',
       22 =>'名义',
       23 =>'weee',
       24 =>'cashondelivery',
     )
     '之前'=> 
    数组
       0 =>'grand_total',
       1 =>'customerbalance',
       2 =>'giftcardaccount',
       3 =>'奖励',
     )
     '_code'=>'cashondelivery_tax',
   )
   'shippingprotection'=> 
  数组
     'class'=>'n98_shippingprotection / quote_address_total_shippingprotection',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'折扣',
       2 =>'运送',
       3 =>'名义',
       4 =>'小计',
       5 =>'运送',
       6 =>'名义',
       7 =>'freeshipping',
       8 =>'tax_subtotal',
       9 =>'tax_shipping',
       10 =>'weee',
       11 =>'小计',
       12 =>'freeshipping',
       13 =>'tax_subtotal',
       14 =>'名义',
     )
     '之前'=> 
    数组
       0 =>“税”,
       1 =>'grand_total',
       2 =>'grand_total',
       3 =>'customerbalance',
       4 =>'giftcardaccount',
       5 =>'tax_giftwrapping',
       6 =>'奖励',
       7 =>'cashondelivery_tax',
       8 =>'customerbalance',
       9 =>'giftcardaccount',
       10 =>'奖励',
     )
     '_code'=>'shippingprotection',
   )
   'shippingprotectiontax'=> 
  数组
     'class'=>'n98_shippingprotection / quote_address_total_shippingprotectionTax',
     '之后'=> 
    数组
       0 =>'小计',
       1 =>'折扣',
       2 =>'运送',
       3 =>“税”,
       4 =>'名义',
       5 =>'小计',
       6 =>'运送',
       7 =>'名义',
       8 =>'freeshipping',
       9 =>'tax_subtotal',
       10 =>'tax_shipping',
       11 =>'weee',
       12 =>'小计',
       13 =>'freeshipping',
       14 =>'tax_subtotal',
       15 =>'名义',
       16 =>'小计',
       17 =>'运送',
       18 =>'折扣',
       19 =>'tax_subtotal',
       20 =>'freeshipping',
       21 =>'tax_shipping',
       22 =>'名义',
       23 =>'weee',
       24 =>'cashondelivery',
       25 =>'shippingprotection',
     )
     '之前'=> 
    数组
       0 =>'grand_total',
       1 =>'customerbalance',
       2 =>'giftcardaccount',
       3 =>'奖励',
     )
     '_code'=>'shippingprotectiontax',
   )
 )

更新: Magento Bug票: https //jira.magento.com/browse/MCACE-129

感谢持久@亚历克斯,这是一个更好的解释更好的答案:)我的第一个答案是错的。

PHP实现了所有数组sorting函数的快速sorting(参考zend_qsort.c )。
如果数组中的两条logging相同,则它们的位置将被交换。

问题是giftwrap总logging,根据_compareTotals() ,它是大于小计名义,等于所有其他总数

取决于$confArrayinput数组的原始顺序和pivot元素的位置,交换giftwrap是合法的,例如折扣 ,因为两者都是相等的,即使折扣大于出货

这可能会使sortingalgorithm的问题更清晰:

  • 运输<tax_shipping
  • giftwrapping ==运送
  • giftwrapping = tax_shipping

有几种可能的解决scheme,即使最初的问题是select快速sorting来构build有向非循环依赖图

  • 一个(坏的,短期的)解决scheme是增加更多的依赖到giftwrapping总,即使可能仍然有更多的问题,其他总计,目前根本没有表面。
  • 真正的解决scheme是为这个问题实现一个拓扑sortingalgorithm。

有趣的是,并没有太多的PHP软件包。 有一个孤儿PEAR包Structures_Graph 。 使用这可能是快速的解决scheme,但这意味着将$confArray转换为Structures_Graph结构(所以可能不那么快)。

维基百科在解释这个问题上做得很好,所以推出自己的解决scheme可能是一个有趣的挑战。 德国维基百科的拓扑sorting页面将问题分解为逻辑步骤,并且在PERL中也有一个很好的示例algorithm。

所以最后,这是我的补丁这个问题。

它实现了Vinaibuild议的拓扑sorting。

  1. app/code/core/Mage/Sales/Model/Config/Ordered.phpapp/code/local/Mage/Sales/Model/Config/Ordered.php
  2. 将修补程序的内容保存到文件total-sorting.patch并调用patch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php

如果升级,请确保重新应用这些步骤。

该补丁已经过testing,可以与Magento 1.7.0.2一起使用

 --- app / code / core / Mage / Sales / Model / Config / Ordered.php 2012-08-14 14:19:50.306504947 +0200
 +++ app / code / local / Mage / Sales / Model / Config / Ordered.php 2012-08-15 10:00:47.027003404 +0200
 @@ -121,6 +121,78 @@
         返回$ totalConfig;
      }

 + // [补丁码开始]
 +
 + / **
 + *拓扑sorting
 + *
 + *版权:http://www.calcatraz.com/blog/php-topological-sort-function-384
 + *并修复看到http://stackoverflow.com/questions/11953021/topological-sorting-in-php评论
 + *
 + * @param $ nodeids节点ID
 + * @参数$边缘数组的边缘。 每条边被指定为一个包含两个元素的数组:边的源节点和目标节点
 + * @return array | null
 + * /
 +函数topological_sort($ nodeids,$ edges){
 + $ L = $ S = $ nodes = array();
 + foreach($ nodeids as $ id){
 + $ nodes [$ id] = array('in'=> array(),'out'=> array());
 + foreach($边为$ e){
 + if($ id == $ e [0]){$ nodes [$ id] ['out'] [] = $ e [1];  }
 + if($ id == $ e [1]){$ nodes [$ id] ['in'] [] = $ e [0];  }
 +}
 +}
 + foreach($ nodes as $ id => $ n){if(empty($ n ['in']))$ S [] = $ id;  }
 + while($ id = array_shift($ S)){
 + if(!in_array($ id,$ L)){
 + $ L [] = $ id;
 + foreach($ nodes [$ id] ['out']为$ m){
 + $ nodes [$ m] ['in'] = array_diff($ nodes [$ m] ['in'],array($ id));
 + if(empty($ nodes [$ m] ['in'])){$ S [] = $ m;  }
 +}
 + $ nodes [$ id] ['out'] = array();
 +}
 +}
 + foreach($节点为$ n){
 + if(!empty($ n ['in'])or!empty($ n ['out'])){
 +返回null;  //不能sorting,因为graphics是循环的
 +}
 +}
 +返回$ L;
 +}
 +
 + / **
 + *排列configuration数组
 + *
 + *公众可以通过testing轻松访问
 + *
 + * @param $ configArray
 + * @返回数组
 + * /
 +公共函数_topSortConfigArray($ configArray)
 + {
 + $ nodes = array_keys($ configArray);
 + $ edges = array();
 +
 + foreach($ configArray as $ code => $ data){
 + $ _code = $ data ['_ code'];
 + if(!isset($ configArray [$ _ code]))continue;
 + foreach($ data ['before'] as $ beforeCode){
 + if(!isset($ configArray [$ beforeCode]))continue;
 + $ edges [] = array($ _ code,$ beforeCode);
 +}
 +
 + foreach($ data ['after'] as $ afterCode){
 + if(!isset($ configArray [$ afterCode]))continue;
 + $ edges [] = array($ afterCode,$ _code);
 +}
 +}
 + return $ this-> topological_sort($ nodes,$ edges);
 +}
 +
 + // [补丁码结束]
 +
 +
      / **
       *根据这些数据汇总所有项目的信息前后的信息
       *
 @@ -138,38 +210,16 @@
          //如果第一个元素包含“sort_order”键,则调用简单的sorting
         重置($ configArray);
          $ element = current($ configArray);
 + // [补丁码开始]
          if(isset($ element ['sort_order'])){
              uasort($ configArray,array($ this,'_compareSortOrder'));
 + $ sortedCollectors = array_keys($ configArray);
 +
          } else {
 -  foreach($ configArray as $ code => $ data){
 -  foreach($ data ['before'] as $ beforeCode){
 -  if(!isset($ configArray [$ beforeCode])){
 - 继续;
 - }
 -  $ configArray [$ code] ['before'] = array_unique(array_merge(
 -  $ configArray [$ code] ['before'],$ configArray [$ beforeCode] ['before']
 - ));
 -  $ configArray [$ beforeCode] [''after'] = array_merge(
 -  $ configArray [$ beforeCode] ['''之后'],数组($ code),$ data ['after']
 - );
 -  $ configArray [$ beforeCode] ['after'] = array_unique($ configArray [$ beforeCode] ['after']);
 - }
 -  foreach($ data ['after'] as $ afterCode){
 -  if(!isset($ configArray [$ afterCode])){
 - 继续;
 - }
 -  $ configArray [$ code] ['after'] = array_unique(array_merge(
 -  $ configArray [$ code] ['after'],$ configArray [$ afterCode] ['after']
 - ));
 -  $ configArray [$ afterCode] ['before'] = array_merge(
 -  $ configArray [$ afterCode] ['before'],数组($ code),$ data ['before']
 - );
 -  $ configArray [$ afterCode] ['before'] = array_unique($ configArray [$ afterCode] ['before']);
 - }
 - }
 -  uasort($ configArray,array($ this,'_compareTotals'));
 + $ sortedCollectors = $ this  - > _ topSortConfigArray($ configArray);
          }
 -  $ sortedCollectors = array_keys($ configArray);
 + // [补丁码结束]
 +
          if(Mage :: app() - > useCache('config')){
              Mage :: app() - > saveCache(serialize($ sortedCollectors),$ this  - > _ collectorsCacheKey,array(
                      Mage_Core_Model_Config :: CACHE_TAG
 @@ -196,27 +246,6 @@
      }

      / **
 -  *之前/之后使用的callback进行比较
 -  *
 -  * @参数数组$ a
 -  * @参数数组$ b
 -  * @return int
 -  * /
 - 保护function_compareTotals($ a,$ b)
 -  {
 -  $ aCode = $ a ['_ code'];
 -  $ bCode = $ b ['_ code'];
 -  if(in_array($ aCode,$ b [''))in_array($ bCode,$ a ['before'])){
 -  $ res = -1;
 - } elseif(in_array($ bCode,$ a ['after'])|| in_array($ aCode,$ b ['before'])){
 -  $ res = 1;
 - } else {
 -  $ res = 0;
 - }
 - 返回$ res;
 - }
 - 
 -  / **
       *使用sort_order进行比较的callback
       *
       * @参数数组$ a

编辑 :还有另一个build议的变化(对于Magento 2): https : //github.com/magento/magento2/pull/49

编辑: 这个答案是错误的 。 请参阅评论中的讨论。


正如Vinai指出的那样,问题在于即使参数不相等,顺序函数也会返回0。 我修改了函数以回退键的string顺序,如下所示:

 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 = strcmp($aCode, $bCode); // was $res = 0 before } return $res; } 

好多年来一直困在这里!!!

现在我知道为什么一些过去的项目是很难调整关于凌晨和税收组合一个噩梦我可以说,永远不明白为什么,昨天我find了为什么,后来我发现这篇文章,真正的耻辱..但大多数我需要知道的回应是能够search的问题的时间..

至less对于linux的头毫不畏惧的obvius解决scheme是下面的代码,基本上我使用古老的linux命令tsort,特别是在这里我们需要的方式topococical顺序..

对于我们之间的昆虫学和考古学家的灵魂,我使用了一些真正的80年代技术… yummmm

  /** * Aggregate before/after information from all items and sort totals based on this data * * @return array */ protected function _getSortedCollectorCodes() { if (Mage::app()->useCache('config')) { $cachedData = Mage::app()->loadCache($this->_collectorsCacheKey); if ($cachedData) { return unserialize($cachedData); } } $configArray = $this->_modelsConfig; // invoke simple sorting if the first element contains the "sort_order" key reset($configArray); $element = current($configArray); if (isset($element['sort_order'])) { uasort($configArray, array($this, '_compareSortOrder')); $sortedCollectors = array_keys($configArray); } else { foreach ($configArray as $code => $data) { foreach ($data['before'] as $beforeCode) { if (!isset($configArray[$beforeCode])) { continue; } $configArray[$code]['before'] = array_merge( $configArray[$code]['before'], $configArray[$beforeCode]['before']); $configArray[$code]['before'] = array_unique( $configArray[$code]['before']); $configArray[$beforeCode]['after'] = array_merge( $configArray[$beforeCode]['after'], array($code), $data['after']); $configArray[$beforeCode]['after'] = array_unique( $configArray[$beforeCode]['after']); } foreach ($data['after'] as $afterCode) { if (!isset($configArray[$afterCode])) { continue; } $configArray[$code]['after'] = array_merge( $configArray[$code]['after'], $configArray[$afterCode]['after']); $configArray[$code]['after'] = array_unique( $configArray[$code]['after']); $configArray[$afterCode]['before'] = array_merge( $configArray[$afterCode]['before'], array($code), $data['before']); $configArray[$afterCode]['before'] = array_unique( $configArray[$afterCode]['before']); } } //uasort($configArray, array($this, '_compareTotals')); $res = ""; foreach ($configArray as $code => $data) { foreach ($data['before'] as $beforeCode) { if (!isset($configArray[$beforeCode])) { continue; } $res = $res . "$code $beforeCode\n"; } foreach ($data['after'] as $afterCode) { if (!isset($configArray[$afterCode])) { continue; } $res = $res . "$afterCode $code\n"; } } file_put_contents(Mage::getBaseDir('tmp')."/graph.txt", $res); $sortedCollectors=explode("\n",shell_exec('tsort '.Mage::getBaseDir('tmp')."/graph.txt"),-1); } if (Mage::app()->useCache('config')) { Mage::app() ->saveCache(serialize($sortedCollectors), $this->_collectorsCacheKey, array(Mage_Core_Model_Config::CACHE_TAG)); } return $sortedCollectors; } 

为了完整起见,我已经发布了完整的function,当然至less对于我来说是一种魅力。

我决定去计划B,重载getSortedCollectors …它的直接,并给我绝对控制,如果当然,如果我会介绍新的模块,我将不得不检查,如果我需要在这里添加

 <?php class YourModule_Sales_Model_Total_Quote_Collector extends Mage_Sales_Model_Quote_Address_Total_Collector { protected function _getSortedCollectorCodes() { return array( 'nominal', 'subtotal', 'msrp', 'freeshipping', 'tax_subtotal', 'weee', 'shipping', 'tax_shipping', 'floorfee', 'bottlediscount', 'discount', 'tax', 'grand_total', ); } } 

上面的讨论清楚地表明了这个问题。 通常的sorting不适用于任何两个元素之间没有sortingfunction的数据集。 如果只有一些关系被定义为“部分依赖”,则必须使用拓扑sorting来服从声明的“之前”和“之后”语句。 在我的testing中,这些声明在结果集中被打破,并且我添加了额外的模块。 恐慌部分,不仅影响附加模块,而且可能以不可预知的方式改变整个sorting结果。 所以,我实现了解决问题的标准拓扑sorting:

 /** * The source data of the nodes and their dependencies, they are not required to be symmetrical node cold list other * node in 'after' but not present in its 'before' list: * @param $configArray * $configArray = [ * <nodeCode> => ["_code"=> <nodeCode>, "after"=> [<dependsOnNodeCode>, ...], "before"=> [<dependedByCode>, ...] ], * ... * ] * The procedure updates adjacency list , to have every edge to be listed in the both nodes (in one 'after' and other 'before') */ function normalizeDependencies(&$configArray) { //For each node in the source data foreach ($configArray as $code => $data) { //Look up all nodes listed 'before' and update their 'after' for consistency foreach ($data['before'] as $beforeCode) { if (!isset($configArray[$beforeCode])) { continue; } $configArray[$beforeCode]['after'] = array_unique(array_merge( $configArray[$beforeCode]['after'], array($code) )); } //Look up all nodes listed 'after' and update their 'before' for consistency foreach ($data['after'] as $afterCode) { if (!isset($configArray[$afterCode])) { continue; } $configArray[$afterCode]['before'] = array_unique(array_merge( $configArray[$afterCode]['before'], array($code) )); } } } /** * http://en.wikipedia.org/wiki/Topological_sorting * Implements Kahn (1962) algorithms */ function topoSort(&$array) { normalizeDependencies($array); $result = array(); // Empty list that will contain the sorted elements $front = array(); // Set of all nodeCodes with no incoming edges //Push all items with no predecessors in S; foreach ($array as $code => $data) { if ( empty ($data['after']) ) { $front[] = $code; } } // While 'front' is not empty while (! empty ($front)) { //Deque 'frontier' from 'front' $frontierCode = array_shift($front); //Push it in 'result' $result[$frontierCode]= $array[$frontierCode]; // Remove all outgoing edges from 'frontier'; while (! empty ($array[$frontierCode]['before'])) { $afterCode = array_shift($array[$frontierCode]['before']); // remove corresponding edge e from the graph $array[$afterCode]['after'] = array_remove($array[$afterCode]['after'], $frontierCode); //* if, no more decencies put node into processing queue: // if m has no other incoming edges then if ( empty ($array[$afterCode]['after']) ) { // insert m into 'front' array_push($front, $afterCode); } } } if(count($result) != count($array)){ saveGraph($array, 'mage-dependencies.dot'); throw new Exception("Acyclic dependencies in data, see graphviz diagram: mage-dependencies.dot for details."); } return $result; } /** * Could not find better way to remove value from array * * @param $array * @param $value * @return array */ protected function array_remove($array, $value){ $cp = array(); foreach($array as $b) { if($b != $value){ $cp[]=$b; } } return $cp; } /** * Saves graph in the graphviz format for visualisation: * >dot -Tpng /tmp/dotfile.dot > viz-graph.png */ function saveGraph($configArray, $fileName){ $fp = fopen($fileName,'w'); fwrite($fp,"digraph TotalOrder\n"); fwrite($fp,"{\n"); foreach($configArray as $code=>$data) { fwrite($fp,"$code;\n"); foreach($data['before'] as $beforeCode) { fwrite($fp,"$beforeCode -> $code;\n"); } foreach($data['after'] as $afterCode) { fwrite($fp,"$code -> $afterCode;\n"); } } fwrite($fp,"}\n"); fclose($fp); } 

The question, how hard would be to get it (or other topo sort) into Magento release/hot fix?