php - Algoritmo de ordenamiento: totales de pago de Magento ordenados erróneamente que causan el cálculo del impuesto de envío incorrecto
sorting graph-theory (6)
¡Bien se quedó pegado a esto por años!
Ahora sé por qué algunos proyectos del pasado fueron tan difíciles de ajustar con respecto a las combinaciones de impuestos e impuestos. Una pesadilla que podría decir, nunca llegué a entender por qué, ayer descubrí por qué, más tarde encontré este artículo, una verdadera lástima ... pero la mayoría de el tiempo que necesito saber la respuesta para poder buscar la pregunta ...
Y la solución obvius al menos para linux head sin miedo, es el siguiente código, básicamente hago uso de la antigua tsort command tsort que hace un orden topolocical específico de la manera que necesitamos aquí.
Para las almas entomológicas y arqueológicas, entre nosotros algunos consejos http://www.gnu.org/software/coreutils/manual/html_node/tsort-invocation.html,I estoy usando la tecnología auténtica de los 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;
}
He publicado la función completa en aras de la exhaustividad, y por supuesto está funcionando como un encanto para mí al menos ...
En Magento hay una funcionalidad donde puede definir el orden del cálculo total especificando antes y después de lo que se debe ejecutar un total.
Agregué un total personalizado y si agrego las siguientes líneas al config.xml, la clasificación es incorrecta. Incorrecto significa: el shipping
tax_shipping
viene antes del shipping
. Esto hace que el impuesto para el costo de envío se agregue dos veces.
Pero esto viola la condición
tax_shipping
after: shipping
Mi suposición: debe haber alguna contradicción en el conjunto completo de reglas. ¿Pero cómo puedo encontrarlo?
Esta es la única regla que agrego. Sin esta regla, tax_shipping
se clasifica después del shipping
.
<shippingprotectiontax>
<class>n98_shippingprotection/quote_address_total_shippingprotectionTax</class>
<after>subtotal,discount,shipping,tax</after>
<before>grand_total</before>
</shippingprotectiontax>
A continuación, pego la matriz ordenada que devuelve la llamada de la Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes()
en Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes()
Para aquellos que no tienen una instalación de Magento, el código es el siguiente:
/**
* 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);
...
Salida de registro:
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:
EDITAR:
Después de la respuesta de Vinai agregué más código de depuración
$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);
Y lo visualicé con graphviz: dot -Tpng dotfile > viz.png
. Ese es el resultado del primer intento. Llamado después de la clasificación.
EDIT2:
Creo que esto es bastante inútil.
Así que hice una visualización de la matriz antes de fusionar las entradas posteriores / anteriores. (justo después de $configArray = $this->_modelsConfig;
)
Esto es sin mi entrada de shippingprotectiontax
:
Esto es con mi entrada shippingprotectiontax
:
No veo ninguna contradicción clara.
EDIT3:
Config array justo antes de 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'', ), )
Actualización: Magento Bug Ticket: https://jira.magento.com/browse/MCACE-129
Así que finalmente, aquí está mi parche para este problema.
Implementa la clasificación topológica según lo sugerido por Vinai.
- Copie la
app/code/core/Mage/Sales/Model/Config/Ordered.php
a laapp/code/local/Mage/Sales/Model/Config/Ordered.php
- Guarde el contenido del parche en un archivo
total-sorting.patch
y llame alpatch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php
En caso de actualizaciones, asegúrese de volver a aplicar estos pasos.
El parche está probado para funcionar con 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://.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
EDITAR : También hay otro cambio sugerido (para Magento 2): https://github.com/magento/magento2/pull/49
Decidí ir con el Plan B, sobrecargando los getSortedCollectors ... es sencillo y me da un control absoluto. Si, por supuesto, introdujera nuevos módulos, debería verificar si necesito agregarlos aquí.
<?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'',
);
}
}
EDITAR: Esta respuesta es incorrecta . Ver la discusión en los comentarios.
Como observó Vinai, el problema es que la función de orden devuelve 0 incluso si los parámetros no son iguales. Modifiqué la función para volver al orden de las cadenas de la siguiente manera:
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;
}
Gracias por persistir @Alex, aquí hay una respuesta mejor con una mejor explicación :) Mi primera respuesta fue incorrecta.
PHP implementa el quicksort para todas las funciones de ordenamiento de matrices (referencia zend_qsort.c ).
Si dos registros en la matriz son idénticos, su lugar se intercambiará.
El problema es el registro total del _compareTotals()
de _compareTotals()
, que, de acuerdo con _compareTotals()
, es más grande que el subtotal y nominal, pero igual a todos los demás totales .
Dependiendo del orden original de la matriz de entrada $confArray
y de la ubicación del elemento pivote, es legal cambiar la envoltura de regalo con, por ejemplo, descuento , porque ambos son iguales, aunque el descuento es mayor que el envío .
Esto podría aclarar el problema desde el punto de vista de los algoritmos de clasificación:
- envío <tax_shipping
- giftwrapping == envío
- giftwrapping == tax_shipping
Hay varias soluciones posibles, a pesar de que el problema original es la elección de quicksort para construir un gráfico de dependencia acíclica dirigida
- Una solución (mala, a corto plazo) sería agregar más dependencias al total de la envoltura de regalo , aunque aún podría haber más problemas con otros totales que simplemente no han salido a la superficie hasta el momento.
- La solución real sería implementar un algoritmo de clasificación topológica para el problema.
Curiosamente, no hay muchos paquetes PHP flotando. Hay un paquete PEAR huérfano Structures_Graph . Usar eso probablemente sería la solución rápida, pero significaría transformar $confArray
en una estructura Structures_Graph
(así que tal vez no tan rápido).
Wikipedia hace un buen trabajo al explicar el problema, por lo que desarrollar tu propia solución puede ser un desafío divertido. La página de clasificación topológica de la Wikipedia alemana descompone el problema en pasos lógicos y también tiene un gran algoritmo de ejemplo en PERL.
La discusión anterior indica claramente el problema. La ordenación usual no funciona en el conjunto de datos sin función de orden que se puede desafiar entre dos elementos del conjunto. Si solo una parte de relacional se define como una "dependencia parcial", entonces la ordenación topológica debe usarse para obedecer las declaraciones declaradas "antes" y "después". En mi prueba, esta declaración se rompió en el conjunto resultante dependiendo de eso y allí agregué módulos adicionales. La parte del susto, no solo afectaba al módulo adicional, sino que también podía cambiar todo el resultado de clasificación de una manera impredecible. Entonces, implemento el tipo topológico estándar que resuelve el problema:
/**
* 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);
}
La pregunta, ¿qué tan difícil sería conseguirlo (u otro tipo de topografía) en la versión Magento / hot fix?