Passed
Push — master ( 29d5f6...586595 )
by Aimeos
04:48
created

common/src/Controller/Common/Order/Standard.php (1 issue)

Labels
Severity
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
	 */
56
	public function block( \Aimeos\MShop\Order\Item\Iface $orderItem )
57
	{
58
		$this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE, 1, -1 );
59
		$this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE, 1, -1 );
60
	}
61
62
63
	/**
64
	 * Frees the resources listed in the order.
65
	 *
66
	 * If customers created orders but didn't pay for them, the blocked resources
67
	 * like products and redeemed coupon codes must be unblocked so they can be
68
	 * ordered again or used by other customers. This method increased the stock
69
	 * level of products, the counts of coupon codes and others.
70
	 *
71
	 * It's save to call this method multiple times for one order. In this case,
72
	 * the actions will be executed only once. All subsequent calls will do
73
	 * nothing as long as the resources haven't been blocked in the meantime.
74
	 *
75
	 * You can also unblock and block resources several times. Please keep in
76
	 * mind that unblocked resources may be reused by other orders in the
77
	 * meantime. This can lead to an oversell of products!
78
	 *
79
	 * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
80
	 */
81
	public function unblock( \Aimeos\MShop\Order\Item\Iface $orderItem )
82
	{
83
		$this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE, 0, +1 );
84
		$this->updateStatus( $orderItem, \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE, 0, +1 );
85
	}
86
87
88
	/**
89
	 * Blocks or frees the resources listed in the order if necessary.
90
	 *
91
	 * After payment status updates, the resources like products or coupon
92
	 * codes listed in the order must be blocked or unblocked. This method
93
	 * cares about executing the appropriate action depending on the payment
94
	 * status.
95
	 *
96
	 * It's save to call this method multiple times for one order. In this case,
97
	 * the actions will be executed only once. All subsequent calls will do
98
	 * nothing as long as the payment status hasn't changed in the meantime.
99
	 *
100
	 * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
101
	 */
102
	public function update( \Aimeos\MShop\Order\Item\Iface $orderItem )
103
	{
104
		switch( $orderItem->getPaymentStatus() )
105
		{
106
			case \Aimeos\MShop\Order\Item\Base::PAY_DELETED:
107
			case \Aimeos\MShop\Order\Item\Base::PAY_CANCELED:
108
			case \Aimeos\MShop\Order\Item\Base::PAY_REFUSED:
109
			case \Aimeos\MShop\Order\Item\Base::PAY_REFUND:
110
				$this->unblock( $orderItem );
111
				break;
112
113
			case \Aimeos\MShop\Order\Item\Base::PAY_PENDING:
114
			case \Aimeos\MShop\Order\Item\Base::PAY_AUTHORIZED:
115
			case \Aimeos\MShop\Order\Item\Base::PAY_RECEIVED:
116
				$this->block( $orderItem );
117
				break;
118
		}
119
	}
120
121
122
	/**
123
	 * Adds a new status record to the order with the type and value.
124
	 *
125
	 * @param string $parentid Order ID
126
	 * @param string $type Status type
127
	 * @param string $value Status value
128
	 */
129
	protected function addStatusItem( $parentid, $type, $value )
130
	{
131
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/status' );
132
133
		$item = $manager->createItem();
134
		$item->setParentId( $parentid );
135
		$item->setType( $type );
136
		$item->setValue( $value );
137
138
		$manager->saveItem( $item, false );
139
	}
140
141
142
	/**
143
	 * Returns the product articles and their bundle product codes for the given article ID
144
	 *
145
	 * @param string $prodId Product ID of the article whose stock level changed
146
	 * @return array Associative list of article codes as keys and lists of bundle product codes as values
147
	 */
148
	protected function getBundleMap( $prodId )
