1 | <?php |
||||||
2 | |||||||
3 | /** |
||||||
4 | * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0 |
||||||
5 | * @copyright Metaways Infosystems GmbH, 2014 |
||||||
6 | * @copyright Aimeos (aimeos.org), 2015-2018 |
||||||
7 | * @package Controller |
||||||
8 | * @subpackage Common |
||||||
9 | */ |
||||||
10 | |||||||
11 | |||||||
12 | namespace Aimeos\Controller\Common\Order; |
||||||
13 | |||||||
14 | |||||||
15 | /** |
||||||
16 | * Common order controller methods. |
||||||
17 | * |
||||||
18 | * @package Controller |
||||||
19 | * @subpackage Common |
||||||
20 | */ |
||||||
21 | class Standard |
||||||
22 | implements \Aimeos\Controller\Common\Order\Iface |
||||||
23 | { |
||||||
24 | private $context; |
||||||
25 | |||||||
26 | |||||||
27 | /** |
||||||
28 | * Initializes the object. |
||||||
29 | * |
||||||
30 | * @param \Aimeos\MShop\Context\Item\Iface $context |
||||||
31 | */ |
||||||
32 | public function __construct( \Aimeos\MShop\Context\Item\Iface $context ) |
||||||
33 | { |
||||||
34 | $this->context = $context; |
||||||
35 | } |
||||||
36 | |||||||
37 | |||||||
38 | /** |
||||||
39 | * Blocks the resources listed in the order. |
||||||
40 | * |
||||||
41 | * Every order contains resources like products or redeemed coupon codes |
||||||
42 | * that must be blocked so they can't be used by another customer in a |
||||||
43 | * later order. This method reduces the the stock level of products, the |
||||||
44 | * counts of coupon codes and others. |
||||||
45 | * |
||||||
46 | * It's save to call this method multiple times for one order. In this case, |
||||||
47 | * the actions will be executed only once. All subsequent calls will do |
||||||
48 | * nothing as long as the resources haven't been unblocked in the meantime. |
||||||
49 | * |
||||||
50 | * You can also block and unblock resources several times. Please keep in |
||||||
51 | * mind that unblocked resources may be reused by other orders in the |
||||||
52 | * meantime. This can lead to an oversell of products! |
||||||
53 | * |
||||||
54 | * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object |
||||||
55 | * @return \Aimeos\MShop\Order\Item\Iface Order item object |
||||||
56 | */ |
||||||
57 | public function block( \Aimeos\MShop\Order\Item\Iface $orderItem ) |
||||||
58 | { |
||||||
59 | $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE, 1, -1 ); |
||||||
60 | $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE, 1, -1 ); |
||||||
61 | |||||||
62 | return $orderItem; |
||||||
63 | } |
||||||
64 | |||||||
65 | |||||||
66 | /** |
||||||
67 | * Frees the resources listed in the order. |
||||||
68 | * |
||||||
69 | * If customers created orders but didn't pay for them, the blocked resources |
||||||
70 | * like products and redeemed coupon codes must be unblocked so they can be |
||||||
71 | * ordered again or used by other customers. This method increased the stock |
||||||
72 | * level of products, the counts of coupon codes and others. |
||||||
73 | * |
||||||
74 | * It's save to call this method multiple times for one order. In this case, |
||||||
75 | * the actions will be executed only once. All subsequent calls will do |
||||||
76 | * nothing as long as the resources haven't been blocked in the meantime. |
||||||
77 | * |
||||||
78 | * You can also unblock and block resources several times. Please keep in |
||||||
79 | * mind that unblocked resources may be reused by other orders in the |
||||||
80 | * meantime. This can lead to an oversell of products! |
||||||
81 | * |
||||||
82 | * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object |
||||||
83 | * @return \Aimeos\MShop\Order\Item\Iface Order item object |
||||||
84 | */ |
||||||
85 | public function unblock( \Aimeos\MShop\Order\Item\Iface $orderItem ) |
||||||
86 | { |
||||||
87 | $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE, 0, +1 ); |
||||||
88 | $this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE, 0, +1 ); |
||||||
89 | |||||||
90 | return $orderItem; |
||||||
91 | } |
||||||
92 | |||||||
93 | |||||||
94 | /** |
||||||
95 | * Blocks or frees the resources listed in the order if necessary. |
||||||
96 | * |
||||||
97 | * After payment status updates, the resources like products or coupon |
||||||
98 | * codes listed in the order must be blocked or unblocked. This method |
||||||
99 | * cares about executing the appropriate action depending on the payment |
||||||
100 | * status. |
||||||
101 | * |
||||||
102 | * It's save to call this method multiple times for one order. In this case, |
||||||
103 | * the actions will be executed only once. All subsequent calls will do |
||||||
104 | * nothing as long as the payment status hasn't changed in the meantime. |
||||||
105 | * |
||||||
106 | * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object |
||||||
107 | * @return \Aimeos\MShop\Order\Item\Iface Order item object |
||||||
108 | */ |
||||||
109 | public function update( \Aimeos\MShop\Order\Item\Iface $orderItem ) |
||||||
110 | { |
||||||
111 | switch( $orderItem->getPaymentStatus() ) |
||||||
112 | { |
||||||
113 | case \Aimeos\MShop\Order\Item\Base::PAY_DELETED: |
||||||
114 | case \Aimeos\MShop\Order\Item\Base::PAY_CANCELED: |
||||||
115 | case \Aimeos\MShop\Order\Item\Base::PAY_REFUSED: |
||||||
116 | case \Aimeos\MShop\Order\Item\Base::PAY_REFUND: |
||||||
117 | $this->unblock( $orderItem ); |
||||||
118 | break; |
||||||
119 | |||||||
120 | case \Aimeos\MShop\Order\Item\Base::PAY_PENDING: |
||||||
121 | case \Aimeos\MShop\Order\Item\Base::PAY_AUTHORIZED: |
||||||
122 | case \Aimeos\MShop\Order\Item\Base::PAY_RECEIVED: |
||||||
123 | $this->block( $orderItem ); |
||||||
124 | break; |
||||||
125 | } |
||||||
126 | |||||||
127 | return $orderItem; |
||||||
128 | } |
||||||
129 | |||||||
130 | |||||||
131 | /** |
||||||
132 | * Adds a new status record to the order with the type and value. |
||||||
133 | * |
||||||
134 | * @param string $parentid Order ID |
||||||
135 | * @param string $type Status type |
||||||
136 | * @param string $value Status value |
||||||
137 | */ |
||||||
138 | protected function addStatusItem( $parentid, $type, $value ) |
||||||
139 | { |
||||||
140 | $manager = \Aimeos\MShop::create( $this->getContext(), 'order/status' ); |
||||||
141 | |||||||
142 | $item = $manager->createItem(); |
||||||
143 | $item->setParentId( $parentid ); |
||||||
0 ignored issues
–
show
|
|||||||
144 | $item->setType( $type ); |
||||||
145 | $item->setValue( $value ); |
||||||
0 ignored issues
–
show
The method
setValue() does not exist on Aimeos\MShop\Attribute\Item\Iface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
146 | |||||||
147 | $manager->saveItem( $item, false ); |
||||||
148 | } |
||||||
149 | |||||||
150 | |||||||
151 | /** |
||||||
152 | * Returns the product articles and their bundle product codes for the given article ID |
||||||
153 | * |
||||||
154 | * @param string $prodId Product ID of the article whose stock level changed |
||||||
155 | * @return array Associative list of article codes as keys and lists of bundle product codes as values |
||||||
156 | */ |
||||||
157 | protected function getBundleMap( $prodId ) |
||||||
158 | { |
||||||
159 | $bundleMap = []; |
||||||
160 | $productManager = \Aimeos\MShop::create( $this->context, 'product' ); |
||||||
161 | |||||||
162 | $search = $productManager->createSearch(); |
||||||
163 | $func = $search->createFunction( 'product:has', ['product', 'default', $prodId] ); |
||||||
164 | $expr = array( |
||||||
165 | $search->compare( '==', 'product.type', 'bundle' ), |
||||||
166 | $search->compare( '!=', $func, null ), |
||||||
167 | ); |
||||||
168 | $search->setConditions( $search->combine( '&&', $expr ) ); |
||||||
169 | $search->setSlice( 0, 0x7fffffff ); |
||||||
170 | |||||||
171 | $bundleItems = $productManager->searchItems( $search, array( 'product' ) ); |
||||||
172 | |||||||
173 | foreach( $bundleItems as $bundleItem ) |
||||||
174 | { |
||||||
175 | foreach( $bundleItem->getRefItems( 'product', null, 'default' ) as $item ) { |
||||||
176 | $bundleMap[ $item->getCode() ][] = $bundleItem->getCode(); |
||||||
177 | } |
||||||
178 | } |
||||||
179 | |||||||
180 | return $bundleMap; |
||||||
181 | } |
||||||
182 | |||||||
183 | |||||||
184 | /** |
||||||
185 | * Returns the context item object. |
||||||
186 | * |
||||||
187 | * @return \Aimeos\MShop\Context\Item\Iface Context item object |
||||||
188 | */ |
||||||
189 | protected function getContext() |
||||||
190 | { |
||||||
191 | return $this->context; |
||||||
192 | } |
||||||
193 | |||||||
194 | |||||||
195 | /** |
||||||
196 | * Returns the last status item for the given order ID. |
||||||
197 | * |
||||||
198 | * @param string $parentid Order ID |
||||||
199 | * @param string $type Status type constant |
||||||
200 | * @param string $status New status value stored along with the order item |
||||||
201 | * @return \Aimeos\MShop\Order\Item\Status\Iface|false Order status item or false if no item is available |
||||||
202 | */ |
||||||
203 | protected function getLastStatusItem( $parentid, $type, $status ) |
||||||
204 | { |
||||||
205 | $manager = \Aimeos\MShop::create( $this->getContext(), 'order/status' ); |
||||||
206 | |||||||
207 | $search = $manager->createSearch(); |
||||||
208 | $expr = array( |
||||||
209 | $search->compare( '==', 'order.status.parentid', $parentid ), |
||||||
210 | $search->compare( '==', 'order.status.type', $type ), |
||||||
211 | $search->compare( '==', 'order.status.value', $status ), |
||||||
212 | ); |
||||||
213 | $search->setConditions( $search->combine( '&&', $expr ) ); |
||||||
214 | $search->setSortations( array( $search->sort( '-', 'order.status.ctime' ) ) ); |
||||||
215 | $search->setSlice( 0, 1 ); |
||||||
216 | |||||||
217 | $result = $manager->searchItems( $search ); |
||||||
218 | |||||||
219 | return reset( $result ); |
||||||
220 | } |
||||||
221 | |||||||
222 | |||||||
223 | /** |
||||||
224 | * Returns the stock items for the given product codes |
||||||
225 | * |
||||||
226 | * @param array $prodCodes List of product codes |
||||||
227 | * @param string $stockType Stock type code the stock items must belong to |
||||||
228 | * @return \Aimeos\MShop\Product\Item\Stock\Iface[] Associative list of stock IDs as keys and stock items as values |
||||||
229 | */ |
||||||
230 | protected function getStockItems( array $prodCodes, $stockType ) |
||||||
231 | { |
||||||
232 | $stockManager = \Aimeos\MShop::create( $this->context, 'stock' ); |
||||||
233 | |||||||
234 | $search = $stockManager->createSearch(); |
||||||
235 | $expr = array( |
||||||
236 | $search->compare( '==', 'stock.productcode', $prodCodes ), |
||||||
237 | $search->compare( '==', 'stock.type', $stockType ), |
||||||
238 | ); |
||||||
239 | $search->setConditions( $search->combine( '&&', $expr ) ); |
||||||
240 | $search->setSlice( 0, count( $prodCodes ) ); |
||||||
241 | |||||||
242 | return $stockManager->searchItems( $search ); |
||||||
243 | } |
||||||
244 | |||||||
245 | |||||||
246 | /** |
||||||
247 | * Increases or decreses the coupon code counts referenced in the order by the given value. |
||||||
248 | * |
||||||
249 | * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object |
||||||
250 | * @param integer $how Positive or negative integer number for increasing or decreasing the coupon count |
||||||
251 | */ |
||||||
252 | protected function updateCoupons( \Aimeos\MShop\Order\Item\Iface $orderItem, $how = +1 ) |
||||||
253 | { |
||||||
254 | $context = $this->getContext(); |
||||||
255 | $manager = \Aimeos\MShop::create( $context, 'order/base/coupon' ); |
||||||
256 | $couponCodeManager = \Aimeos\MShop::create( $context, 'coupon/code' ); |
||||||
257 | |||||||
258 | $search = $manager->createSearch(); |
||||||
259 | $search->setConditions( $search->compare( '==', 'order.base.coupon.baseid', $orderItem->getBaseId() ) ); |
||||||
260 | |||||||
261 | $start = 0; |
||||||
262 | |||||||
263 | $couponCodeManager->begin(); |
||||||
264 | |||||||
265 | try |
||||||
266 | { |
||||||
267 | do |
||||||
268 | { |
||||||
269 | $items = $manager->searchItems( $search ); |
||||||
270 | |||||||
271 | foreach( $items as $item ) { |
||||||
272 | $couponCodeManager->decrease( $item->getCode(), $how * -1 ); |
||||||
273 | } |
||||||
274 | |||||||
275 | $count = count( $items ); |
||||||
276 | $start += $count; |
||||||
277 | $search->setSlice( $start ); |
||||||
278 | } |
||||||
279 | while( $count >= $search->getSliceSize() ); |
||||||
280 | |||||||
281 | $couponCodeManager->commit(); |
||||||
282 | } |
||||||
283 | catch( \Exception $e ) |
||||||
284 | { |
||||||
285 | $couponCodeManager->rollback(); |
||||||
286 | throw $e; |
||||||
287 | } |
||||||
288 | } |
||||||
289 | |||||||
290 | |||||||
291 | /** |
||||||
292 | * Increases or decreases the stock level or the coupon code count for referenced items of the given order. |
||||||
293 | * |
||||||
294 | * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object |
||||||
295 | * @param string $type Constant from \Aimeos\MShop\Order\Item\Status\Base, e.g. STOCK_UPDATE or COUPON_UPDATE |
||||||
296 | * @param string $status New status value stored along with the order item |
||||||
297 | * @param integer $value Number to increse or decrease the stock level or coupon code count |
||||||
298 | */ |
||||||
299 | protected function updateStatus( \Aimeos\MShop\Order\Item\Iface $orderItem, $type, $status, $value ) |
||||||
300 | { |
||||||
301 | $statusItem = $this->getLastStatusItem( $orderItem->getId(), $type, $status ); |
||||||
302 | |||||||
303 | if( $statusItem !== false && $statusItem->getValue() == $status ) { |
||||||
304 | return; |
||||||
305 | } |
||||||
306 | |||||||
307 | if( $type == \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE ) { |
||||||
308 | $this->updateStock( $orderItem, $value ); |
||||||
309 | } elseif( $type == \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE ) { |
||||||
310 | $this->updateCoupons( $orderItem, $value ); |
||||||
311 | } |
||||||
312 | |||||||
313 | $this->addStatusItem( $orderItem->getId(), $type, $status ); |
||||||
314 | } |
||||||
315 | |||||||
316 | |||||||
317 | /** |
||||||
318 | * Increases or decreses the stock levels of the products referenced in the order by the given value. |
||||||
319 | * |
||||||
320 | * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object |
||||||
321 | * @param integer $how Positive or negative integer number for increasing or decreasing the stock levels |
||||||
322 | */ |
||||||
323 | protected function updateStock( \Aimeos\MShop\Order\Item\Iface $orderItem, $how = +1 ) |
||||||
324 | { |
||||||
325 | $context = $this->getContext(); |
||||||
326 | $stockManager = \Aimeos\MShop::create( $context, 'stock' ); |
||||||
327 | $manager = \Aimeos\MShop::create( $context, 'order/base/product' ); |
||||||
328 | |||||||
329 | $search = $manager->createSearch(); |
||||||
330 | $search->setConditions( $search->compare( '==', 'order.base.product.baseid', $orderItem->getBaseId() ) ); |
||||||
331 | |||||||
332 | $start = 0; |
||||||
333 | |||||||
334 | $stockManager->begin(); |
||||||
335 | |||||||
336 | try |
||||||
337 | { |
||||||
338 | do |
||||||
339 | { |
||||||
340 | $items = $manager->searchItems( $search ); |
||||||
341 | |||||||
342 | foreach( $items as $item ) |
||||||
343 | { |
||||||
344 | $stockManager->decrease( [$item->getProductCode() => $how * -1 * $item->getQuantity()], $item->getStockType() ); |
||||||
345 | |||||||
346 | switch( $item->getType() ) { |
||||||
347 | case 'default': |
||||||
348 | $this->updateStockBundle( $item->getProductId(), $item->getStockType() ); break; |
||||||
349 | case 'select': |
||||||
350 | $this->updateStockSelection( $item->getProductId(), $item->getStockType() ); break; |
||||||
351 | } |
||||||
352 | } |
||||||
353 | |||||||
354 | $count = count( $items ); |
||||||
355 | $start += $count; |
||||||
356 | $search->setSlice( $start ); |
||||||
357 | } |
||||||
358 | while( $count >= $search->getSliceSize() ); |
||||||
359 | |||||||
360 | $stockManager->commit(); |
||||||
361 | } |
||||||
362 | catch( \Exception $e ) |
||||||
363 | { |
||||||
364 | $stockManager->rollback(); |
||||||
365 | throw $e; |
||||||
366 | } |
||||||
367 | } |
||||||
368 | |||||||
369 | |||||||
370 | /** |
||||||
371 | * Updates the stock levels of bundles for a specific type |
||||||
372 | * |
||||||
373 | * @param string $prodId Unique product ID |
||||||
374 | * @param string $stockType Unique stock type |
||||||
375 | */ |
||||||
376 | protected function updateStockBundle( $prodId, $stockType ) |
||||||
377 | { |
||||||
378 | if( ( $bundleMap = $this->getBundleMap( $prodId ) ) === [] ) { |
||||||
379 | return; |
||||||
380 | } |
||||||
381 | |||||||
382 | |||||||
383 | $bundleCodes = $stock = []; |
||||||
384 | |||||||
385 | foreach( $this->getStockItems( array_keys( $bundleMap ), $stockType ) as $stockItem ) |
||||||
386 | { |
||||||
387 | if( isset( $bundleMap[$stockItem->getProductCode()] ) && $stockItem->getStockLevel() !== null ) |
||||||
388 | { |
||||||
389 | foreach( $bundleMap[$stockItem->getProductCode()] as $bundleCode ) |
||||||
390 | { |
||||||
391 | if( isset( $stock[$bundleCode] ) ) { |
||||||
392 | $stock[$bundleCode] = min( $stock[$bundleCode], $stockItem->getStockLevel() ); |
||||||
393 | } else { |
||||||
394 | $stock[$bundleCode] = $stockItem->getStockLevel(); |
||||||
395 | } |
||||||
396 | |||||||
397 | $bundleCodes[$bundleCode] = null; |
||||||
398 | } |
||||||
399 | } |
||||||
400 | } |
||||||
401 | |||||||
402 | if( empty( $stock ) ) { |
||||||
403 | return; |
||||||
404 | } |
||||||
405 | |||||||
406 | $stockManager = \Aimeos\MShop::create( $this->context, 'stock' ); |
||||||
407 | |||||||
408 | foreach( $this->getStockItems( array_keys( $bundleCodes ), $stockType ) as $item ) |
||||||
409 | { |
||||||
410 | if( isset( $stock[$item->getProductCode()] ) ) |
||||||
411 | { |
||||||
412 | $item->setStockLevel( $stock[$item->getProductCode()] ); |
||||||
413 | $stockManager->saveItem( $item ); |
||||||
414 | } |
||||||
415 | } |
||||||
416 | } |
||||||
417 | |||||||
418 | |||||||
419 | /** |
||||||
420 | * Updates the stock levels of selection products for a specific type |
||||||
421 | * |
||||||
422 | * @param string $prodId Unique product ID |
||||||
423 | * @param string $stocktype Unique stock type |
||||||
424 | */ |
||||||
425 | protected function updateStockSelection( $prodId, $stocktype ) |
||||||
426 | { |
||||||
427 | $productManager = \Aimeos\MShop::create( $this->context, 'product' ); |
||||||
428 | $stockManager = \Aimeos\MShop::create( $this->context, 'stock' ); |
||||||
429 | |||||||
430 | $productItem = $productManager->getItem( $prodId, array( 'product' ) ); |
||||||
431 | $prodCodes = array( $productItem->getCode() ); |
||||||
432 | $sum = 0; $selStockItem = null; |
||||||
433 | |||||||
434 | foreach( $productItem->getRefItems( 'product', 'default', 'default' ) as $product ) { |
||||||
435 | $prodCodes[] = $product->getCode(); |
||||||
436 | } |
||||||
437 | |||||||
438 | foreach( $this->getStockItems( $prodCodes, $stocktype ) as $stockItem ) |
||||||
439 | { |
||||||
440 | if( $stockItem->getProductCode() === $productItem->getCode() ) |
||||||
441 | { |
||||||
442 | $selStockItem = $stockItem; |
||||||
443 | continue; |
||||||
444 | } |
||||||
445 | |||||||
446 | if( ( $stock = $stockItem->getStockLevel() ) === null ) { |
||||||
447 | $sum = null; |
||||||
448 | } |
||||||
449 | |||||||
450 | if( $sum !== null ) { |
||||||
451 | $sum += $stock; |
||||||
452 | } |
||||||
453 | } |
||||||
454 | |||||||
455 | if( $selStockItem === null ) |
||||||
456 | { |
||||||
457 | $selStockItem = $stockManager->createItem(); |
||||||
458 | $selStockItem->setProductCode( $productItem->getCode() ); |
||||||
0 ignored issues
–
show
The method
setProductCode() does not exist on Aimeos\MShop\Attribute\Item\Iface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
459 | $selStockItem->setType( $stocktype ); |
||||||
460 | } |
||||||
461 | |||||||
462 | $selStockItem->setStockLevel( $sum ); |
||||||
0 ignored issues
–
show
The method
setStockLevel() does not exist on Aimeos\MShop\Attribute\Item\Iface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
463 | $stockManager->saveItem( $selStockItem, false ); |
||||||
464 | } |
||||||
465 | } |
||||||
466 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.