Алгоритм сортировки: итоговые суммы погашения Magento отсортированы неправильно, что приводит к неправильному исчислению налога на отправку

В Magento есть функциональность, в которой вы можете определить порядок общего расчета, указав до и после которого общее количество должно быть запущено.

Я добавил пользовательскую общую сумму, и если я добавлю следующие строки в файл config.xml, сортировка будет неправильной. Неправильные средства: tax_shipping приходит перед shipping . Это приводит к тому, что налог за доставку будет добавляться дважды.

Но это нарушает условие

 tax_shipping after: shipping 

Мое предположение: должно быть какое-то противоречие в полном своде правил. Но как я могу его найти?

Это единственное правило, которое я добавляю. Без этого правила tax_shipping сортируется после shipping .

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

Ниже я вставляю отсортированный массив, который возвращается вызовом usort в Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes() Для тех, у кого нет установки 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: 

РЕДАКТИРОВАТЬ:

После ответа Винаи я добавил еще код отладки

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

И визуализировал его с помощью графика: dot -Tpng dotfile > viz.png . Это результат первой попытки. Вызывается после сортировки.

Визуализация

EDIT2:

Я думаю, что это бесполезно.

Поэтому я сделал визуализацию массива перед объединением записей after / before. (сразу после $configArray = $this->_modelsConfig; )

Это без моей записи shippingprotectiontax :

введите описание изображения здесь

Это с моей записью shippingprotectiontax :

введите описание изображения здесь

Я не вижу ясных противоречий.

EDIT3:

