Skip to content

Commit e62eb52

Browse files
committed
Merge branch 'MAGETWO-57880' into bugs
2 parents b60a1ba + 87e0647 commit e62eb52

File tree

14 files changed

+344
-4
lines changed

14 files changed

+344
-4
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Bundle\Model\Sales\Order\Plugin;
8+
9+
/**
10+
* Plugin to calculate bundle item qty available for cancel
11+
*/
12+
class Item
13+
{
14+
/**
15+
* Retrieve item qty available for cancel
16+
*
17+
* @param \Magento\Sales\Model\Order\Item $subject
18+
* @param float|integer $result
19+
* @return float|integer
20+
*/
21+
public function afterGetQtyToCancel(\Magento\Sales\Model\Order\Item $subject, $result)
22+
{
23+
if ($subject->getProductType() === \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE || $subject->getParentItem()
24+
&& $subject->getParentItem()->getProductType() === \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
25+
) {
26+
$qtyToCancel = $this->getQtyToCancelBundle($subject);
27+
return max($qtyToCancel, 0);
28+
}
29+
return $result;
30+
}
31+
32+
/**
33+
* Retrieve item qty available for ship
34+
*
35+
* @param \Magento\Sales\Model\Order\Item $subject
36+
* @param float|integer $result
37+
* @return bool
38+
*/
39+
public function afterIsProcessingAvailable(\Magento\Sales\Model\Order\Item $subject, $result)
40+
{
41+
if ($subject->getProductType() === \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE || $subject->getParentItem()
42+
&& $subject->getParentItem()->getProductType() === \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
43+
) {
44+
return $subject->getSimpleQtyToShip() > $subject->getQtyToCancel();
45+
}
46+
return $result;
47+
}
48+
49+
/**
50+
* Retrieve Bundle child item qty available for cancel
51+
* getQtyToShip() always returns 0 for BundleItems that ship together
52+
*
53+
* @param \Magento\Sales\Model\Order\Item $item
54+
* @return float|integer
55+
*/
56+
private function getQtyToCancelBundle($item)
57+
{
58+
if ($item->isDummy(true)) {
59+
return min($item->getQtyToInvoice(), $item->getSimpleQtyToShip());
60+
}
61+
return min($item->getQtyToInvoice(), $item->getQtyToShip());
62+
}
63+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Bundle\Test\Unit\Model\Sales\Order\Plugin;
8+
9+
class ItemTest extends \PHPUnit_Framework_TestCase
10+
{
11+
private $plugin;
12+
13+
private $itemMock;
14+
15+
protected function setUp()
16+
{
17+
$this->itemMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
18+
->disableOriginalConstructor()
19+
->getMock();
20+
$this->plugin = new \Magento\Bundle\Model\Sales\Order\Plugin\Item();
21+
}
22+
23+
public function testAfterGetQtyToCancelIfProductIsBundle()
24+
{
25+
$qtyToCancel = 10;
26+
$result = 5;
27+
28+
$this->itemMock
29+
->expects($this->once())
30+
->method('getProductType')
31+
->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE);
32+
$this->itemMock->expects($this->once())->method('isDummy')->willReturn(true);
33+
$this->itemMock->expects($this->once())->method('getQtyToInvoice')->willReturn(15);
34+
$this->itemMock->expects($this->once())->method('getSimpleQtyToShip')->willReturn($qtyToCancel);
35+
$this->assertEquals($qtyToCancel, $this->plugin->afterGetQtyToCancel($this->itemMock, $result));
36+
}
37+
38+
public function testAfterGetQtyToCancelIfParentProductIsBundle()
39+
{
40+
$qtyToCancel = 10;
41+
$result = 5;
42+
$parentItemMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
43+
->disableOriginalConstructor()
44+
->getMock();
45+
$this->itemMock
46+
->expects($this->once())
47+
->method('getProductType')
48+
->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE);
49+
$this->itemMock->expects($this->any())->method('getParentItem')->willReturn($parentItemMock);
50+
$parentItemMock->expects($this->once())
51+
->method('getProductType')
52+
->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE);
53+
$this->itemMock->expects($this->once())->method('isDummy')->willReturn(false);
54+
$this->itemMock->expects($this->once())->method('getQtyToInvoice')->willReturn(15);
55+
$this->itemMock->expects($this->once())->method('getQtyToShip')->willReturn($qtyToCancel);
56+
$this->assertEquals($qtyToCancel, $this->plugin->afterGetQtyToCancel($this->itemMock, $result));
57+
}
58+
public function testAfterGetQtyToCancelForSimpleProduct()
59+
{
60+
$result = 5;
61+
$this->itemMock
62+
->expects($this->once())
63+
->method('getProductType')
64+
->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE);
65+
$this->itemMock->expects($this->any())->method('getParentItem')->willReturn(false);
66+
$this->itemMock->expects($this->never())->method('isDummy');
67+
$this->itemMock->expects($this->never())->method('getQtyToInvoice');
68+
$this->assertEquals($result, $this->plugin->afterGetQtyToCancel($this->itemMock, $result));
69+
}
70+
71+
public function testAfterIsProcessingAvailableForProductWithoutParent()
72+
{
73+
$this->itemMock->expects($this->once())->method('getParentItem')->willReturn(false);
74+
$this->assertFalse($this->plugin->afterIsProcessingAvailable($this->itemMock, false));
75+
}
76+
77+
public function testAfterIsProcessingAvailableForProductWhenParentIsBundle()
78+
{
79+
$parentItemMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
80+
->disableOriginalConstructor()
81+
->getMock();
82+
$this->itemMock->expects($this->any())->method('getParentItem')->willReturn($parentItemMock);
83+
$parentItemMock->expects($this->once())
84+
->method('getProductType')
85+
->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE);
86+
$this->itemMock->expects($this->once())->method('getSimpleQtyToShip')->willReturn(10);
87+
$this->itemMock->expects($this->once())->method('getQtyToCancel')->willReturn(5);
88+
$this->assertTrue($this->plugin->afterIsProcessingAvailable($this->itemMock, false));
89+
}
90+
91+
public function testAfterIsProcessingAvailableForBundleProduct()
92+
{
93+
$this->itemMock->expects($this->once())
94+
->method('getProductType')
95+
->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE);
96+
$this->itemMock->expects($this->once())->method('getSimpleQtyToShip')->willReturn(10);
97+
$this->itemMock->expects($this->once())->method('getQtyToCancel')->willReturn(5);
98+
$this->assertTrue($this->plugin->afterIsProcessingAvailable($this->itemMock, false));
99+
}
100+
}