149
	{
150
		$bundleMap = [];
151
		$productManager = \Aimeos\MShop\Factory::createManager( $this->context, 'product' );
152
153
		$search = $productManager->createSearch();
154
		$func = $search->createFunction( 'product:has', ['product', 'default', $prodId] );
155
		$expr = array(
156
			$search->compare( '==', 'product.type.code', 'bundle' ),
157
			$search->compare( '!=', $func, null ),
158
		);
159
		$search->setConditions( $search->combine( '&&', $expr ) );
160
		$search->setSlice( 0, 0x7fffffff );
161
162
		$bundleItems = $productManager->searchItems( $search, array( 'product' ) );
163
164
		foreach( $bundleItems as $bundleItem )
165
		{
166
			foreach( $bundleItem->getRefItems( 'product', null, 'default' ) as $item ) {
167
				$bundleMap[ $item->getCode() ][] = $bundleItem->getCode();
168
			}
169
		}
170
171
		return $bundleMap;
172
	}
173
174
175
	/**
176
	 * Returns the context item object.
177
	 *
178
	 * @return \Aimeos\MShop\Context\Item\Iface Context item object
179
	 */
180
	protected function getContext()
181
	{
182
		return $this->context;
183
	}
184
185
186
	/**
187
	 * Returns the last status item for the given order ID.
188
	 *
189
	 * @param string $parentid Order ID
190
	 * @param string $type Status type constant
191
	 * @param string $status New status value stored along with the order item
192
	 * @return \Aimeos\MShop\Order\Item\Status\Iface|false Order status item or false if no item is available
193
	 */
194
	protected function getLastStatusItem( $parentid, $type, $status )
195
	{
196
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/status' );
197
198
		$search = $manager->createSearch();
199
		$expr = array(
200
			$search->compare( '==', 'order.status.parentid', $parentid ),
201
			$search->compare( '==', 'order.status.type', $type ),
202
			$search->compare( '==', 'order.status.value', $status ),
203
		);
204
		$search->setConditions( $search->combine( '&&', $expr ) );
205
		$search->setSortations( array( $search->sort( '-', 'order.status.ctime' ) ) );
206
		$search->setSlice( 0, 1 );
207
208
		$result = $manager->searchItems( $search );
209
210
		return reset( $result );
211
	}
212
213
214
	/**
215
	 * Returns the stock items for the given product codes
216
	 *
217
	 * @param array $prodCodes List of product codes
218
	 * @param string $stockType Stock type code the stock items must belong to
219
	 * @return \Aimeos\MShop\Product\Item\Stock\Iface[] Associative list of stock IDs as keys and stock items as values
220
	 */
221
	protected function getStockItems( array $prodCodes, $stockType )
222
	{
223
		$stockManager = \Aimeos\MShop\Factory::createManager( $this->context, 'stock' );
224
225
		$search = $stockManager->createSearch();
226
		$expr = array(
227
			$search->compare( '==', 'stock.productcode', $prodCodes ),
228
			$search->compare( '==', 'stock.type.code', $stockType ),
229
		);
230
		$search->setConditions( $search->combine( '&&', $expr ) );
231
		$search->setSlice( 0, 0x7fffffff );
232
233
		return $stockManager->searchItems( $search );
234
	}
235
236
237
	/**
238
	 * Increases or decreses the coupon code counts referenced in the order by the given value.
239
	 *
240
	 * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
241
	 * @param integer $how Positive or negative integer number for increasing or decreasing the coupon count
242
	 */
243
	protected function updateCoupons( \Aimeos\MShop\Order\Item\Iface $orderItem, $how = +1 )
244
	{
245
		$context = $this->getContext();
246
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/coupon' );
247
		$couponCodeManager = \Aimeos\MShop\Factory::createManager( $context, 'coupon/code' );
248
249
		$search = $manager->createSearch();
250
		$search->setConditions( $search->compare( '==', 'order.base.coupon.baseid', $orderItem->getBaseId() ) );
251
252
		$start = 0;
253
254
		$couponCodeManager->begin();
255
256
		try
257
		{
258
			do
259
			{
260
				$items = $manager->searchItems( $search );
261
262
				foreach( $items as $item ) {
263
					$couponCodeManager->increase( $item->getCode(), $how * 1 );
264
				}
265
266
				$count = count( $items );
267
				$start += $count;
268
				$search->setSlice( $start );
269
			}
270
			while( $count >= $search->getSliceSize() );
271
272
			$couponCodeManager->commit();
273
		}
274
		catch( \Exception $e )
275
		{
276
			$couponCodeManager->rollback();
277
			throw $e;
278
		}
279
	}