Конфигурировать массив непосредственно перед uasort:

 массив (
   'номинальный' => 
   массив (
     'class' => 'sales / quote_address_total_nominal',
     'before' => 
     массив (
       0 => 'subtotal',
       1 => 'grand_total',
     ),
     'renderer' => 'checkout / total_nominal',
     'after' => 
     массив (
     ),
     '_code' => 'номинальный',
   ),
   'subtotal' => 
   массив (
     'class' => 'sales / quote_address_total_subtotal',
     'after' => 
     массив (
       0 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => 'grand_total',
       1 => 'доставка',
       2 => 'freeshipping',
       3 => 'tax_subtotal',
       4 => 'скидка',
       5 => «налог»,
       6 => 'weee',
       7 => 'giftwrapping',
       8 => «наличная доставка»,
       9 => 'cashondelivery_tax',
       10 => 'shippingprotection',
       11 => 'shippingprotectiontax',
     ),
     'renderer' => 'tax / checkout_subtotal',
     'admin_renderer' => 'adminhtml / sales_order_create_totals_subtotal',
     '_code' => 'subtotal',
   ),
   'shipping' => 
   массив (
     'class' => 'sales / quote_address_total_shipping',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'freeshipping',
       2 => 'tax_subtotal',
       3 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => 'grand_total',
       1 => 'скидка',
       2 => 'tax_shipping',
       3 => «налог»,
       4 => «наличная доставка»,
       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',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'номинальный',
       2 => 'доставка',
       3 => 'freeshipping',
       4 => 'tax_subtotal',
       5 => «скидка»,
       6 => «налог»,
       7 => 'tax_giftwrapping',
       8 => «наличная доставка»,
       9 => 'cashondelivery_tax',
       10 => 'shippingprotection',
       11 => 'shippingprotectiontax',
     ),
     'renderer' => 'tax / checkout_grandtotal',
     'admin_renderer' => 'adminhtml / sales_order_create_totals_grandtotal',
     'before' => 
     массив (
       0 => 'balancebalance',
       1 => 'giftcardaccount',
       2 => 'награда',
     ),
     '_code' => 'grand_total',
   ),
   'freeshipping' => 
   массив (
     'class' => 'salesrule / quote_freeshipping',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => 'tax_subtotal',
       1 => 'доставка',
       2 => 'grand_total',
       3 => «налог»,
       4 => 'скидка',
     ),
     '_code' => 'freeshipping',
   ),
   'discount' => 
   массив (
     'class' => 'salesrule / quote_discount',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'доставка',
       2 => 'номинальный',
       3 => 'freeshipping',
       4 => 'tax_subtotal',
       5 => 'tax_shipping',
       6 => 'weee',
     ),
     'before' => 
     массив (
       0 => 'grand_total',
       1 => «налог»,
       2 => «пользовательский баланс»,
       3 => 'giftcardaccount',
       4 => 'награда',
       5 => «наличная доставка»,
       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',
     'after' => 
     массив (
       0 => 'freeshipping',
       1 => 'subtotal',
       2 => 'subtotal',
       3 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => «налог»,
       1 => 'скидка',
       2 => 'доставка',
       3 => 'grand_total',
       4 => 'weee',
       5 => «пользовательский баланс»,
       6 => 'giftcardaccount',
       7 => 'награда',
     ),
     '_code' => 'tax_subtotal',
   ),
   'tax_shipping' => 
   массив (
     'class' => 'tax / sales_total_quote_shipping',
     'after' => 
     массив (
       0 => 'отгрузка',
       1 => 'subtotal',
       2 => 'freeshipping',
       3 => 'tax_subtotal',
       4 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => «налог»,
       1 => 'скидка',
       2 => 'grand_total',
       3 => 'grand_total',
     ),
     '_code' => 'tax_shipping',
   ),
   'tax' => 
   массив (
     'class' => 'tax / sales_total_quote_tax',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'доставка',
       2 => «скидка»,
       3 => 'tax_subtotal',
       4 => 'freeshipping',
       5 => 'tax_shipping',
       6 => 'номинальный',
       7 => 'weee',
       8 => «наличная доставка»,
       9 => 'shippingprotection',
     ),
     'before' => 
     массив (
       0 => 'grand_total',
       1 => «пользовательский баланс»,
       2 => 'giftcardaccount',
       3 => 'tax_giftwrapping',
       4 => 'награда',
       5 => 'cashondelivery_tax',
       6 => 'shippingprotectiontax',
     ),
     'renderer' => 'tax / checkout_tax',
     'admin_renderer' => 'adminhtml / sales_order_create_totals_tax',
     '_code' => 'tax',
   ),
   'weee' => 
   массив (
     'class' => 'weee / total_quote_weee',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'tax_subtotal',
       2 => 'номинальный',
       3 => 'freeshipping',
       4 => 'subtotal',
       5 => 'subtotal',
       6 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => «налог»,
       1 => 'скидка',
       2 => 'grand_total',
       3 => 'grand_total',
       4 => «налог»,
     ),
     '_code' => 'weee',
   ),
   'customerbalance' => 
   массив (
     'class' => 'enterprise_customerbalance / total_quote_customerbalance',
     'after' => 
     массив (
       0 => 'wee',
       1 => 'скидка',
       2 => «налог»,
       3 => 'tax_subtotal',
       4 => 'grand_total',
       5 => 'награда',
       6 => 'giftcardaccount',
       7 => 'subtotal',
       8 => 'доставка',
       9 => 'номинальный',
       10 => 'freeshipping',
       11 => 'tax_shipping',
       12 => 'weee',
     ),
     'renderer' => 'enterprise_customerbalance / checkout_total',
     'before' => 
     массив (
     ),
     '_code' => 'balancebalance',
   ),
   'giftcardaccount' => 
   массив (
     'class' => 'enterprise_giftcardaccount / total_quote_giftcardaccount',
     'after' => 
     массив (
       0 => 'wee',
       1 => 'скидка',
       2 => «налог»,
       3 => 'tax_subtotal',
       4 => 'grand_total',
       5 => 'награда',
       6 => 'subtotal',
       7 => 'доставка',
       8 => 'номинальный',
       9 => 'freeshipping',
       11 => 'tax_shipping',
       12 => 'weee',
     ),
     'before' => 
     массив (
       0 => 'balancebalance',
     ),
     'renderer' => 'enterprise_giftcardaccount / checkout_cart_total',
     '_code' => 'giftcardaccount',
   ),
   'giftwrapping' => 
   массив (
     'class' => 'enterprise_giftwrapping / total_quote_giftwrapping',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'номинальный',
     ),
     'renderer' => 'enterprise_giftwrapping / checkout_totals',
     'before' => 
     массив (
     ),
     '_code' => 'giftwrapping',
   ),
   'tax_giftwrapping' => 
   массив (
     'class' => 'enterprise_giftwrapping / total_quote_tax_giftwrapping',
     'after' => 
     массив (
       0 => «налог»,
       1 => 'subtotal',
       2 => 'доставка',
       3 => 'скидка',
       4 => 'tax_subtotal',
       5 => 'freeshipping',
       6 => 'tax_shipping',
       7 => 'номинальный',
       8 => 'weee',
     ),
     'before' => 
     массив (
       0 => 'grand_total',
       1 => «пользовательский баланс»,
       2 => 'giftcardaccount',
     ),
     '_code' => 'tax_giftwrapping',
   ),
   'награда' => 
   массив (
     'class' => 'enterprise_reward / total_quote_reward',
     'after' => 
     массив (
       0 => 'wee',
       1 => 'скидка',
       2 => «налог»,
       3 => 'tax_subtotal',
       4 => 'grand_total',
       5 => 'subtotal',
       6 => 'доставка',
       7 => 'номинальный',
       8 => 'freeshipping',
       9 => 'tax_subtotal',
       10 => 'tax_shipping',
       11 => 'weee',
       12 => 'subtotal',
       13 => «отгрузка»,
       14 => «скидка»,
       15 => 'tax_subtotal',
       16 => 'freeshipping',
       17 => 'tax_shipping',
       18 => 'номинальный',
       19 => 'weee',
       20 => 'freeshipping',
       21 => 'subtotal',
       22 => 'subtotal',
       23 => 'номинальный',
       24 => 'subtotal',
       25 => 'номинальный',
       26 => 'доставка',
       27 => 'freeshipping',
       28 => 'tax_subtotal',
       29 => «скидка»,
       30 => «налог»,
       31 => 'tax_giftwrapping',
     ),
     'before' => 
     массив (
       0 => 'giftcardaccount',
       1 => «пользовательский баланс»,
       2 => «пользовательский баланс»,
     ),
     'renderer' => 'enterprise_reward / checkout_total',
     '_code' => 'вознаграждение',
   ),
   'cashondelivery' => 
   массив (
     'class' => 'cashondelivery / quote_total',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'скидка',
       2 => 'доставка',
       3 => 'номинальный',
       4 => 'subtotal',
       5 => 'доставка',
       6 => 'номинальный',
       7 => 'freeshipping',
       8 => 'tax_subtotal',
       9 => 'tax_shipping',
       10 => 'weee',
       11 => 'subtotal',
       12 => 'freeshipping',
       13 => 'tax_subtotal',
       14 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => «налог»,
       1 => 'grand_total',
       2 => 'grand_total',
       3 => «пользовательский баланс»,
       4 => 'giftcardaccount',
       5 => 'tax_giftwrapping',
       6 => 'награда',
       7 => «пользовательский баланс»,
       8 => 'giftcardaccount',
       9 => 'награда',
     ),
     'renderer' => 'cashondelivery / checkout_cod',
     'admin_renderer' => 'cashondelivery / adminhtml_sales_order_create_totals_cod',
     '_code' => 'cashondelivery',
   ),
   'cashondelivery_tax' => 
   массив (
     'class' => 'cashondelivery / quote_taxTotal',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'скидка',
       2 => 'доставка',
       3 => «налог»,
       4 => 'номинальный',
       5 => 'subtotal',
       6 => 'доставка',
       7 => 'номинальный',
       8 => 'freeshipping',
       9 => 'tax_subtotal',
       10 => 'tax_shipping',
       11 => 'weee',
       12 => 'subtotal',
       13 => 'freeshipping',
       14 => 'tax_subtotal',
       15 => 'номинальный',
       16 => 'subtotal',
       17 => 'доставка',
       18 => 'скидка',
       19 => 'tax_subtotal',
       20 => 'freeshipping',
       21 => 'tax_shipping',
       22 => 'номинальный',
       23 => 'weee',
       24 => «наличная доставка»,
     ),
     'before' => 
     массив (
       0 => 'grand_total',
       1 => «пользовательский баланс»,
       2 => 'giftcardaccount',
       3 => 'награда',
     ),
     '_code' => 'cashondelivery_tax',
   ),
   'shippingprotection' => 
   массив (
     'class' => 'n98_shippingprotection / quote_address_total_shippingprotection',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'скидка',
       2 => 'доставка',
       3 => 'номинальный',
       4 => 'subtotal',
       5 => 'доставка',
       6 => 'номинальный',
       7 => 'freeshipping',
       8 => 'tax_subtotal',
       9 => 'tax_shipping',
       10 => 'weee',
       11 => 'subtotal',
       12 => 'freeshipping',
       13 => 'tax_subtotal',
       14 => 'номинальный',
     ),
     'before' => 
     массив (
       0 => «налог»,
       1 => 'grand_total',
       2 => 'grand_total',
       3 => «пользовательский баланс»,
       4 => 'giftcardaccount',
       5 => 'tax_giftwrapping',
       6 => 'награда',
       7 => 'cashondelivery_tax',
       8 => «пользовательский баланс»,
       9 => 'giftcardaccount',
       10 => 'награда',
     ),
     '_code' => 'shippingprotection',
   ),
   'shippingprotectiontax' => 
   массив (
     'class' => 'n98_shippingprotection / quote_address_total_shippingprotectionTax',
     'after' => 
     массив (
       0 => 'subtotal',
       1 => 'скидка',
       2 => 'доставка',
       3 => «налог»,
       4 => 'номинальный',
       5 => 'subtotal',
       6 => 'доставка',
       7 => 'номинальный',
       8 => 'freeshipping',
       9 => 'tax_subtotal',
       10 => 'tax_shipping',
       11 => 'weee',
       12 => 'subtotal',
       13 => 'freeshipping',
       14 => 'tax_subtotal',
       15 => 'номинальный',
       16 => 'subtotal',
       17 => 'доставка',
       18 => 'скидка',
       19 => 'tax_subtotal',
       20 => 'freeshipping',
       21 => 'tax_shipping',
       22 => 'номинальный',
       23 => 'weee',
       24 => «наличная доставка»,
       25 => 'shippingprotection',
     ),
     'before' => 
     массив (
       0 => 'grand_total',
       1 => «пользовательский баланс»,
       2 => 'giftcardaccount',
       3 => 'награда',
     ),
     '_code' => 'shippingprotectiontax',
   ),
 )

