99
1010use Magento \Bundle \Model \Sales \Order \Shipment \BundleShipmentTypeValidator ;
1111use \Laminas \Validator \ValidatorInterface ;
12+ use Magento \Catalog \Model \Product \Type ;
13+ use Magento \Framework \Exception \NoSuchEntityException ;
1214use Magento \Framework \Phrase ;
1315use Magento \Framework \Webapi \Request ;
16+ use Magento \Sales \Api \Data \ShipmentItemInterface ;
17+ use Magento \Sales \Model \Order \Item ;
1418use Magento \Sales \Model \Order \Shipment ;
1519
1620/**
@@ -20,6 +24,10 @@ class BundleOrderTypeValidator extends BundleShipmentTypeValidator implements Va
2024{
2125 private const SHIPMENT_API_ROUTE = 'v1/shipment ' ;
2226
27+ public const SHIPMENT_TYPE_TOGETHER = '0 ' ;
28+
29+ public const SHIPMENT_TYPE_SEPARATELY = '1 ' ;
30+
2331 /**
2432 * @var array
2533 */
@@ -52,14 +60,23 @@ public function isValid($value): bool
5260 return true ;
5361 }
5462
55- foreach ($ value ->getOrder ()->getAllItems () as $ orderItem ) {
56- foreach ($ value ->getItems () as $ shipmentItem ) {
57- if ($ orderItem ->getItemId () == $ shipmentItem ->getOrderItemId ()) {
58- if ($ validationMessages = $ this ->validate ($ orderItem )) {
59- $ this ->renderValidationMessages ($ validationMessages );
60- }
61- }
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 );
6278 }
79+ $ this ->renderValidationMessages ($ result );
6380 }
6481
6582 return empty ($ this ->messages );
@@ -75,6 +92,118 @@ public function getMessages(): array
7592 return $ this ->messages ;
7693 }
7794
95+ /**
96+ * @param Item $orderItem
97+ * @param array $shipmentInfo
98+ * @return Phrase|null
99+ * @throws NoSuchEntityException
100+ */
101+ private function checkChildItem (Item $ orderItem , array $ shipmentInfo ): ?Phrase
102+ {
103+ $ result = null ;
104+ if ($ orderItem ->getParentItem ()->getProductType () === Type::TYPE_BUNDLE &&
105+ $ orderItem ->getParentItem ()->getProduct ()->getShipmentType () === self ::SHIPMENT_TYPE_TOGETHER ) {
106+ $ result = __ (
107+ 'Cannot create shipment as bundle product "%1" has shipment type "%2". ' .
108+ '%3 should be shipped instead. ' ,
109+ $ orderItem ->getParentItem ()->getSku (),
110+ __ ('Together ' ),
111+ __ ('Bundle product itself ' ),
112+ );
113+ }
114+
115+ if ($ orderItem ->getParentItem ()->getProductType () === Type::TYPE_BUNDLE &&
116+ $ orderItem ->getParentItem ()->getProduct ()->getShipmentType () === self ::SHIPMENT_TYPE_SEPARATELY &&
117+ false === $ this ->hasParentInShipping ($ orderItem , $ shipmentInfo )
118+ ) {
119+ $ result = __ (
120+ 'Cannot create shipment as bundle product %1 should be included as well. ' ,
121+ $ orderItem ->getParentItem ()->getSku ()
122+ );
123+ }
124+
125+ return $ result ;
126+ }
127+
128+ /**
129+ * @param array $shippingItemInfo
130+ * @param array $shippingInfo
131+ * @return Phrase|null
132+ */
133+ private function checkBundleItem (array $ shippingItemInfo , array $ shippingInfo ): ?Phrase
134+ {
135+ $ result = null ;
136+ /** @var Item $orderItem */
137+ $ orderItem = $ shippingItemInfo ['order_info ' ];
138+ /** @var ShipmentItemInterface $shipmentItem */
139+ $ shipmentItem = $ shippingItemInfo ['shipment_info ' ];
140+
141+ if ($ orderItem ->getProduct ()->getShipmentType () === self ::SHIPMENT_TYPE_TOGETHER &&
142+ $ this ->hasChildrenInShipping ($ shipmentItem , $ shippingInfo )
143+ ) {
144+ $ result = __ (
145+ 'Cannot create shipment as bundle product "%1" has shipment type "%2". ' .
146+ '%3 should be shipped instead. ' ,
147+ $ orderItem ->getSku (),
148+ __ ('Together ' ),
149+ __ ('Bundle product itself ' ),
150+ );
151+ }
152+ if ($ orderItem ->getProduct ()->getShipmentType () === self ::SHIPMENT_TYPE_SEPARATELY &&
153+ false === $ this ->hasChildrenInShipping ($ shipmentItem , $ shippingInfo )
154+ ) {
155+ $ result = __ (
156+ 'Cannot create shipment as bundle product "%1" has shipment type "%2". ' .
157+ '%3. ' ,
158+ $ orderItem ->getSku (),
159+ __ ('Separately ' ),
160+ __ ('Shipment should also incorporate bundle options ' ),
161+ );
162+ }
163+ return $ result ;
164+ }
165+
166+ /**
167+ * Determines if a child shipment item has its corresponding parent in shipment
168+ *
169+ * @param Item $childItem
170+ * @param array $shipmentInfo
171+ * @return bool
172+ */
173+ private function hasParentInShipping (Item $ childItem , array $ shipmentInfo ): bool
174+ {
175+ /** @var Item $orderItem */
176+ foreach (array_column ($ shipmentInfo , 'order_info ' ) as $ orderItem ) {
177+ if (!$ orderItem ->getParentItemId () &&
178+ $ orderItem ->getProductType () === Type::TYPE_BUNDLE &&
179+ $ childItem ->getParentItemId () == $ orderItem ->getItemId ()
180+ ) {
181+ return true ;
182+ }
183+ }
184+ return false ;
185+ }
186+
187+ /**
188+ * Determines if a bundle shipment item has at least one child in shipment
189+ *
190+ * @param ShipmentItemInterface $bundleItem
191+ * @param array $shippingInfo
192+ * @return bool
193+ */
194+ private function hasChildrenInShipping (ShipmentItemInterface $ bundleItem , array $ shippingInfo ): bool
195+ {
196+ /** @var Item $orderItem */
197+ foreach (array_column ($ shippingInfo , 'order_info ' ) as $ orderItem ) {
198+ if ($ orderItem ->getParentItemId () &&
199+ $ orderItem ->getParentItemId () == $ bundleItem ->getOrderItemId ()
200+ ) {
201+ return true ;
202+ }
203+ }
204+ return false ;
205+ }
206+
78207 /**
79208 * Determines if the validation should be triggered or not
80209 *
@@ -98,5 +227,6 @@ private function renderValidationMessages(array $validationMessages): void
98227 $ this ->messages [] = $ message ->render ();
99228 }
100229 }
230+ $ this ->messages = array_unique ($ this ->messages );
101231 }
102232}
0 commit comments