280
281
282
	/**
283
	 * Increases or decreases the stock level or the coupon code count for referenced items of the given order.
284
	 *
285
	 * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
286
	 * @param string $type Constant from \Aimeos\MShop\Order\Item\Status\Base, e.g. STOCK_UPDATE or COUPON_UPDATE
287
	 * @param string $status New status value stored along with the order item
288
	 * @param integer $value Number to increse or decrease the stock level or coupon code count
289
	 */
290
	protected function updateStatus( \Aimeos\MShop\Order\Item\Iface $orderItem, $type, $status, $value )
291
	{
292
		$statusItem = $this->getLastStatusItem( $orderItem->getId(), $type, $status );
293
294
		if( $statusItem !== false && $statusItem->getValue() == $status ) {
295
			return;
296
		}
297
298
		if( $type == \Aimeos\MShop\Order\Item\Status\Base::STOCK_UPDATE ) {
299
			$this->updateStock( $orderItem, $value );
300
		} elseif( $type == \Aimeos\MShop\Order\Item\Status\Base::COUPON_UPDATE ) {
301
			$this->updateCoupons( $orderItem, $value );
302
		}
303
304
		$this->addStatusItem( $orderItem->getId(), $type, $status );
305
	}
306
307
308
	/**
309
	 * Increases or decreses the stock levels of the products referenced in the order by the given value.
310
	 *
311
	 * @param \Aimeos\MShop\Order\Item\Iface $orderItem Order item object
312
	 * @param integer $how Positive or negative integer number for increasing or decreasing the stock levels
313
	 */
314
	protected function updateStock( \Aimeos\MShop\Order\Item\Iface $orderItem, $how = +1 )
315
	{
316
		$context = $this->getContext();
317
		$stockManager = \Aimeos\MShop\Factory::createManager( $context, 'stock' );
318
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' );
319
320
		$search = $manager->createSearch();
321
		$search->setConditions( $search->compare( '==', 'order.base.product.baseid', $orderItem->getBaseId() ) );
322
323
		$start = 0;
324
325
		$stockManager->begin();
326
327
		try
328
		{
329
			do
330
			{
331
				$items = $manager->searchItems( $search );
332
333
				foreach( $items as $item )
334
				{
335
					$stockManager->increase( $item->getProductCode(), $item->getStockType(), $how * $item->getQuantity() );
336
337
					switch( $item->getType() ) {
338
						case 'default':
339
							$this->updateStockBundle( $item->getProductId(), $item->getStockType() ); break;
340
						case 'select':
341
							$this->updateStockSelection( $item->getProductId(), $item->getStockType() ); break;
342
					}
343
				}
344
345
				$count = count( $items );
346
				$start += $count;
347
				$search->setSlice( $start );
348
			}
349
			while( $count >= $search->getSliceSize() );
350
351
			$stockManager->commit();
352
		}
353
		catch( \Exception $e )
354
		{
355
			$stockManager->rollback();
356
			throw $e;
357
		}
358
	}
359
360
361
	/**
362
	 * Updates the stock levels of bundles for a specific type
363
	 *
364
	 * @param string $prodId Unique product ID
365
	 * @param string $stockType Unique stock type
366
	 */
367
	protected function updateStockBundle( $prodId, $stockType )
368
	{
369
		if( ( $bundleMap = $this->getBundleMap( $prodId ) ) === [] ) {
370
			return;
371
		}
372
373
374
		$bundleCodes = $stock = [];
375
376
		foreach( $this->getStockItems( array_keys( $bundleMap ), $stockType ) as $stockItem )
377
		{
378
			if( isset( $bundleMap[$stockItem->getProductCode()] ) && $stockItem->getStockLevel() !== null )
379
			{
380
				foreach( $bundleMap[$stockItem->getProductCode()] as $bundleCode )
381
				{
382
					if( isset( $stock[$bundleCode] ) ) {
383
						$stock[$bundleCode] = min( $stock[$bundleCode], $stockItem->getStockLevel() );
384
					} else {
385
						$stock[$bundleCode] = $stockItem->getStockLevel();
386
					}
387
388
					$bundleCodes[$bundleCode] = null;
389
				}
390
			}
391
		}
392
393
		if( empty( $stock ) ) {
394
			return;
395
		}
396
397
		$stockManager = \Aimeos\MShop\Factory::createManager( $this->context, 'stock' );
398
399
		foreach( $this->getStockItems( array_keys( $bundleCodes ), $stockType ) as $item )
400
		{
401
			if( isset( $stock[$item->getProductCode()] ) )
402
			{
403
				$item->setStockLevel( $stock[$item->getProductCode()] );
404
				$stockManager->saveItem( $item );
405
			}
406
		}
407
	}
