From 86cdadbec7b2e879b3dc6ce6135ee3ac82a96e45 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 20 Jun 2022 15:44:14 +0200 Subject: [PATCH] Sort product table (in email, invoices and order view) by reference id --- classes/pdf/HTMLTemplateDeliverySlip.php | 6 + classes/pdf/HTMLTemplateInvoice.php | 9 +- classes/pdf/HTMLTemplateOrderSlip.php | 9 +- .../QueryHandler/GetCartForViewingHandler.php | 5 + .../QueryHandler/GetOrderPreviewHandler.php | 8 +- .../GetOrderProductsForViewingHandler.php | 17 +- src/Core/Util/Sorter.php | 62 ++++++ tests/Unit/Core/Util/SorterTest.php | 185 ++++++++++++++++++ 8 files changed, 289 insertions(+), 12 deletions(-) create mode 100644 src/Core/Util/Sorter.php create mode 100644 tests/Unit/Core/Util/SorterTest.php diff --git a/classes/pdf/HTMLTemplateDeliverySlip.php b/classes/pdf/HTMLTemplateDeliverySlip.php index f1bc28f562676..b45775110b751 100644 --- a/classes/pdf/HTMLTemplateDeliverySlip.php +++ b/classes/pdf/HTMLTemplateDeliverySlip.php @@ -24,6 +24,8 @@ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ +use PrestaShop\PrestaShop\Core\Util\Sorter; + /** * @since 1.5 */ @@ -126,6 +128,10 @@ public function getContent() } } + // Sort products by Reference ID (and if equals (like combination) by Supplier Reference) + $sorter = new Sorter(); + $order_details = $sorter->natural($order_details, Sorter::ORDER_DESC, 'product_reference', 'product_supplier_reference'); + $this->smarty->assign([ 'order' => $this->order, 'order_details' => $order_details, diff --git a/classes/pdf/HTMLTemplateInvoice.php b/classes/pdf/HTMLTemplateInvoice.php index 331468b8da606..c491e85185848 100644 --- a/classes/pdf/HTMLTemplateInvoice.php +++ b/classes/pdf/HTMLTemplateInvoice.php @@ -24,6 +24,8 @@ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ +use PrestaShop\PrestaShop\Core\Util\Sorter; + /** * @since 1.5 */ @@ -238,6 +240,10 @@ public function getContent() unset($order_detail); // don't overwrite the last order_detail later } + // Sort products by Reference ID (and if equals (like combination) by Supplier Reference) + $sorter = new Sorter(); + $order_details = $sorter->natural($order_details, Sorter::ORDER_DESC, 'product_reference', 'product_supplier_reference'); + $cart_rules = $this->order->getCartRules(); $free_shipping = false; foreach ($cart_rules as $key => $cart_rule) { @@ -307,10 +313,9 @@ public function getContent() $footer[$key] = Tools::ps_round($value, Context::getContext()->getComputingPrecision(), $this->order->round_mode); } - /** + /* * Need the $round_mode for the tests. */ - $round_type = null; switch ($this->order->round_type) { case Order::ROUND_TOTAL: $round_type = 'total'; diff --git a/classes/pdf/HTMLTemplateOrderSlip.php b/classes/pdf/HTMLTemplateOrderSlip.php index c0705131779f9..2b02879f1e8cf 100644 --- a/classes/pdf/HTMLTemplateOrderSlip.php +++ b/classes/pdf/HTMLTemplateOrderSlip.php @@ -24,6 +24,8 @@ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ +use PrestaShop\PrestaShop\Core\Util\Sorter; + /** * @since 1.5 */ @@ -159,10 +161,15 @@ public function getContent() } } + $order_details = $this->order->products; + // Sort products by Reference ID (and if equals (like combination) by Supplier Reference) + $sorter = new Sorter(); + $order_details = $sorter->natural($order_details, Sorter::ORDER_DESC, 'product_reference', 'product_supplier_reference'); + $this->smarty->assign([ 'order' => $this->order, 'order_slip' => $this->order_slip, - 'order_details' => $this->order->products, + 'order_details' => $order_details, 'cart_rules' => $this->order_slip->order_slip_type == 1 ? $this->order->getCartRules() : false, 'amount_choosen' => $this->order_slip->order_slip_type == 2 ? true : false, 'delivery_address' => $formatted_delivery_address, diff --git a/src/Adapter/Cart/QueryHandler/GetCartForViewingHandler.php b/src/Adapter/Cart/QueryHandler/GetCartForViewingHandler.php index 6f32fcbf5fb7e..8086475dbec99 100644 --- a/src/Adapter/Cart/QueryHandler/GetCartForViewingHandler.php +++ b/src/Adapter/Cart/QueryHandler/GetCartForViewingHandler.php @@ -40,6 +40,7 @@ use PrestaShop\PrestaShop\Core\Domain\Cart\QueryHandler\GetCartForViewingHandlerInterface; use PrestaShop\PrestaShop\Core\Domain\Cart\QueryResult\CartView; use PrestaShop\PrestaShop\Core\Localization\Locale; +use PrestaShop\PrestaShop\Core\Util\Sorter; use Product; use StockAvailable; use Validate; @@ -117,6 +118,10 @@ public function handle(GetCartForViewing $query) $total_shipping = $summary['total_shipping']; } + // Sort products by Reference ID (and if equals (like combination) by Supplier Reference) + $sorter = new Sorter(); + $products = $sorter->natural($products, Sorter::ORDER_DESC, 'reference', 'supplier_reference'); + foreach ($products as &$product) { if ($tax_calculation_method == PS_TAX_EXC) { $product['product_price'] = $product['price']; diff --git a/src/Adapter/Order/QueryHandler/GetOrderPreviewHandler.php b/src/Adapter/Order/QueryHandler/GetOrderPreviewHandler.php index 0a15d0f0bcb69..9d600c74f14dc 100644 --- a/src/Adapter/Order/QueryHandler/GetOrderPreviewHandler.php +++ b/src/Adapter/Order/QueryHandler/GetOrderPreviewHandler.php @@ -47,6 +47,7 @@ use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderPreviewShippingDetails; use PrestaShop\PrestaShop\Core\Domain\Order\ValueObject\OrderId; use PrestaShop\PrestaShop\Core\Localization\Locale\Repository as LocaleRepository; +use PrestaShop\PrestaShop\Core\Util\Sorter; use State; use StockAvailable; use Validate; @@ -208,7 +209,12 @@ private function getProductDetails(Order $order): array $taxCalculationMethod = $this->getOrderTaxCalculationMethod($order); - foreach ($order->getProductsDetail() as $detail) { + $orderDetails = $order->getProductsDetail(); + // Sort products by Reference ID (and if equals (like combination) by Supplier Reference) + $sorter = new Sorter(); + $orderDetails = $sorter->natural($orderDetails, Sorter::ORDER_DESC, 'product_reference', 'product_supplier_reference'); + + foreach ($orderDetails as $detail) { $unitPrice = $detail['unit_price_tax_excl']; $totalPrice = $detail['total_price_tax_excl']; diff --git a/src/Adapter/Order/QueryHandler/GetOrderProductsForViewingHandler.php b/src/Adapter/Order/QueryHandler/GetOrderProductsForViewingHandler.php index 06472b97774cf..c247ce930cb1a 100644 --- a/src/Adapter/Order/QueryHandler/GetOrderProductsForViewingHandler.php +++ b/src/Adapter/Order/QueryHandler/GetOrderProductsForViewingHandler.php @@ -43,10 +43,10 @@ use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductCustomizationsForViewing; use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductForViewing; use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductsForViewing; -use PrestaShop\PrestaShop\Core\Domain\ValueObject\QuerySorting; use PrestaShop\PrestaShop\Core\Image\Parser\ImageTagSourceParserInterface; use PrestaShop\PrestaShop\Core\Localization\CLDR\ComputingPrecision; use PrestaShop\PrestaShop\Core\Localization\Locale; +use PrestaShop\PrestaShop\Core\Util\Sorter; use Product; use Shop; use StockAvailable; @@ -175,13 +175,14 @@ public function handle(GetOrderProductsForViewing $query): OrderProductsForViewi unset($product); - if (QuerySorting::DESC === $query->getProductsSorting()->getValue()) { - // reorder products by order_detail_id DESC - krsort($products); - } else { - // reorder products by order_detail_id ASC - ksort($products); - } + // Sort products by Reference ID (and if equals (like combination) by Supplier Reference) + $sorter = new Sorter(); + $products = $sorter->natural( + $products, + $query->getProductsSorting()->getValue(), + 'product_reference', + 'product_supplier_reference' + ); $productsForViewing = []; diff --git a/src/Core/Util/Sorter.php b/src/Core/Util/Sorter.php new file mode 100644 index 0000000000000..6d8574ab95d27 --- /dev/null +++ b/src/Core/Util/Sorter.php @@ -0,0 +1,62 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace PrestaShop\PrestaShop\Core\Util; + +class Sorter +{ + public const ORDER_ASC = 'ASC'; + public const ORDER_DESC = 'DESC'; + + /** + * @param array> $array + * @param string $order + * @param string ...$criterias + * + * @return array + */ + public function natural(array $array, string $order, string ...$criterias): array + { + usort($array, function ($a, $b) use ($order, $criterias) { + $cmp = 0; + foreach ($criterias as $criteria) { + if (!isset($a[$criteria]) || !isset($b[$criteria])) { + return 0; + } + $cmp = strnatcmp($a[$criteria], $b[$criteria]); + if ($cmp !== 0) { + break; + } + } + + return static::ORDER_DESC === $order ? $cmp : $cmp * -1; + }); + + return $array; + } +} diff --git a/tests/Unit/Core/Util/SorterTest.php b/tests/Unit/Core/Util/SorterTest.php new file mode 100644 index 0000000000000..eba9e54ab9f1e --- /dev/null +++ b/tests/Unit/Core/Util/SorterTest.php @@ -0,0 +1,185 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace Tests\Unit\Core\Util; + +use PHPUnit\Framework\TestCase; +use PrestaShop\PrestaShop\Core\Util\Sorter; + +class SorterTest extends TestCase +{ + /** + * @dataProvider dataProviderNatural + * + * @param array $expected + * @param array $array + * @param string $order + * @param string $criteria1 + * @param string $criteria2 + * + * @return void + */ + public function testNatural( + array $expected, + array $array, + string $order, + string $criteria1 = '', + string $criteria2 = '' + ): void { + $sorter = new Sorter(); + $this->assertEquals( + $expected, + $sorter->natural($array, $order, $criteria1, $criteria2) + ); + } + + public function dataProviderNatural(): iterable + { + yield [ + [], + [], + Sorter::ORDER_ASC, + ]; + + // Array with one criteria + yield [ + [ + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + [ + ['keyA' => 'b', 'keyB' => 'b'], + ['keyA' => 'a', 'keyB' => 'a'], + ], + Sorter::ORDER_DESC, + 'keyA', + ]; + + yield [ + [ + ['keyA' => 'b', 'keyB' => 'b'], + ['keyA' => 'a', 'keyB' => 'a'], + ], + [ + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + Sorter::ORDER_ASC, + 'keyA', + ]; + + // Array with one criteria which doesn't exist + yield [ + [ + ['keyA' => 'b', 'keyB' => 'b'], + ['keyA' => 'a', 'keyB' => 'a'], + ], + [ + ['keyA' => 'b', 'keyB' => 'b'], + ['keyA' => 'a', 'keyB' => 'a'], + ], + Sorter::ORDER_DESC, + 'keyC', + ]; + + // Array with two criterias + yield [ + [ + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + [ + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + Sorter::ORDER_DESC, + 'keyA', + 'keyB', + ]; + + yield [ + [ + ['keyA' => 'b', 'keyB' => 'b'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'a', 'keyB' => 'a'], + ], + [ + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + Sorter::ORDER_ASC, + 'keyA', + 'keyB', + ]; + + // Array with two criterias which the first doesn't exist + yield [ + [ + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + [ + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + Sorter::ORDER_DESC, + 'keyC', + 'keyB', + ]; + + // Array with two criterias which the second doesn't exist + yield [ + [ + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + [ + ['keyA' => 'a', 'keyB' => 'b'], + ['keyA' => 'b', 'keyB' => 'a'], + ['keyA' => 'a', 'keyB' => 'a'], + ['keyA' => 'b', 'keyB' => 'b'], + ], + Sorter::ORDER_DESC, + 'keyA', + 'keyC', + ]; + } +}