2012-02-08 9 views
34

W Magento istnieje funkcja, w której można zdefiniować kolejność obliczeń sumarycznych, określając przed i po której sumy powinny być sumowane.Algorytm sortowania: sumy kontrolne Magento posortowane błędnie powodujące błędne obliczenie podatku dostawy

Dodałem niestandardową sumę i jeśli dodaję poniższe wiersze do pliku config.xml, sortowanie jest nieprawidłowe. Nieprawidłowe środki: tax_shipping pochodzi przedshipping. Powoduje to dwukrotne dodanie podatku za koszt wysyłki.

Ale to narusza warunku

tax_shipping 
after: shipping 

moje przypuszczenie: Tam musi być jakaś sprzeczność w pełnym zestawem reguł. Ale jak mogę to znaleźć?

Jest to jedyna zasada, którą dodaję. Bez tej reguły tax_shipping jest sortowane po shipping.

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

Poniżej wklej posortowaną tablicę, który jest zwracany przez wywołanie usort w Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes() dla tych, którzy nie posiadają instalacji Magento, kod wygląda następująco: wyjście

/** 
* 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); 

    ... 

Log:

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:

Po odpowiedź Vinai za dodałem więcej kodu debugowania

$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); 

i wizualizowane go graphviz: dot -Tpng dotfile > viz.png. To wynik pierwszej próby. Wywoływany po sortowaniu.

Visualization

EDIT2:

myślę, że to jest całkiem bezużyteczny.

Zrobiłem wizualizację tablicy przed scaleniem wpisów przed/po. (Zaraz po $configArray = $this->_modelsConfig;)

Jest to bez mojego shippingprotectiontax wpis:

enter image description here

to jest to z moim shippingprotectiontax wpis:

enter image description here

nie widzę żadnych wyraźnych sprzeczności .

Edit3:

Config tablicy tuż przed 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', 
), 
    'tax' => 
    array (
    'class' => 'tax/sales_total_quote_tax', 
    'after' => 
    array (
     0 => 'subtotal', 
     1 => 'shipping', 
     2 => 'discount', 
     3 => 'tax_subtotal', 
     4 => 'freeshipping', 
     5 => 'tax_shipping', 
     6 => 'nominal', 
     7 => 'weee', 
     8 => 'cashondelivery', 
     9 => 'shippingprotection', 
    ), 
    'before' => 
    array (
     0 => 'grand_total', 
     1 => 'customerbalance', 
     2 => 'giftcardaccount', 
     3 => 'tax_giftwrapping', 
     4 => 'reward', 
     5 => 'cashondelivery_tax', 
     6 => 'shippingprotectiontax', 
    ), 
    'renderer' => 'tax/checkout_tax', 
    'admin_renderer' => 'adminhtml/sales_order_create_totals_tax', 
    '_code' => 'tax', 
), 
    'weee' => 
    array (
    'class' => 'weee/total_quote_weee', 
    'after' => 
    array (
     0 => 'subtotal', 
     1 => 'tax_subtotal', 
     2 => 'nominal', 
     3 => 'freeshipping', 
     4 => 'subtotal', 
     5 => 'subtotal', 
     6 => 'nominal', 
    ), 
    'before' => 
    array (
     0 => 'tax', 
     1 => 'discount', 
     2 => 'grand_total', 
     3 => 'grand_total', 
     4 => 'tax', 
    ), 
    '_code' => 'weee', 
), 
    'customerbalance' => 
    array (
    'class' => 'enterprise_customerbalance/total_quote_customerbalance', 
    'after' => 
    array (
     0 => 'wee', 
     1 => 'discount', 
     2 => 'tax', 
     3 => 'tax_subtotal', 
     4 => 'grand_total', 
     5 => 'reward', 
     6 => 'giftcardaccount', 
     7 => 'subtotal', 
     8 => 'shipping', 
     9 => 'nominal', 
     10 => 'freeshipping', 
     11 => 'tax_shipping', 
     12 => 'weee', 
    ), 
    'renderer' => 'enterprise_customerbalance/checkout_total', 
    'before' => 
    array (
    ), 
    '_code' => 'customerbalance', 
), 
    'giftcardaccount' => 
    array (
    'class' => 'enterprise_giftcardaccount/total_quote_giftcardaccount', 
    'after' => 
    array (
     0 => 'wee', 
     1 => 'discount', 
     2 => 'tax', 
     3 => 'tax_subtotal', 
     4 => 'grand_total', 
     5 => 'reward', 
     6 => 'subtotal', 
     7 => 'shipping', 
     8 => 'nominal', 
     9 => 'freeshipping', 
     11 => 'tax_shipping', 
     12 => 'weee', 
    ), 
    'before' => 
    array (
     0 => 'customerbalance', 
    ), 
    'renderer' => 'enterprise_giftcardaccount/checkout_cart_total', 
    '_code' => 'giftcardaccount', 
), 
    'giftwrapping' => 
    array (
    'class' => 'enterprise_giftwrapping/total_quote_giftwrapping', 
    'after' => 
    array (
     0 => 'subtotal', 
     1 => 'nominal', 
    ), 
    'renderer' => 'enterprise_giftwrapping/checkout_totals', 
    'before' => 
    array (
    ), 
    '_code' => 'giftwrapping', 
), 
    'tax_giftwrapping' => 
    array (
    'class' => 'enterprise_giftwrapping/total_quote_tax_giftwrapping', 
    'after' => 
    array (
     0 => 'tax', 
     1 => 'subtotal', 
     2 => 'shipping', 
     3 => 'discount', 
     4 => 'tax_subtotal', 
     5 => 'freeshipping', 
     6 => 'tax_shipping', 
     7 => 'nominal', 
     8 => 'weee', 
    ), 
    'before' => 
    array (
     0 => 'grand_total', 
     1 => 'customerbalance', 
     2 => 'giftcardaccount', 
    ), 
    '_code' => 'tax_giftwrapping', 
), 
    'reward' => 
    array (
    'class' => 'enterprise_reward/total_quote_reward', 
    'after' => 
    array (
     0 => 'wee', 
     1 => 'discount', 
     2 => 'tax', 
     3 => 'tax_subtotal', 
     4 => 'grand_total', 
     5 => 'subtotal', 
     6 => 'shipping', 
     7 => 'nominal', 
     8 => 'freeshipping', 
     9 => 'tax_subtotal', 
     10 => 'tax_shipping', 
     11 => 'weee', 
     12 => 'subtotal', 
     13 => 'shipping', 
     14 => 'discount', 
     15 => 'tax_subtotal', 
     16 => 'freeshipping', 
     17 => 'tax_shipping', 
     18 => 'nominal', 
     19 => 'weee', 
     20 => 'freeshipping', 
     21 => 'subtotal', 
     22 => 'subtotal', 
     23 => 'nominal', 
     24 => 'subtotal', 
     25 => 'nominal', 
     26 => 'shipping', 
     27 => 'freeshipping', 
     28 => 'tax_subtotal', 
     29 => 'discount', 
     30 => 'tax', 
     31 => 'tax_giftwrapping', 
    ), 
    'before' => 
    array (
     0 => 'giftcardaccount', 
     1 => 'customerbalance', 
     2 => 'customerbalance', 
    ), 
    'renderer' => 'enterprise_reward/checkout_total', 
    '_code' => 'reward', 
), 
    'cashondelivery' => 
    array (
    'class' => 'cashondelivery/quote_total', 
    'after' => 
    array (
     0 => 'subtotal', 
     1 => 'discount', 
     2 => 'shipping', 
     3 => 'nominal', 
     4 => 'subtotal', 
     5 => 'shipping', 
     6 => 'nominal', 
     7 => 'freeshipping', 
     8 => 'tax_subtotal', 
     9 => 'tax_shipping', 
     10 => 'weee', 
     11 => 'subtotal', 
     12 => 'freeshipping', 
     13 => 'tax_subtotal', 
     14 => 'nominal', 
    ), 
    'before' => 
    array (
     0 => 'tax', 
     1 => 'grand_total', 
     2 => 'grand_total', 
     3 => 'customerbalance', 
     4 => 'giftcardaccount', 
     5 => 'tax_giftwrapping', 
     6 => 'reward', 
     7 => 'customerbalance', 
     8 => 'giftcardaccount', 
     9 => 'reward', 
    ), 
    'renderer' => 'cashondelivery/checkout_cod', 
    'admin_renderer' => 'cashondelivery/adminhtml_sales_order_create_totals_cod', 
    '_code' => 'cashondelivery', 
), 
    'cashondelivery_tax' => 
    array (
    'class' => 'cashondelivery/quote_taxTotal', 
    'after' => 
    array (
     0 => 'subtotal', 
     1 => 'discount', 
     2 => 'shipping', 
     3 => 'tax', 
     4 => 'nominal', 
     5 => 'subtotal', 
     6 => 'shipping', 
     7 => 'nominal', 
     8 => 'freeshipping', 
     9 => 'tax_subtotal', 
     10 => 'tax_shipping', 
     11 => 'weee', 
     12 => 'subtotal', 
     13 => 'freeshipping', 
     14 => 'tax_subtotal', 
     15 => 'nominal', 
     16 => 'subtotal', 
     17 => 'shipping', 
     18 => 'discount', 
     19 => 'tax_subtotal', 
     20 => 'freeshipping', 
     21 => 'tax_shipping', 
     22 => 'nominal', 
     23 => 'weee', 
     24 => 'cashondelivery', 
    ), 
    'before' => 
    array (
     0 => 'grand_total', 
     1 => 'customerbalance', 
     2 => 'giftcardaccount', 
     3 => 'reward', 
    ), 
    '_code' => 'cashondelivery_tax', 
), 
    'shippingprotection' => 
    array (
    'class' => 'n98_shippingprotection/quote_address_total_shippingprotection', 
    'after' => 
    array (
     0 => 'subtotal', 
     1 => 'discount', 
     2 => 'shipping', 
     3 => 'nominal', 
     4 => 'subtotal', 
     5 => 'shipping', 
     6 => 'nominal', 
     7 => 'freeshipping', 
     8 => 'tax_subtotal', 
     9 => 'tax_shipping', 
     10 => 'weee', 
     11 => 'subtotal', 
     12 => 'freeshipping', 
     13 => 'tax_subtotal', 
     14 => 'nominal', 
    ), 
    'before' => 
    array (
     0 => 'tax', 
     1 => 'grand_total', 
     2 => 'grand_total', 
     3 => 'customerbalance', 
     4 => 'giftcardaccount', 
     5 => 'tax_giftwrapping', 
     6 => 'reward', 
     7 => 'cashondelivery_tax', 
     8 => 'customerbalance', 
     9 => 'giftcardaccount', 
     10 => 'reward', 
    ), 
    '_code' => 'shippingprotection', 
), 
    'shippingprotectiontax' => 
    array (
    'class' => 'n98_shippingprotection/quote_address_total_shippingprotectionTax', 
    'after' => 
    array (
     0 => 'subtotal', 
     1 => 'discount', 
     2 => 'shipping', 
     3 => 'tax', 
     4 => 'nominal', 
     5 => 'subtotal', 
     6 => 'shipping', 
     7 => 'nominal', 
     8 => 'freeshipping', 
     9 => 'tax_subtotal', 
     10 => 'tax_shipping', 
     11 => 'weee', 
     12 => 'subtotal', 
     13 => 'freeshipping', 
     14 => 'tax_subtotal', 
     15 => 'nominal', 
     16 => 'subtotal', 
     17 => 'shipping', 
     18 => 'discount', 
     19 => 'tax_subtotal', 
     20 => 'freeshipping', 
     21 => 'tax_shipping', 
     22 => 'nominal', 
     23 => 'weee', 
     24 => 'cashondelivery', 
     25 => 'shippingprotection', 
    ), 
    'before' => 
    array (
     0 => 'grand_total', 
     1 => 'customerbalance', 
     2 => 'giftcardaccount', 
     3 => 'reward', 
    ), 
    '_code' => 'shippingprotectiontax', 
), 
) 

Aktualizacja: Magento Bug biletu: https://jira.magento.com/browse/MCACE-129

+1

Wierzę, że masz rację. Musi istnieć sprzeczność. Zbudowanie wykresu zależności w celu znalezienia błędu nie jest rzeczą trywialną i raczej zajmuje dużo czasu. Myślę, że użyłbym narzędzia takiego jak http://www.graphviz.org/ do przetwarzania tych danych dla mnie. Zamiast rejestrowania można wygenerować plik DOT z kodu PHP jako dane wejściowe dla GraphViz. Tylko pomysł. – Vinai

+0

@Vinai: Spójrz na moją edycję - YEAH :-) – Alex

+0

Brak zawiadomień i błędów! – Alex

Odpowiedz

16

W końcu, oto moja łatka dla tego problemu.

Wdraża sortowanie topologiczne zgodnie z sugestią Vinai.

  1. Kopiowanie app/code/core/Mage/Sales/Model/Config/Ordered.php do app/code/local/Mage/Sales/Model/Config/Ordered.php
  2. zapisać zawartość patcha do pliku total-sorting.patch i zadzwonić patch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php

W przypadku aktualizacji upewnij się, aby ponownie ubiegać się kroki.

Plaster został przetestowany do pracy z 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 @@ 
     return $totalConfig; 
    } 

+// [PATCHED CODE BEGIN] 
+ 
+ /** 
+  * Topological sort 
+  * 
+  * Copyright: http://www.calcatraz.com/blog/php-topological-sort-function-384 
+  * And fix see comment on http://stackoverflow.com/questions/11953021/topological-sorting-in-php 
+  * 
+  * @param $nodeids Node Ids 
+  * @param $edges Array of Edges. Each edge is specified as an array with two elements: The source and destination node of the edge 
+  * @return array|null 
+  */ 
+ function topological_sort($nodeids, $edges) { 
+  $L = $S = $nodes = array(); 
+  foreach($nodeids as $id) { 
+   $nodes[$id] = array('in'=>array(), 'out'=>array()); 
+   foreach($edges as $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'] as $m) { 
+     $nodes[$m]['in'] = array_diff($nodes[$m]['in'], array($id)); 
+     if (empty($nodes[$m]['in'])) { $S[] = $m; } 
+    } 
+    $nodes[$id]['out'] = array(); 
+   } 
+  } 
+  foreach($nodes as $n) { 
+   if (!empty($n['in']) or !empty($n['out'])) { 
+    return null; // not sortable as graph is cyclic 
+   } 
+  } 
+  return $L; 
+ } 
+ 
+ /** 
+  * Sort config array 
+  * 
+  * public to be easily accessable by test 
+  * 
+  * @param $configArray 
+  * @return array 
+  */ 
+ public function _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); 
+ } 
+ 
+// [PATCHED CODE END] 
+ 
+ 
    /** 
     * Aggregate before/after information from all items and sort totals based on this data 
     * 
@@ -138,38 +210,16 @@ 
     // invoke simple sorting if the first element contains the "sort_order" key 
     reset($configArray); 
     $element = current($configArray); 
+  // [PATCHED CODE BEGIN] 
     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_unique(array_merge(
-      $configArray[$code]['before'], $configArray[$beforeCode]['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_unique(array_merge(
-      $configArray[$code]['after'], $configArray[$afterCode]['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')); 
+   $sortedCollectors = $this->_topSortConfigArray($configArray); 
     } 
-  $sortedCollectors = array_keys($configArray); 
+  // [PATCHED CODE END] 
+ 
     if (Mage::app()->useCache('config')) { 
      Mage::app()->saveCache(serialize($sortedCollectors), $this->_collectorsCacheKey, array(
        Mage_Core_Model_Config::CACHE_TAG 
@@ -196,27 +246,6 @@ 
    } 

    /** 
-  * Callback that uses after/before for comparison 
-  * 
-  * @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; 
- } 
- 
- /** 
     * Callback that uses sort_order for comparison 
     * 
     * @param array $a 

EDIT: Jest też inny zaproponował zmianę (dla Magento 2): https://github.com/magento/magento2/pull/49

+1

Wielkie dzięki, łatka w tym. kod debugowania i wizualizacja działają naprawdę dobrze! –

+0

@Alex: używam 1.9 wersja magento i mam ten sam problem, czy możesz mi pomóc rozwiązać ten problem ... Proszę sprawdzić moje pytanie ... http://stackoverflow.com/questions/26118357/conflicting-two-magento-extenstion –

+0

Hej, czy ktoś ma szczęście z 1.9, czy po prostu musimy zrobić to samo? – WebDevB

2

EDIT: Ta odpowiedź jest błędna. Zobacz dyskusję w komentarzach.


Jak zauważył Vinai, problemem jest to, że funkcja zwraca 0 porządek, nawet jeśli parametry są nie równy. I zmodyfikowana funkcja spadnie z powrotem na celu ciąg klawiszy następująco:

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; 
} 
+0

Tak, podpisałem to. Jak mogę przesłać poprawkę? – Alex

+0

Niestety ta odpowiedź jest również błędna. Zobacz moją odpowiedź poniżej - problemem jest algorytm quicksort, ale rozwiązanie nie było właściwe. – Vinai

+0

Dlaczego jest źle? Moja odpowiedź pozwala uniknąć tego, że elementy, które w rzeczywistości nie są równe, są przyjmowane jako równe i dlatego unika się zmęczonego przełączania takich elementów. – Alex

19

Dzięki za utrzymujących @Alex, tutaj jest lepsza odpowiedź z lepszego wyjaśnienia :) Moja pierwsza odpowiedź była nieprawidłowa.

PHP implementuje quicksort dla wszystkich funkcji sortowania tablic (odniesienie zend_qsort.c).
Jeśli dwa rekordy w tablicy są identyczne, ich miejsce zostanie zamienione.

Problemem jest giftwrap całkowity zapis, który zgodnie z _compareTotals() jest większe niż suma cząstkowai nominalna ale równa innych sumy.

W zależności od oryginalnej kolejności tablic wejściowych $confArray i położenia elementu przestawnego można zamienić giftwrap za pomocą np. zniżka, ponieważ oba są równe, chociaż zniżka jest większa niż wysyłka.

To może sprawić, że problemem jaśniejsze z punktu widzenia algorytmów sortowania:

  • żeglugi < tax_shipping
  • giftwrapping == żeglugowego
  • giftwrapping == tax_shipping

Istnieje kilka możliwe rozwiązania, mimo że pierwotnym problemem jest wybór Quicksorta do zbudowania directed acyclic dependency graph

  • Jednym z rozwiązań (źle, krótkoterminowej) byłoby dodać więcej zależności do giftwrapping sumie, mimo że może być jeszcze więcej problemów z innych sum, które po prostu nie powierzchni, tak daleko.
  • Prawdziwym rozwiązaniem byłoby zaimplementowanie algorytmu topological sorting dla problemu.

Co ciekawe, nie ma zbyt wielu pakietów PHP. Istnieje osierocony pakiet PEAR Structures_Graph. Używanie tego byłoby prawdopodobnie szybkim rozwiązaniem, ale oznaczałoby przekształcenie struktury w strukturę Structures_Graph (więc być może nie było to szybkie).

Wikipedia radzi sobie z wyjaśnieniem problemu, więc przetaczanie własnego rozwiązania może być zabawnym wyzwaniem. Strona German Wikipedia topological sorting rozkłada problem na logiczne kroki, a także ma świetny przykładowy algorytm w PERL.

1

Dobrze było stucked na to od lat !!! +

Teraz wiem, dlaczego niektóre projekty z przeszłości były tak trudne do regulacji dotyczących wczesnych i podatkowych kombinacji koszmar mogę powiedzieć, nigdy nie zrozumieć, dlaczego Wczoraj znalazłem dlaczego, później znalazłem ten artykuł, prawdziwy wstyd ... ale przez większość czasu muszę znać odpowiedź, aby móc wyszukać pytanie ..

I rozwiązanie obvius przynajmniej dla głowic linuxowych bez strach, jest poniższy kod, w zasadzie używam starszego polecenia linuksowego tsort, które specjalnie robi topolocical porządek w taki sposób, jakiego potrzebujemy tutaj ..

Dla entomologiczne i archeologowi dusz wśród nas kilka wskazówek http://www.gnu.org/software/coreutils/manual/html_node/tsort-invocation.html,I używam technologii 80-autentyczny ... 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; 
} 

ja pisał kompletny función dla kompletności wywodu i oczywiście działa jak czar dla mnie przynajmniej ...

+0

Szalony pomysł, aby zadzwonić do funkcji powłoki :-) Oczywiście, że nie będzie działać na serwerach Windows ... Ale dzięki! – Alex

+0

@Alex Jak wszyscy wiemy, Windows nie jest obsługiwanym systemem operacyjnym dla Magento, więc nie stanowi to problemu :) –

+0

Jest tylko jeden mały problem: Sumy, które nie zostały zdefiniowane, a przed/po zostały pominięte. – Alex

0

Dyskusja powyżej jasno wskazują problemu . Zwykłe sortowanie nie działa na zbiorze danych bez funkcji porządkowej, która ma zostać rozstrzygnięta między dwoma elementami zestawu. Jeśli tylko niektóre relacje są zdefiniowane jako "zależność częściowa", to sortowanie topologiczne musi być użyte do wypełnienia deklaracji "przed" i "po". W moim teście ta deklaracja została rozbita w wynikowym zestawie w zależności od tego i tam dodaję dodatkowe moduły. Część przestraszyła, nie tylko wpływała na dodatkowy moduł, ale mogła zmienić niepodzielnie cały wynik sortowania. Więc wdrożenie standardowego rodzaju topologii, która nie rozwiązuje problemu:

/** 
* 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); 
} 

Pytanie, jak trudno byłoby je zdobyć (lub innego rodzaju TOPO) w Magento uwalniania/hot fix?

1

Postanowiłem iść z Planu B, przeciążenia getSortedCollectors ... jego proste i daje mi kontrolę absolut, jeśli oczywiście gdybym wprowadzenie nowych modułów musiałbym sprawdzić, czy muszę je dodać tutaj

<?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', 
     ); 
    } 

} 
Powiązane problemy