408
409
410
	/**
411
	 * Updates the stock levels of selection products for a specific type
412
	 *
413
	 * @param string $prodId Unique product ID
414
	 * @param string $stocktype Unique stock type
415
	 */
416
	protected function updateStockSelection( $prodId, $stocktype )
417
	{
418
		$productManager = \Aimeos\MShop\Factory::createManager( $this->context, 'product' );
419
		$stockManager = \Aimeos\MShop\Factory::createManager( $this->context, 'stock' );
420
421
		$productItem = $productManager->getItem( $prodId, array( 'product' ) );
422
		$prodCodes = array( $productItem->getCode() );
423
		$sum = 0; $selStockItem = null;
424
425
		foreach( $productItem->getRefItems( 'product', 'default', 'default' ) as $product ) {
1 ignored issue
show
The method getRefItems() does not exist on Aimeos\MShop\Common\Item\Iface. It seems like you code against a sub-type of Aimeos\MShop\Common\Item\Iface such as Aimeos\MShop\Product\Item\Iface or Aimeos\MShop\Service\Item\Iface or Aimeos\MShop\Customer\Item\Iface or Aimeos\MShop\Text\Item\Iface or Aimeos\MShop\Media\Item\Iface or Aimeos\MShop\Price\Item\Iface or Aimeos\MShop\Attribute\Item\Iface or Aimeos\MShop\Catalog\Item\Iface or Aimeos\MShop\Supplier\Item\Iface or Aimeos\MShop\Attribute\Item\Standard or Aimeos\MShop\Catalog\Item\Standard or Aimeos\MShop\Customer\Item\Base or Aimeos\MShop\Media\Item\Standard or Aimeos\MShop\Text\Item\Standard or Aimeos\MShop\Service\Item\Standard or Aimeos\MShop\Common\Item\ListRef\Base or Aimeos\MShop\Price\Item\Base or Aimeos\MShop\Supplier\Item\Standard or Aimeos\MShop\Product\Item\Standard or Aimeos\MShop\Product\Item\Iface or Aimeos\MShop\Service\Item\Iface or Aimeos\MShop\Customer\Item\Iface or Aimeos\MShop\Text\Item\Iface or Aimeos\MShop\Media\Item\Iface or Aimeos\MShop\Price\Item\Iface or Aimeos\MShop\Attribute\Item\Iface or Aimeos\MShop\Supplier\Item\Iface or Aimeos\MShop\Catalog\Item\Iface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

425
		foreach( $productItem->/** @scrutinizer ignore-call */ getRefItems( 'product', 'default', 'default' ) as $product ) {
Loading history...
426
			$prodCodes[] = $product->getCode();
427
		}
428
429
		foreach( $this->getStockItems( $prodCodes, $stocktype ) as $stockItem )
430
		{
431
			if( $stockItem->getProductCode() === $productItem->getCode() )
432
			{
433
				$selStockItem = $stockItem;
434
				continue;
435
			}
436
437
			if( ( $stock = $stockItem->getStockLevel() ) === null ) {
438
				$sum = null;
439
			}
440
441
			if( $sum !== null ) {
442
				$sum += $stock;
443
			}
444
		}
445
446
		if( $selStockItem === null )
447
		{
448
			$typeManager = \Aimeos\MShop\Factory::createManager( $this->context, 'stock/type' );
449
			$typeId = $typeManager->findItem( $stocktype )->getId();
450
451
			$selStockItem = $stockManager->createItem();
452
			$selStockItem->setProductCode( $productItem->getCode() );
453
			$selStockItem->setTypeId( $typeId );
454
		}
455
456
		$selStockItem->setStockLevel( $sum );
457
		$stockManager->saveItem( $selStockItem, false );
458
	}
459
}
460