Skip to content

Commit 3ffccc0

Browse files
committed
Merge remote-tracking branch 'local/ACP2E-1650' into 11_APR_2023
2 parents ed7879c + 21c15d9 commit 3ffccc0

File tree

8 files changed

+576
-48
lines changed

8 files changed

+576
-48
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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+
namespace Magento\Bundle\Model\Sales\Order;
9+
10+
use Magento\Bundle\Model\Sales\Order\Shipment\BundleShipmentTypeValidator;
11+
use \Laminas\Validator\ValidatorInterface;
12+
use Magento\Catalog\Model\Product\Type;
13+
use Magento\Framework\Exception\NoSuchEntityException;
14+
use Magento\Framework\Phrase;
15+
use Magento\Framework\Webapi\Request;
16+
use Magento\Sales\Api\Data\ShipmentItemInterface;
17+
use Magento\Sales\Model\Order\Item;
18+
use Magento\Sales\Model\Order\Shipment;
19+
20+
/**
21+
* Validate if requested order items can be shipped according to bundle product shipment type
22+
*/
23+
class BundleOrderTypeValidator extends BundleShipmentTypeValidator implements ValidatorInterface
24+
{
25+
private const SHIPMENT_API_ROUTE = 'v1/shipment';
26+
27+
public const SHIPMENT_TYPE_TOGETHER = '0';
28+
29+
public const SHIPMENT_TYPE_SEPARATELY = '1';
30+
31+
/**
32+
* @var array
33+
*/
34+
private array $messages = [];
35+
36+
/**
37+
* @var Request
38+
*/
39+
private Request $request;
40+
41+
/**
42+
* @param Request $request
43+
*/
44+
public function __construct(Request $request)
45+
{
46+
$this->request = $request;
47+
}
48+
49+
/**
50+
* Validates shipment items based on order item properties
51+
*
52+
* @param Shipment $value
53+
* @return bool
54+
* @throws \Magento\Framework\Exception\NoSuchEntityException
55+
* @throws \Magento\Sales\Exception\DocumentValidationException
56+
*/
57+
public function isValid($value): bool
58+
{
59+
if (false === $this->validationNeeded()) {
60+
return true;
61+
}
62+
63+
$result = $shippingInfo = [];
64+
foreach ($value->getItems() as $shipmentItem) {
65+
$shippingInfo[$shipmentItem->getOrderItemId()] = [
66+
'shipment_info' => $shipmentItem,
67+
'order_info' => $value->getOrder()->getItemById($shipmentItem->getOrderItemId())
68+
];
69+
}
70+
71+
foreach ($shippingInfo as $shippingItemInfo) {
72+
if ($shippingItemInfo['order_info']->getProductType() === Type::TYPE_BUNDLE) {
73+
$result[] = $this->checkBundleItem($shippingItemInfo, $shippingInfo);
74+
} elseif ($shippingItemInfo['order_info']->getParentItem() &&
75+
$shippingItemInfo['order_info']->getParentItem()->getProductType() === Type::TYPE_BUNDLE
76+
) {
77+
$result[] = $this->checkChildItem($shippingItemInfo['order_info'], $shippingInfo);
78+
}
79+
$this->renderValidationMessages($result);
80+
}
81+
82+
return empty($this->messages);
83+
}
84+
85+
/**
86+
* Returns validation messages
87+
*
88+
* @return array|string[]
89+
*/
90+
public function getMessages(): array
91+
{
92+
return $this->messages;
93+
}
94+
95+
/**
96+
* Checks if shipment child item can be processed
97+
*
98+
* @param Item $orderItem
99+
* @param array $shipmentInfo
100+
* @return Phrase|null
101+
* @throws NoSuchEntityException
102+
*/
103+
private function checkChildItem(Item $orderItem, array $shipmentInfo): ?Phrase
104+
{
105+
$result = null;
106+
if ($orderItem->getParentItem()->getProductType() === Type::TYPE_BUNDLE &&
107+
$orderItem->getParentItem()->getProduct()->getShipmentType() === self::SHIPMENT_TYPE_TOGETHER) {
108+
$result = __(
109+
'Cannot create shipment as bundle product "%1" has shipment type "%2". ' .
110+
'%3 should be shipped instead.',
111+
$orderItem->getParentItem()->getSku(),
112+
__('Together'),
113+
__('Bundle product itself')
114+
);
115+
}
116+
117+
if ($orderItem->getParentItem()->getProductType() === Type::TYPE_BUNDLE &&
118+
$orderItem->getParentItem()->getProduct()->getShipmentType() === self::SHIPMENT_TYPE_SEPARATELY &&
119+
false === $this->hasParentInShipping($orderItem, $shipmentInfo)
120+
) {
121+
$result = __(
122+
'Cannot create shipment as bundle product %1 should be included as well.',
123+
$orderItem->getParentItem()->getSku()
124+
);
125+
}
126+
127+
return $result;
128+
}
129+
130+
/**
131+
* Checks if bundle item can be processed as a shipment item
132+
*
133+
* @param array $shippingItemInfo
134+
* @param array $shippingInfo
135+
* @return Phrase|null
136+
*/
137+
private function checkBundleItem(array $shippingItemInfo, array $shippingInfo): ?Phrase
138+
{
139+
$result = null;
140+
/** @var Item $orderItem */
141+
$orderItem = $shippingItemInfo['order_info'];
142+
/** @var ShipmentItemInterface $shipmentItem */
143+
$shipmentItem = $shippingItemInfo['shipment_info'];
144+
145+
if ($orderItem->getProduct()->getShipmentType() === self::SHIPMENT_TYPE_TOGETHER &&
146+
$this->hasChildrenInShipping($shipmentItem, $shippingInfo)
147+
) {
148+
$result = __(
149+
'Cannot create shipment as bundle product "%1" has shipment type "%2". ' .
150+
'%3 should be shipped instead.',
151+
$orderItem->getSku(),
152+
__('Together'),
153+
__('Bundle product itself')
154+
);
155+
}
156+
if ($orderItem->getProduct()->getShipmentType() === self::SHIPMENT_TYPE_SEPARATELY &&
157+
false === $this->hasChildrenInShipping($shipmentItem, $shippingInfo)
158+
) {
159+
$result = __(
160+
'Cannot create shipment as bundle product "%1" has shipment type "%2". ' .
161+
'Shipment should also incorporate bundle options.',
162+
$orderItem->getSku(),
163+
__('Separately')
164+
);
165+
}
166+
return $result;
167+
}
168+
169+
/**
170+
* Determines if a child shipment item has its corresponding parent in shipment
171+
*
172+
* @param Item $childItem
173+
* @param array $shipmentInfo
174+
* @return bool
175+
*/
176+
private function hasParentInShipping(Item $childItem, array $shipmentInfo): bool
177+
{
178+
/** @var Item $orderItem */
179+
foreach (array_column($shipmentInfo, 'order_info') as $orderItem) {
180+
if (!$orderItem->getParentItemId() &&
181+
$orderItem->getProductType() === Type::TYPE_BUNDLE &&
182+
$childItem->getParentItemId() == $orderItem->getItemId()
183+
) {
184+
return true;
185+
}
186+
}
187+
return false;
188+
}
189+
190+
/**
191+
* Determines if a bundle shipment item has at least one child in shipment
192+
*
193+
* @param ShipmentItemInterface $bundleItem
194+
* @param array $shippingInfo
195+
* @return bool
196+
*/
197+
private function hasChildrenInShipping(ShipmentItemInterface $bundleItem, array $shippingInfo): bool
198+
{
199+
/** @var Item $orderItem */
200+
foreach (array_column($shippingInfo, 'order_info') as $orderItem) {
201+
if ($orderItem->getParentItemId() &&
202+
$orderItem->getParentItemId() == $bundleItem->getOrderItemId()
203+
) {
204+
return true;
205+
}
206+
}
207+
return false;
208+
}
209+
210+
/**
211+
* Determines if the validation should be triggered or not
212+
*
213+
* @return bool
214+
*/
215+
private function validationNeeded(): bool
216+
{
217+
return str_contains(strtolower($this->request->getUri()->getPath()), self::SHIPMENT_API_ROUTE);
218+
}
219+
220+
/**
221+
* Creates text based validation messages
222+
*
223+
* @param array $validationMessages
224+
* @return void
225+
*/
226+
private function renderValidationMessages(array $validationMessages): void
227+
{
228+
foreach ($validationMessages as $message) {
229+
if ($message instanceof Phrase) {
230+
$this->messages[] = $message->render();
231+
}
232+
}
233+
$this->messages = array_unique($this->messages);
234+
}
235+
}

0 commit comments

Comments
 (0)