Skip to content

Commit 91182c7

Browse files
committed
[BUG#AC-2621] - Shipping costs not calculated correctly with table rates and virtual/physical products in cart
1 parent 2a774fc commit 91182c7

File tree

4 files changed

+211
-2
lines changed

4 files changed

+211
-2
lines changed

app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ public function collectRates(RateRequest $request)
111111
}
112112
} elseif ($item->getProduct()->isVirtual()) {
113113
$request->setPackageValue($request->getPackageValue() - $item->getBaseRowTotal());
114+
$request->setPackageValueWithDiscount(
115+
$request->getPackageValueWithDiscount() - $item->getBaseRowTotal()
116+
);
114117
}
115118
}
116119
}

dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use Magento\Customer\Api\Data\GroupInterface;
1212
use Magento\Customer\Api\GroupRepositoryInterface;
1313
use Magento\Customer\Model\Vat;
14-
use Magento\Customer\Observer\AfterAddressSaveObserver;
1514
use Magento\Framework\Api\SearchCriteriaBuilder;
1615
use Magento\Framework\App\Config\MutableScopeConfigInterface;
1716
use Magento\Framework\DataObject;
@@ -23,7 +22,6 @@
2322
use Magento\Quote\Api\GuestShippingMethodManagementInterface;
2423
use Magento\Quote\Api\ShippingMethodManagementInterface;
2524
use Magento\Quote\Observer\Frontend\Quote\Address\CollectTotalsObserver;
26-
use Magento\Quote\Observer\Frontend\Quote\Address\VatValidator;
2725
use Magento\Store\Model\ScopeInterface;
2826
use Magento\Tax\Api\Data\TaxClassInterface;
2927
use Magento\Tax\Api\TaxClassRepositoryInterface;
@@ -127,6 +125,47 @@ public function testTableRateFreeShipping()
127125
}
128126
}
129127