app/code/Magento/Bundle/etc/di.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
<type name="Magento\Catalog\Model\Product">
8282
<plugin name="bundle" type="Magento\Bundle\Model\Plugin\Product" sortOrder="100" />
8383
</type>
84+
<type name="Magento\Sales\Model\Order\Item">
85+
<plugin name="bundle" type="Magento\Bundle\Model\Sales\Order\Plugin\Item" sortOrder="100" />
86+
</type>
8487
<type name="Magento\Framework\EntityManager\Operation\ExtensionPool">
8588
<arguments>
8689
<argument name="extensionActions" xsi:type="array">

app/code/Magento/Sales/Model/Order.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1126,7 +1126,7 @@ public function registerCancellation($comment = '', $graceful = true)
11261126
$state = self::STATE_CANCELED;
11271127
foreach ($this->getAllItems() as $item) {
11281128
if ($state != self::STATE_PROCESSING && $item->getQtyToRefund()) {
1129-
if ($item->getQtyToShip() > $item->getQtyToCancel()) {
1129+
if ($item->isProcessingAvailable()) {
11301130
$state = self::STATE_PROCESSING;
11311131
} else {
11321132
$state = self::STATE_COMPLETE;

app/code/Magento/Sales/Model/Order/Item.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2391,4 +2391,14 @@ public function setExtensionAttributes(\Magento\Sales\Api\Data\OrderItemExtensio
23912391
}
23922392

23932393
//@codeCoverageIgnoreEnd
2394+
2395+
/**
2396+
* Check if it is possible to process item after cancellation
2397+
*
2398+
* @return bool
2399+
*/
2400+
public function isProcessingAvailable()
2401+
{
2402+
return $this->getQtyToShip() > $this->getQtyToCancel();
2403+
}
23942404
}

dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/Cart/Item.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public function getData($key = null)
4646
$optionData = [
4747
'title' => $checkoutOption['title'],
4848
'value' => "{$qty} x {$value} {$price}",
49+
'sku' => "{$qty} x {$value}"
4950
];
5051

5152
$checkoutBundleOptions[$checkoutOptionKey] = $optionData;

dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,5 +411,43 @@
411411
<item name="dataset" xsi:type="string">bundle_low_stock_fixed</item>
412412
</field>
413413
</dataset>
414+
415+
<dataset name="bundle_dynamic_product_shipment_together">
416+
<field name="name" xsi:type="string">Bundle dynamic product %isolation%</field>
417+
<field name="sku" xsi:type="string">sku_bundle_dynamic_product_%isolation%</field>
418+
<field name="sku_type" xsi:type="string">Yes</field>
419+
<field name="price_type" xsi:type="string">Yes</field>
420+
<field name="price" xsi:type="array">
421+
<item name="dataset" xsi:type="string">default_dynamic</item>
422+
</field>
423+
<field name="quantity_and_stock_status" xsi:type="array">
424+
<item name="is_in_stock" xsi:type="string">In Stock</item>
425+
</field>
426+
<field name="weight_type" xsi:type="string">Yes</field>
427+
<field name="shipment_type" xsi:type="string">Together</field>
428+
<field name="tax_class_id" xsi:type="array">
429+
<item name="dataset" xsi:type="string">taxable_goods</item>
430+
</field>
431+
<field name="website_ids" xsi:type="array">
432+
<item name="0" xsi:type="array">
433+
<item name="dataset" xsi:type="string">default</item>
434+
</item>
435+
</field>
436+
<field name="stock_data" xsi:type="array">
437+
<item name="manage_stock" xsi:type="string">Yes</item>
438+
<item name="use_config_enable_qty_increments" xsi:type="string">Yes</item>
439+
<item name="use_config_qty_increments" xsi:type="string">Yes</item>
440+
<item name="is_in_stock" xsi:type="string">In Stock</item>
441+
</field>
442+
<field name="url_key" xsi:type="string">bundle-dynamic-product-%isolation%</field>
443+
<field name="visibility" xsi:type="string">Catalog, Search</field>
444+
<field name="bundle_selections" xsi:type="array">
445+
<item name="dataset" xsi:type="string">default_dynamic</item>
446+
</field>
447+
<field name="attribute_set_id" xsi:type="string">Default</field>
448+
<field name="checkout_data" xsi:type="array">
449+
<item name="dataset" xsi:type="string">bundle_default_dynamic</item>
450+
</field>
451+
</dataset>
414452
</repository>
415453
</config>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Bundle\Test\TestCase;
8+
9+
use Magento\Mtf\TestCase\Scenario;
10+
11+
/**
12+
* Preconditions:
13+
* 1. Enable payment method: "Transfer/Cash on Delivery/Purchase Order/Zero Subtotal Checkout".
14+
* 2. Enable shipping method one of "Flat Rate.
15+
* 3. Create order.
16+
*
17+
* Steps:
18+
* 1. Login to backend.
19+
* 2. Sales > Orders.
20+
* 3. Open the created order.
21+
* 4. Create partial invoice
22+
* 4. Do cancel Order.
23+
* 5. Perform all assetions.
24+
*
25+
* @group Order_Management
26+
* @ZephyrId MAGETWO-67787
27+
*/
28+
class CancelPartiallyInvoicedOrderTest extends Scenario
29+
{
30+
/**
31+
* Runs test for invoice creation for order placed with offline payment method.
32+
*
33+
* @return void
34+
*/
35+
public function test()
36+
{
37+
$this->executeScenario();
38+
}
39+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
9+
<testCase name="Magento\Bundle\Test\TestCase\CancelPartiallyInvoicedOrderTest" summary="Cancel for order that was partially invoiced with Bundle product type" ticketId="MAGETWO-67787">
10+
<variation name="CancelPartiallyInvoicedOrderWithBundleProductTest">
11+
<data name="order/dataset" xsi:type="string">default</data>
12+
<data name="order/data/entity_id/products" xsi:type="string">bundleProduct::bundle_dynamic_product_shipment_together</data>
13+
<data name="order/data/total_qty_ordered/0" xsi:type="string">-</data>
14+
<data name="order/data/payment_auth_expiration/method" xsi:type="string">cashondelivery</data>
15+
<data name="order/data/invoice" xsi:type="array">
16+
<item name="0" xsi:type="array">
17+
<item name="items_data" xsi:type="array">
18+
<item name="0" xsi:type="array">
19+
<item name="qty" xsi:type="string">1</item>
20+
</item>
21+
</item>
22+
</item>
23+
</data>
24+
<data name="configData" xsi:type="string">cashondelivery</data>
25+
<data name="status" xsi:type="string">Processing</data>
26+
<constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelSuccessMessage" />
27+
<constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" />
28+
</variation>
29+
</testCase>
30+
</config>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Bundle\Test\TestStep;
8+
9+
use Magento\Mtf\TestStep\TestStepInterface;
10+
11+
/**
12+
* Create invoice from order on backend.
13+
*/
14+
class CreatePartialInvoiceStep extends \Magento\Sales\Test\TestStep\CreateInvoiceStep implements TestStepInterface
15+
{
16+
17+
/**
18+
* {@inheritdoc}
19+
*/
20+
protected function getItems()
21+
{
22+
$items = parent::getItems();
23+
return $items[0]->getData()['options'];
24+
}
25+
}

0 commit comments

Comments
 (0)