Обновление: Magento Bug Ticket: https://jira.magento.com/browse/MCACE-129

    Спасибо за сохранение @Alex, вот лучший ответ с лучшим объяснением 🙂 Мой первый ответ был неправильным.

    PHP реализует quicksort для всех функций сортировки массивов (ссылка zend_qsort.c ).
    Если две записи в массиве идентичны, их место будет заменено.

    Проблема заключается в суммарной записи giftwrap , которая, согласно _compareTotals() , больше, чем промежуточная и номинальная, но равна всем остальным суммам .

    В зависимости от исходного порядка входного массива $confArray и размещения элемента сводки законно обменивать подарочную упаковку, например, скидкой , потому что оба они равны, хотя скидка больше, чем доставка .

    Это может сделать проблему более ясной с точки зрения алгоритмов сортировки:

    • отправка <налог_поставка
    • giftwrapping == доставка
    • giftwrapping == tax_shipping

    Существует несколько возможных решений, хотя исходной проблемой является выбор quicksort для построения ориентированного ациклического графика зависимости

    • Одно (плохое, короткое) решение будет заключаться в том, чтобы добавить больше зависимостей к сумме giftwrapping , даже несмотря на то, что все еще может быть больше проблем с другими суммами, которые до сих пор просто не отображались.
    • Реальным решением было бы реализовать топологический алгоритм сортировки для проблемы.

    Интересно, что не так много платных пакетов PHP. Существует сиротский пакет PEAR Structures_Graph . Использование этого, вероятно, было бы быстрым решением, но это означало бы преобразование $confArray в Structures_Graph (возможно, не так быстро).

    Википедия хорошо справляется с этой проблемой, поэтому распространение собственного решения может быть интересной задачей. Страница топологической сортировки в немецкой Википедии разбивает проблему на логические шаги, а также имеет отличный примерный алгоритм в PERL.

    Итак, вот мой патч для этой проблемы.

    Он реализует топологическую сортировку, предложенную Винаем.

    1. Скопируйте app/code/core/Mage/Sales/Model/Config/Ordered.php в app/code/local/Mage/Sales/Model/Config/Ordered.php
    2. Сохраните содержимое патча в файл total-sorting.patch и вызовите patch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php

    В случае обновления обязательно повторите эти шаги.

    Патч проверен на работу с 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;
          }
    
     + // [НАЧАЛО ПОСЛЕДНЕГО КОДА]
     +
     + / **
     + * Топологическая сортировка
     + *
     + * Авторские права: http://www.calcatraz.com/blog/php-topological-sort-function-384
     + * И исправить см. Комментарий на http://stackoverflow.com/questions/11953021/topological-sorting-in-php
     + *
     + * @param $ nodeids Идентификаторы узлов
     + * @param $ edge Массив ребер.  Каждое ребро задается как массив с двумя элементами: исходный и конечный узел края
     + * @return array | null
     + * /
     + function topological_sort ($ nodeids, $ edge) {
     + $ 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 ($ nodes as $ n) {
     + if (! empty ($ n ['in']) или! empty ($ n ['out'])) {
     + return null;  // не сортируется, так как граф является циклическим
     +}
     +}
     + return $ L;
     +}
     +
     + / **
     + * Сортировка массива конфигурации
     + *
     + * общедоступный, доступный для тестирования
     + *
     + * @param $ configArray
     + * @return array
     + * /
     + public function _topSortConfigArray ($ configArray)
     + {
     + $ nodes = array_keys ($ configArray);
     + $ edge = array ();
     +
     + foreach ($ configArray как $ code => $ data) {
     + $ _code = $ data ['_ code'];
     + if (! isset ($ configArray [$ _ code])) continue;
     + foreach ($ data ['before'] как $ beforeCode) {
     + if (! isset ($ configArray [$ beforeCode])) продолжить;
     + $ edges [] = массив ($ _ code, $ beforeCode);
     +}
     +
     + foreach ($ data ['after'] as $ afterCode) {
     + if (! isset ($ configArray [$ afterCode])) продолжить;
     + $ edges [] = массив ($ afterCode, $ _code);
     +}
     +}
     + return $ this-> topological_sort ($ nodes, $ edge);
     +}
     +
     + // [КОНЕЦ КОНЕЧНОГО КОДА]
     +
     +
          / **
           * Совокупность информации до / после всех элементов и итоговых итогов на основе этих данных
           *
     @@ -138,38 +210,16 @@
              // вызывать простую сортировку, если первый элемент содержит ключ sort_order
              сброса ($ configArray);
              $ element = current ($ configArray);
     + // [НАЧАЛО ПОСЛЕДНЕГО КОДА]
              if (isset ($ element ['sort_order'])) {
                  uasort ($ configArray, array ($ this, '_compareSortOrder));
     + $ sortedCollectors = array_keys ($ configArray);
     +
              } else {
     - foreach ($ configArray как $ code => $ data) {
     - foreach ($ data ['before'] как $ 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] ['after'], массив ($ code), $ data ['after']
     -);
     - $ configArray [$ beforeCode] ['after'] = array_unique ($ configArray [$ beforeCode] ['after']);
     -}
     - foreach ($ data ['after'] как $ 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 @@
          }
    
          / **
     - * Обратный вызов, который использует после / перед сравнением
     - *
     - * @param array $ a
     - * @param array $ b
     - * @return int
     - * /
     - защищенная функция _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;
     -}
     - вернуть $ res;
     -}
     -
     - / **
           * Обратный вызов, который использует sort_order для сравнения
           *
           * @param array $ a
    

    EDIT : Существует также другое предложенное изменение (для Magento 2): https://github.com/magento/magento2/pull/49

    EDIT: Этот ответ неверен . См. Обсуждение в комментариях.


    Как заметил Винай, проблема в том, что функция порядка возвращает 0, даже если параметры не равны. Я изменил функцию, чтобы вернуться к строковому порядку клавиш следующим образом:

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

    Хорошо было застрял в этом годами !!! +

    Теперь я знаю, почему некоторые проекты прошлого были настолько сложными для корректировки в отношении комбинаций налогов и налогов, которые я мог сказать, никогда не понимал, почему вчера я нашел, почему, позже я нашел эту статью, настоящий позор … но большая часть время, когда мне нужно знать ответ, чтобы быть в состоянии поискать вопрос ..

    И решение obvius, по крайней мере, для Linux-голов без страха, – это код ниже, в основном я использую древнюю командную команду linux, которая особенно делает топологический порядок так, как нам нужно здесь.

    Для энтомологических и археологических душ среди нас есть несколько указателей http://www.gnu.org/software/coreutils/manual/html_node/tsort-invocation.html. Я использую аутентичную технологию 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; } 

    Я написал полную функциональность ради полноты, и, конечно же, для меня как бы прелести, по крайней мере …

    Я решил пойти с Plan 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', ); } } 

    В приведенном выше обсуждении четко указывается проблема. Обычная сортировка не работает с набором данных без функции заказа, которую нужно игнорировать между любыми двумя элементами набора. Если только некоторые из реляционных определяется как «частичная зависимость», то топологическая сортировка должна использоваться для выполнения заявленных «до» и «после» операторов. В моем тесте это объявление было нарушено в приведенном наборе в зависимости от этого, и там я добавляю дополнительные модули. Испуганная часть, это не только повлияло на дополнительный модуль, но и изменило весь результат сортировки непредсказуемым образом. Итак, я реализую стандартную топологическую сортировку, которая решает проблему:

     /** * 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?