128+
/**
129+
* @magentoConfigFixture default_store carriers/tablerate/active 1
130+
* @magentoConfigFixture default_store carriers/flatrate/active 0
131+
* @magentoConfigFixture current_store carriers/tablerate/condition_name package_value_with_discount
132+
* @magentoConfigFixture default_store carriers/tablerate/include_virtual_price 0
133+
* @magentoDataFixture Magento/Sales/_files/quote_with_simple_and_virtual_product.php
134+
* @magentoDataFixture Magento/OfflineShipping/_files/tablerates_price.php
135+
* @return void
136+
*/
137+
public function testTableRateWithoutIncludingVirtualProduct()
138+
{
139+
$quote = $this->getQuote('quoteWithVirtualProduct');
140+
$cartId = $quote->getId();
141+
142+
if (!$cartId) {
143+
$this->fail('quote fixture failed');
144+
}
145+
146+
/** @var QuoteIdMask $quoteIdMask */
147+
$quoteIdMask = $this->objectManager
148+
->create(QuoteIdMaskFactory::class)
149+
->create()
150+
->load($cartId, 'quote_id');
151+
152+
/** @var GuestShippingMethodManagementInterface $shippingEstimation */
153+
$shippingEstimation = $this->objectManager->get(GuestShippingMethodManagementInterface::class);
154+
$result = $shippingEstimation->estimateByExtendedAddress(
155+
$quoteIdMask->getMaskedId(),
156+
$quote->getShippingAddress()
157+
);
158+
159+
$this->assertCount(1, $result);
160+
$rate = reset($result);
161+
$expectedResult = [
162+
'method_code' => 'bestway',
163+
'amount' => 15,
164+
];
165+
$this->assertEquals($expectedResult['method_code'], $rate->getMethodCode());
166+
$this->assertEquals($expectedResult['amount'], $rate->getAmount());
167+
}
168+
130169
/**
131170
* Test table rate amount for the cart that contains some items with free shipping applied.
132171
*
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\Product;
10+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
11+
use Magento\Catalog\Model\Product\Type;
12+
use Magento\Catalog\Model\Product\Visibility;
13+
use Magento\Quote\Api\CartRepositoryInterface;
14+
use Magento\Quote\Model\Quote;
15+
use Magento\Quote\Model\Quote\Address;
16+
use Magento\Quote\Model\QuoteIdMask;
17+
use Magento\Quote\Model\QuoteIdMaskFactory;
18+
use Magento\Store\Model\StoreManagerInterface;
19+
use Magento\TestFramework\Helper\Bootstrap;
20+
21+
Bootstrap::getInstance()->loadArea('frontend');
22+
$objectManager = Bootstrap::getObjectManager();
23+
$productRepository = $objectManager
24+
->create(ProductRepositoryInterface::class);
25+
$firstProduct = $objectManager->create(Product::class);
26+
$firstProduct->setTypeId(Type::TYPE_SIMPLE)
27+
->setCategoryIds([3])
28+
->setId(123)
29+
->setAttributeSetId(4)
30+
->setName('First Test Product For TableRate')
31+
->setSku('tableRate-1')
32+
->setPrice(40)
33+
->setTaxClassId(0)
34+
->setMetaTitle('meta title')
35+
->setMetaKeyword('meta keyword')
36+
->setMetaDescription('meta description')
37+
->setVisibility(Visibility::VISIBILITY_BOTH)
38+
->setStatus(Status::STATUS_ENABLED)
39+
->setStockData(
40+
[
41+
'qty' => 100,
42+
'is_in_stock' => 1,
43+
'manage_stock' => 1,
44+
]
45+
)
46+
->save();
47+
48+
/** @var ProductRepositoryInterface $productRepository */
49+
$firstProduct = $productRepository->save($firstProduct);
50+
51+
$secondProduct = $objectManager->create(Product::class);
52+
$secondProduct->setTypeId(Type::TYPE_VIRTUAL)
53+
->setCategoryIds([6])
54+
->setId(124)
55+
->setAttributeSetId(4)
56+
->setName('Second Test Product For TableRate')
57+
->setSku('tableRate-2')
58+
->setPrice(20)
59+
->setTaxClassId(0)
60+
->setMetaTitle('meta title')
61+
->setMetaKeyword('meta keyword')
62+
->setMetaDescription('meta description')
63+
->setVisibility(Visibility::VISIBILITY_BOTH)
64+
->setStatus(Status::STATUS_ENABLED)
65+
->setStockData(
66+
[
67+
'qty' => 100,
68+
'is_in_stock' => 1,
69+
'manage_stock' => 1,
70+
]
71+
)
72+
->save();
73+
74+
/** @var ProductRepositoryInterface $productRepository */
75+
$secondProduct = $productRepository->save($secondProduct);
76+
77+
$addressData = include __DIR__ . '/address_data.php';
78+
$billingAddress = $objectManager->create(
79+
Address::class,
80+
['data' => $addressData]
81+
);
82+
$billingAddress->setAddressType('billing');
83+
84+
$shippingAddress = $objectManager->create(
85+
Address::class,
86+
['data' => $addressData]
87+
);
88+
$shippingAddress->setAddressType('shipping');
89+
90+
$store = $objectManager
91+
->get(StoreManagerInterface::class)
92+
->getStore();
93+
94+
/** @var Quote $quote */
95+
$quote = $objectManager->create(Quote::class);
96+
$quote->setCustomerIsGuest(true)
97+
->setStoreId($store->getId())
98+
->setReservedOrderId('quoteWithVirtualProduct')
99+
->setBillingAddress($billingAddress)
100+
->setShippingAddress($shippingAddress);
101+
$quote->getPayment()->setMethod('checkmo');
102+
$quote->addProduct($firstProduct);
103+
$quote->addProduct($secondProduct);
104+
$quote->setIsMultiShipping(0);
105+
106+
$quoteRepository = $objectManager->get(CartRepositoryInterface::class);
107+
$quoteRepository->save($quote);
108+
109+
/** @var QuoteIdMask $quoteIdMask */
110+
$quoteIdMask = $objectManager
111+
->create(QuoteIdMaskFactory::class)
112+
->create();
113+
$quoteIdMask->setQuoteId($quote->getId())
114+
->setDataChanges(true)
115+
->save();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\CatalogInventory\Model\StockRegistryStorage;
10+
use Magento\Framework\Exception\NoSuchEntityException;
11+
use Magento\Framework\Registry;
12+
use Magento\Quote\Model\Quote;
13+
use Magento\TestFramework\Helper\Bootstrap;
14+
use Magento\TestFramework\ObjectManager;
15+
16+
/** @var $objectManager ObjectManager */
17+
$objectManager = Bootstrap::getObjectManager();
18+
19+
/** @var Registry $registry */
20+
$registry = $objectManager->get(Registry::class);
21+
$registry->unregister('isSecureArea');
22+
$registry->register('isSecureArea', true);
23+
24+
$quote = $objectManager->create(Quote::class);
25+
$quote->load('quoteWithVirtualProduct', 'reserved_order_id')->delete();
26+
27+
/**
28+
* @var ProductRepositoryInterface $productRepository
29+
*/
30+
$productRepository = Bootstrap::getObjectManager()
31+
->get(ProductRepositoryInterface::class);
32+
try {
33+
$product = $productRepository->get('tableRate-1', false, null, true);
34+
$productRepository->delete($product);
35+
} catch (NoSuchEntityException $e) {
36+
//Product already removed
37+
}
38+
39+
try {
40+
$customDesignProduct = $productRepository->get('tableRate-2', false, null, true);
41+
$productRepository->delete($customDesignProduct);
42+
} catch (NoSuchEntityException $e) {
43+
//Product already removed
44+
}
45+
46+
/** @var StockRegistryStorage $stockRegistryStorage */
47+
$stockRegistryStorage = Bootstrap::getObjectManager()
48+
->get(StockRegistryStorage::class);
49+
$stockRegistryStorage->clean();
50+
51+
$registry->unregister('isSecureArea');
52+
$registry->register('isSecureArea', false);

0 commit comments

Comments
 (0)