Passed
Push — master ( 99820d...65faa3 )
by Aimeos
04:18
created

Standard::createInvoiceNumber()   A

Complexity

Conditions 2
Paths 9

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 26
rs 9.7666
cc 2
nc 9
nop 1
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package MShop
8
 * @subpackage Order
9
 */
10
11
12
namespace Aimeos\MShop\Order\Manager;
13
14
15
/**
16
 * Default order manager implementation.
17
 *
18
 * @package MShop
19
 * @subpackage Order
20
 */
21
class Standard
22
	extends \Aimeos\MShop\Common\Manager\Base
23
	implements \Aimeos\MShop\Order\Manager\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
24
{
25
	/** mshop/order/manager/name
26
	 * Class name of the used order manager implementation
27
	 *
28
	 * Each default manager can be replace by an alternative imlementation.
29
	 * To use this implementation, you have to set the last part of the class
30
	 * name as configuration value so the manager factory knows which class it
31
	 * has to instantiate.
32
	 *
33
	 * For example, if the name of the default class is
34
	 *
35
	 *  \Aimeos\MShop\Order\Manager\Standard
36
	 *
37
	 * and you want to replace it with your own version named
38
	 *
39
	 *  \Aimeos\MShop\Order\Manager\Mymanager
40
	 *
41
	 * then you have to set the this configuration option:
42
	 *
43
	 *  mshop/order/manager/name = Mymanager
44
	 *
45
	 * The value is the last part of your own class name and it's case sensitive,
46
	 * so take care that the configuration value is exactly named like the last
47
	 * part of the class name.
48
	 *
49
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
50
	 * characters are possible! You should always start the last part of the class
51
	 * name with an upper case character and continue only with lower case characters
52
	 * or numbers. Avoid chamel case names like "MyManager"!
53
	 *
54
	 * @param string Last part of the class name
55
	 * @since 2014.03
56
	 * @category Developer
57
	 */
58
59
	/** mshop/order/manager/decorators/excludes
60
	 * Excludes decorators added by the "common" option from the order manager
61
	 *
62
	 * Decorators extend the functionality of a class by adding new aspects
63
	 * (e.g. log what is currently done), executing the methods of the underlying
64
	 * class only in certain conditions (e.g. only for logged in users) or
65
	 * modify what is returned to the caller.
66
	 *
67
	 * This option allows you to remove a decorator added via
68
	 * "mshop/common/manager/decorators/default" before they are wrapped
69
	 * around the order manager.
70
	 *
71
	 *  mshop/order/manager/decorators/excludes = array( 'decorator1' )
72
	 *
73
	 * This would remove the decorator named "decorator1" from the list of
74
	 * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
75
	 * "mshop/common/manager/decorators/default" for the order manager.
76
	 *
77
	 * @param array List of decorator names
78
	 * @since 2014.03
79
	 * @category Developer
80
	 * @see mshop/common/manager/decorators/default
81
	 * @see mshop/order/manager/decorators/global
82
	 * @see mshop/order/manager/decorators/local
83
	 */
84
85
	/** mshop/order/manager/decorators/global
86
	 * Adds a list of globally available decorators only to the order manager
87
	 *
88
	 * Decorators extend the functionality of a class by adding new aspects
89
	 * (e.g. log what is currently done), executing the methods of the underlying
90
	 * class only in certain conditions (e.g. only for logged in users) or
91
	 * modify what is returned to the caller.
92
	 *
93
	 * This option allows you to wrap global decorators
94
	 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the order manager.
95
	 *
96
	 *  mshop/order/manager/decorators/global = array( 'decorator1' )
97
	 *
98
	 * This would add the decorator named "decorator1" defined by
99
	 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the order
100
	 * manager.
101
	 *
102
	 * @param array List of decorator names
103
	 * @since 2014.03
104
	 * @category Developer
105
	 * @see mshop/common/manager/decorators/default
106
	 * @see mshop/order/manager/decorators/excludes
107
	 * @see mshop/order/manager/decorators/local
108
	 */
109
110
	/** mshop/order/manager/decorators/local
111
	 * Adds a list of local decorators only to the order manager
112
	 *
113
	 * Decorators extend the functionality of a class by adding new aspects
114
	 * (e.g. log what is currently done), executing the methods of the underlying
115
	 * class only in certain conditions (e.g. only for logged in users) or
116
	 * modify what is returned to the caller.
117
	 *
118
	 * This option allows you to wrap local decorators
119
	 * ("\Aimeos\MShop\Order\Manager\Decorator\*") around the order manager.
120
	 *
121
	 *  mshop/order/manager/decorators/local = array( 'decorator2' )
122
	 *
123
	 * This would add the decorator named "decorator2" defined by
124
	 * "\Aimeos\MShop\Order\Manager\Decorator\Decorator2" only to the order
125
	 * manager.
126
	 *
127
	 * @param array List of decorator names
128
	 * @since 2014.03
129
	 * @category Developer
130
	 * @see mshop/common/manager/decorators/default
131
	 * @see mshop/order/manager/decorators/excludes
132
	 * @see mshop/order/manager/decorators/global
133
	 */
134
135
136
	private $searchConfig = array(
137
		'order.id' => array(
138
			'code' => 'order.id',
139
			'internalcode' => 'mord."id"',
140
			'label' => 'Invoice ID',
141
			'type' => 'integer',
142
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
143
		),
144
		'order.siteid' => array(
145
			'code' => 'order.siteid',
146
			'internalcode' => 'mord."siteid"',
147
			'label' => 'Invoice site ID',
148
			'type' => 'string',
149
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
150
			'public' => false,
151
		),
152
		'order.baseid' => array(
153
			'code' => 'order.baseid',
154
			'internalcode' => 'mord."baseid"',
155
			'label' => 'Invoice base ID',
156
			'type' => 'integer',
157
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
158
			'public' => false,
159
		),
160
		'order.invoiceno' => array(
161
			'code' => 'order.invoiceno',
162
			'internalcode' => 'mord."invoiceno"',
163
			'label' => 'Invoice number',
164
			'type' => 'integer',
165
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
166
		),
167
		'order.relatedid' => array(
168
			'code' => 'order.relatedid',
169
			'internalcode' => 'mord."relatedid"',
170
			'label' => 'Related invoice ID',
171
			'type' => 'string',
172
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
173
		),
174
		'order.channel' => array(
175
			'code' => 'order.channel',
176
			'internalcode' => 'mord."channel"',
177
			'label' => 'Order channel',
178
			'type' => 'string',
179
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
180
		),
181
		'order.datepayment' => array(
182
			'code' => 'order.datepayment',
183
			'internalcode' => 'mord."datepayment"',
184
			'label' => 'Purchase date',
185
			'type' => 'datetime',
186
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
187
		),
188
		'order.datedelivery' => array(
189
			'code' => 'order.datedelivery',
190
			'internalcode' => 'mord."datedelivery"',
191
			'label' => 'Delivery date',
192
			'type' => 'datetime',
193
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
194
		),
195
		'order.statusdelivery' => array(
196
			'code' => 'order.statusdelivery',
197
			'internalcode' => 'mord."statusdelivery"',
198
			'label' => 'Delivery status',
199
			'type' => 'integer',
200
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
201
		),
202
		'order.statuspayment' => array(
203
			'code' => 'order.statuspayment',
204
			'internalcode' => 'mord."statuspayment"',
205
			'label' => 'Payment status',
206
			'type' => 'integer',
207
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
208
		),
209
		'order.cdate' => array(
210
			'code' => 'order.cdate',
211
			'internalcode' => 'mord."cdate"',
212
			'label' => 'Create date',
213
			'type' => 'string',
214
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
215
			'public' => false,
216
		),
217
		'order.cmonth' => array(
218
			'code' => 'order.cmonth',
219
			'internalcode' => 'mord."cmonth"',
220
			'label' => 'Create month',
221
			'type' => 'string',
222
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
223
			'public' => false,
224
		),
225
		'order.cweek' => array(
226
			'code' => 'order.cweek',
227
			'internalcode' => 'mord."cweek"',
228
			'label' => 'Create week',
229
			'type' => 'string',
230
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
231
			'public' => false,
232
		),
233
		'order.cwday' => array(
234
			'code' => 'order.cwday',
235
			'internalcode' => 'mord."cwday"',
236
			'label' => 'Create weekday',
237
			'type' => 'string',
238
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
239
			'public' => false,
240
		),
241
		'order.chour' => array(
242
			'code' => 'order.chour',
243
			'internalcode' => 'mord."chour"',
244
			'label' => 'Create hour',
245
			'type' => 'string',
246
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
247
			'public' => false,
248
		),
249
		'order.ctime' => array(
250
			'code' => 'order.ctime',
251
			'internalcode' => 'mord."ctime"',
252
			'label' => 'Create date/time',
253
			'type' => 'datetime',
254
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
255
			'public' => false,
256
		),
257
		'order.mtime' => array(
258
			'code' => 'order.mtime',
259
			'internalcode' => 'mord."mtime"',
260
			'label' => 'Modify date/time',
261
			'type' => 'datetime',
262
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
263
			'public' => false,
264
		),
265
		'order.editor' => array(
266
			'code' => 'order.editor',
267
			'internalcode' => 'mord."editor"',
268
			'label' => 'Editor',
269
			'type' => 'string',
270
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
271
			'public' => false,
272
		),
273
		'order:status' => array(
274
			'code' => 'order:status()',
275
			'internalcode' => '( SELECT COUNT(mordst_cs."parentid")
276
				FROM "mshop_order_status" AS mordst_cs
277
				WHERE mord."id" = mordst_cs."parentid" AND :site
278
				AND mordst_cs."type" = $1 AND mordst_cs."value" IN ( $2 ) )',
279
			'label' => 'Number of order status items, parameter(<type>,<value>)',
280
			'type' => 'integer',
281
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
282
			'public' => false,
283
		),
284
	);
285
286
287
	/**
288
	 * Creates the manager that will use the given context object.
289
	 *
290
	 * @param \Aimeos\MShop\ContextIface $context Context object with required objects
291
	 */
292
	public function __construct( \Aimeos\MShop\ContextIface $context )
293
	{
294
		parent::__construct( $context );
295
		$this->setResourceName( 'db-order' );
296
297
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
298
		$level = $context->config()->get( 'mshop/order/manager/sitemode', $level );
299
300
		$name = 'order:status';
301
		$expr = $this->siteString( 'mordst_cs."siteid"', $level );
302
		$this->searchConfig[$name]['internalcode'] = str_replace( ':site', $expr, $this->searchConfig[$name]['internalcode'] );
303
	}
304
305
306
	/**
307
	 * Counts the number items that are available for the values of the given key.
308
	 *
309
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
310
	 * @param array|string $key Search key or list of key to aggregate items for
311
	 * @param string|null $value Search key for aggregating the value column
312
	 * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
313
	 * @return \Aimeos\Map List of the search keys as key and the number of counted items as value
314
	 */
315
	public function aggregate( \Aimeos\Base\Criteria\Iface $search, $key, string $value = null, string $type = null ) : \Aimeos\Map
316
	{
317
		/** mshop/order/manager/aggregate/mysql
318
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
319
		 *
320
		 * @see mshop/order/manager/aggregate/ansi
321
		 */
322
323
		/** mshop/order/manager/aggregate/ansi
324
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
325
		 *
326
		 * Groups all records by the values in the key column and counts their
327
		 * occurence. The matched records can be limited by the given criteria
328
		 * from the order database. The records must be from one of the sites
329
		 * that are configured via the context item. If the current site is part
330
		 * of a tree of sites, the statement can count all records from the
331
		 * current site and the complete sub-tree of sites.
332
		 *
333
		 * As the records can normally be limited by criteria from sub-managers,
334
		 * their tables must be joined in the SQL context. This is done by
335
		 * using the "internaldeps" property from the definition of the ID
336
		 * column of the sub-managers. These internal dependencies specify
337
		 * the JOIN between the tables and the used columns for joining. The
338
		 * ":joins" placeholder is then replaced by the JOIN strings from
339
		 * the sub-managers.
340
		 *
341
		 * To limit the records matched, conditions can be added to the given
342
		 * criteria object. It can contain comparisons like column names that
343
		 * must match specific values which can be combined by AND, OR or NOT
344
		 * operators. The resulting string of SQL conditions replaces the
345
		 * ":cond" placeholder before the statement is sent to the database
346
		 * server.
347
		 *
348
		 * This statement doesn't return any records. Instead, it returns pairs
349
		 * of the different values found in the key column together with the
350
		 * number of records that have been found for that key values.
351
		 *
352
		 * The SQL statement should conform to the ANSI standard to be
353
		 * compatible with most relational database systems. This also
354
		 * includes using double quotes for table and column names.
355
		 *
356
		 * @param string SQL statement for aggregating order items
357
		 * @since 2014.09
358
		 * @category Developer
359
		 * @see mshop/order/manager/insert/ansi
360
		 * @see mshop/order/manager/update/ansi
361
		 * @see mshop/order/manager/newid/ansi
362
		 * @see mshop/order/manager/delete/ansi
363
		 * @see mshop/order/manager/search/ansi
364
		 * @see mshop/order/manager/count/ansi
365
		 */
366
367
		/** mshop/order/manager/aggregateavg/mysql
368
		 * Computes the average of all values grouped by the key column and matched by the given criteria
369
		 *
370
		 * @param string SQL statement for aggregating the order items and computing the average value
371
		 * @since 2017.10
372
		 * @category Developer
373
		 * @see mshop/order/manager/aggregateavg/ansi
374
		 * @see mshop/order/manager/aggregate/mysql
375
		 */
376
377
		/** mshop/order/manager/aggregateavg/ansi
378
		 * Computes the average of all values grouped by the key column and matched by the given criteria
379
		 *
380
		 * @param string SQL statement for aggregating the order items and computing the average value
381
		 * @since 2017.10
382
		 * @category Developer
383
		 * @see mshop/order/manager/aggregate/ansi
384
		 */
385
386
		/** mshop/order/manager/aggregatesum/mysql
387
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
388
		 *
389
		 * @param string SQL statement for aggregating the order items and computing the sum
390
		 * @since 2017.10
391
		 * @category Developer
392
		 * @see mshop/order/manager/aggregatesum/ansi
393
		 * @see mshop/order/manager/aggregate/mysql
394
		 */
395
396
		/** mshop/order/manager/aggregatesum/ansi
397
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
398
		 *
399
		 * @param string SQL statement for aggregating the order items and computing the sum
400
		 * @since 2017.10
401
		 * @category Developer
402
		 * @see mshop/order/manager/aggregate/ansi
403
		 */
404
405
		$cfgkey = 'mshop/order/manager/aggregate';
406
		return $this->aggregateBase( $search, $key, $cfgkey, ['order'], $value, $type );
407
	}
408
409
410
	/**
411
	 * Removes old entries from the storage.
412
	 *
413
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
414
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
415
	 */
416
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
417
	{
418
		$path = 'mshop/order/manager/submanagers';
419
		foreach( $this->context()->config()->get( $path, array( 'status', 'base' ) ) as $domain ) {
420
			$this->object()->getSubManager( $domain )->clear( $siteids );
421
		}
422
423
		return $this->clearBase( $siteids, 'mshop/order/manager/delete' );
424
	}
425
426
427
	/**
428
	 * Creates a new empty item instance
429
	 *
430
	 * @param array $values Values the item should be initialized with
431
	 * @return \Aimeos\MShop\Order\Item\Iface New order item object
432
	 */
433
	public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
434
	{
435
		$values['order.siteid'] = $values['order.siteid'] ?? $this->context()->locale()->getSiteId();
436
		return $this->createItemBase( $values );
437
	}
438
439
440
	/**
441
	 * Creates a search critera object
442
	 *
443
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
444
	 * @param bool $site TRUE to add site criteria to show orders with available products only
445
	 * @return \Aimeos\Base\Criteria\Iface New search criteria object
446
	 */
447
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
448
	{
449
		$search = parent::filter();
450
		$context = $this->context();
451
452
		if( $default !== false ) {
453
			$search->add( ['order.base.customerid' => $context->user()] );
454
		}
455
456
		if( $site === true )
457
		{
458
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE;
459
			$search->add( $this->siteCondition( 'order.base.product.siteid', $level ) );
460
		}
461
462
		return $search;
463
	}
464
465
466
	/**
467
	 * Creates a one-time order in the storage from the given invoice object.
468
	 *
469
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item with necessary values
470
	 * @param bool $fetch True if the new ID should be returned in the item
471
	 * @return \Aimeos\MShop\Order\Item\Iface $item Updated item including the generated ID
472
	 */
473
	public function saveItem( \Aimeos\MShop\Order\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Order\Item\Iface
474
	{
475
		if( $item->getBaseId() === null ) {
476
			throw new \Aimeos\MShop\Order\Exception( 'Required order base ID is missing' );
477
		}
478
479
		if( !$item->isModified() ) {
480
			return $item;
481
		}
482
483
		if( empty( $item->getInvoiceNumber() ) && $item->getStatusPayment() >= \Aimeos\MShop\Order\Item\Base::PAY_AUTHORIZED )
484
		{
485
			try {
486
				$item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
487
			} catch( \Exception $e ) { // redo on transaction deadlock
488
				$item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
489
			}
490
		}
491
492
		$context = $this->context();
493
		$conn = $context->db( $this->getResourceName() );
494
495
		$id = $item->getId();
496
		$date = date( 'Y-m-d H:i:s' );
497
		$columns = $this->object()->getSaveAttributes();
498
499
		if( $id === null )
500
		{
501
			/** mshop/order/manager/insert/mysql
502
			 * Inserts a new order record into the database table
503
			 *
504
			 * @see mshop/order/manager/insert/ansi
505
			 */
506
507
			/** mshop/order/manager/insert/ansi
508
			 * Inserts a new order record into the database table
509
			 *
510
			 * Items with no ID yet (i.e. the ID is NULL) will be created in
511
			 * the database and the newly created ID retrieved afterwards
512
			 * using the "newid" SQL statement.
513
			 *
514
			 * The SQL statement must be a string suitable for being used as
515
			 * prepared statement. It must include question marks for binding
516
			 * the values from the order item to the statement before they are
517
			 * sent to the database server. The number of question marks must
518
			 * be the same as the number of columns listed in the INSERT
519
			 * statement. The order of the columns must correspond to the
520
			 * order in the save() method, so the correct values are
521
			 * bound to the columns.
522
			 *
523
			 * The SQL statement should conform to the ANSI standard to be
524
			 * compatible with most relational database systems. This also
525
			 * includes using double quotes for table and column names.
526
			 *
527
			 * @param string SQL statement for inserting records
528
			 * @since 2014.03
529
			 * @category Developer
530
			 * @see mshop/order/manager/update/ansi
531
			 * @see mshop/order/manager/newid/ansi
532
			 * @see mshop/order/manager/delete/ansi
533
			 * @see mshop/order/manager/search/ansi
534
			 * @see mshop/order/manager/count/ansi
535
			 */
536
			$path = 'mshop/order/manager/insert';
537
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ) );
0 ignored issues
show
Bug introduced by
It seems like $this->getSqlConfig($path) can also be of type array; however, parameter $sql of Aimeos\MShop\Common\Manager\Base::addSqlColumns() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

537
			$sql = $this->addSqlColumns( array_keys( $columns ), /** @scrutinizer ignore-type */ $this->getSqlConfig( $path ) );
Loading history...
538
		}
539
		else
540
		{
541
			/** mshop/order/manager/update/mysql
542
			 * Updates an existing order record in the database
543
			 *
544
			 * @see mshop/order/manager/update/ansi
545
			 */
546
547
			/** mshop/order/manager/update/ansi
548
			 * Updates an existing order record in the database
549
			 *
550
			 * Items which already have an ID (i.e. the ID is not NULL) will
551
			 * be updated in the database.
552
			 *
553
			 * The SQL statement must be a string suitable for being used as
554
			 * prepared statement. It must include question marks for binding
555
			 * the values from the order item to the statement before they are
556
			 * sent to the database server. The order of the columns must
557
			 * correspond to the order in the save() method, so the
558
			 * correct values are bound to the columns.
559
			 *
560
			 * The SQL statement should conform to the ANSI standard to be
561
			 * compatible with most relational database systems. This also
562
			 * includes using double quotes for table and column names.
563
			 *
564
			 * @param string SQL statement for updating records
565
			 * @since 2014.03
566
			 * @category Developer
567
			 * @see mshop/order/manager/insert/ansi
568
			 * @see mshop/order/manager/newid/ansi
569
			 * @see mshop/order/manager/delete/ansi
570
			 * @see mshop/order/manager/search/ansi
571
			 * @see mshop/order/manager/count/ansi
572
			 */
573
			$path = 'mshop/order/manager/update';
574
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false );
575
		}
576
577
		$idx = 1;
578
		$stmt = $this->getCachedStatement( $conn, $path, $sql );
579
580
		foreach( $columns as $name => $entry ) {
581
			$stmt->bind( $idx++, $item->get( $name ), $entry->getInternalType() );
582
		}
583
584
		$stmt->bind( $idx++, $item->getBaseId(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
585
		$stmt->bind( $idx++, $item->getInvoiceNumber() );
586
		$stmt->bind( $idx++, $item->getChannel() );
587
		$stmt->bind( $idx++, $item->getDatePayment() );
588
		$stmt->bind( $idx++, $item->getDateDelivery() );
589
		$stmt->bind( $idx++, $item->getStatusDelivery(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
590
		$stmt->bind( $idx++, $item->getStatusPayment(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
591
		$stmt->bind( $idx++, $item->getRelatedId(), \Aimeos\Base\DB\Statement\Base::PARAM_STR );
592
		$stmt->bind( $idx++, $date ); // mtime
593
		$stmt->bind( $idx++, $context->editor() );
594
		$stmt->bind( $idx++, $context->locale()->getSiteId() );
595
596
		if( $id !== null ) {
597
			$stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT );
598
		} else {
599
			$stmt->bind( $idx++, $date ); // ctime
600
			$stmt->bind( $idx++, date( 'Y-m-d' ) ); // cdate
601
			$stmt->bind( $idx++, date( 'Y-m' ) ); // cmonth
602
			$stmt->bind( $idx++, date( 'Y-W' ) ); // cweek
603
			$stmt->bind( $idx++, date( 'w' ) ); // cwday
604
			$stmt->bind( $idx++, date( 'H' ) ); // chour
605
		}
606
607
		$stmt->execute()->finish();
608
609
		if( $id === null && $fetch === true )
610
		{
611
			/** mshop/order/manager/newid/mysql
612
			 * Retrieves the ID generated by the database when inserting a new record
613
			 *
614
			 * @see mshop/order/manager/newid/ansi
615
			 */
616
617
			/** mshop/order/manager/newid/ansi
618
			 * Retrieves the ID generated by the database when inserting a new record
619
			 *
620
			 * As soon as a new record is inserted into the database table,
621
			 * the database server generates a new and unique identifier for
622
			 * that record. This ID can be used for retrieving, updating and
623
			 * deleting that specific record from the table again.
624
			 *
625
			 * For MySQL:
626
			 *  SELECT LAST_INSERT_ID()
627
			 * For PostgreSQL:
628
			 *  SELECT currval('seq_mord_id')
629
			 * For SQL Server:
630
			 *  SELECT SCOPE_IDENTITY()
631
			 * For Oracle:
632
			 *  SELECT "seq_mord_id".CURRVAL FROM DUAL
633
			 *
634
			 * There's no way to retrive the new ID by a SQL statements that
635
			 * fits for most database servers as they implement their own
636
			 * specific way.
637
			 *
638
			 * @param string SQL statement for retrieving the last inserted record ID
639
			 * @since 2014.03
640
			 * @category Developer
641
			 * @see mshop/order/manager/insert/ansi
642
			 * @see mshop/order/manager/update/ansi
643
			 * @see mshop/order/manager/delete/ansi
644
			 * @see mshop/order/manager/search/ansi
645
			 * @see mshop/order/manager/count/ansi
646
			 */
647
			$path = 'mshop/order/manager/newid';
648
			$id = $this->newId( $conn, $path );
649
		}
650
651
		$item->setId( $id );
652
653
		$this->addStatus( $item );
654
655
		return $item;
656
	}
657
658
659
	/**
660
	 * Returns an order invoice item built from database values.
661
	 *
662
	 * @param string $id Unique id of the order invoice
663
	 * @param string[] $ref List of domains to fetch list items and referenced items for
664
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
665
	 * @return \Aimeos\MShop\Order\Item\Iface Returns order invoice item of the given id
666
	 * @throws \Aimeos\MShop\Order\Exception If item couldn't be found
667
	 */
668
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
669
	{
670
		return $this->getItemBase( 'order.id', $id, $ref, $default );
671
	}
672
673
674
	/**
675
	 * Removes multiple items.
676
	 *
677
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
678
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
679
	 */
680
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
681
	{
682
		/** mshop/order/manager/delete/mysql
683
		 * Deletes the items matched by the given IDs from the database
684
		 *
685
		 * @see mshop/order/manager/delete/ansi
686
		 */
687
688
		/** mshop/order/manager/delete/ansi
689
		 * Deletes the items matched by the given IDs from the database
690
		 *
691
		 * Removes the records specified by the given IDs from the order database.
692
		 * The records must be from the site that is configured via the
693
		 * context item.
694
		 *
695
		 * The ":cond" placeholder is replaced by the name of the ID column and
696
		 * the given ID or list of IDs while the site ID is bound to the question
697
		 * mark.
698
		 *
699
		 * The SQL statement should conform to the ANSI standard to be
700
		 * compatible with most relational database systems. This also
701
		 * includes using double quotes for table and column names.
702
		 *
703
		 * @param string SQL statement for deleting items
704
		 * @since 2014.03
705
		 * @category Developer
706
		 * @see mshop/order/manager/insert/ansi
707
		 * @see mshop/order/manager/update/ansi
708
		 * @see mshop/order/manager/newid/ansi
709
		 * @see mshop/order/manager/search/ansi
710
		 * @see mshop/order/manager/count/ansi
711
		 */
712
		$path = 'mshop/order/manager/delete';
713
714
		return $this->deleteItemsBase( $itemIds, $path );
715
	}
716
717
718
	/**
719
	 * Returns the available manager types
720
	 *
721
	 * @param bool $withsub Return also the resource type of sub-managers if true
722
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
723
	 */
724
	public function getResourceType( bool $withsub = true ) : array
725
	{
726
		$path = 'mshop/order/manager/submanagers';
727
		return $this->getResourceTypeBase( 'order', $path, array( 'base', 'status' ), $withsub );
728
	}
729
730
731
	/**
732
	 * Returns the attributes that can be used for searching.
733
	 *
734
	 * @param bool $withsub Return also attributes of sub-managers if true
735
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of search attribute items
736
	 */
737
	public function getSearchAttributes( bool $withsub = true ) : array
738
	{
739
		/** mshop/order/manager/submanagers
740
		 * List of manager names that can be instantiated by the order manager
741
		 *
742
		 * Managers provide a generic interface to the underlying storage.
743
		 * Each manager has or can have sub-managers caring about particular
744
		 * aspects. Each of these sub-managers can be instantiated by its
745
		 * parent manager using the getSubManager() method.
746
		 *
747
		 * The search keys from sub-managers can be normally used in the
748
		 * manager as well. It allows you to search for items of the manager
749
		 * using the search keys of the sub-managers to further limit the
750
		 * retrieved list of items.
751
		 *
752
		 * @param array List of sub-manager names
753
		 * @since 2014.03
754
		 * @category Developer
755
		 */
756
		$path = 'mshop/order/manager/submanagers';
757
		$default = array( 'base', 'status' );
758
759
		return $this->getSearchAttributesBase( $this->searchConfig, $path, $default, $withsub );
760
	}
761
762
763
	/**
764
	 * Searches for orders based on the given criteria.
765
	 *
766
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
767
	 * @param string[] $ref List of domains to fetch list items and referenced items for
768
	 * @param int|null &$total Number of items that are available in total
769
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Order\Item\Iface with ids as keys
770
	 */
771
	public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
772
	{
773
		$context = $this->context();
774
		$conn = $context->db( $this->getResourceName() );
775
		$map = $items = $baseItems = [];
776
777
			$required = array( 'order' );
778
779
			/** mshop/order/manager/sitemode
780
			 * Mode how items from levels below or above in the site tree are handled
781
			 *
782
			 * By default, only items from the current site are fetched from the
783
			 * storage. If the ai-sites extension is installed, you can create a
784
			 * tree of sites. Then, this setting allows you to define for the
785
			 * whole order domain if items from parent sites are inherited,
786
			 * sites from child sites are aggregated or both.
787
			 *
788
			 * Available constants for the site mode are:
789
			 * * 0 = only items from the current site
790
			 * * 1 = inherit items from parent sites
791
			 * * 2 = aggregate items from child sites
792
			 * * 3 = inherit and aggregate items at the same time
793
			 *
794
			 * You also need to set the mode in the locale manager
795
			 * (mshop/locale/manager/sitelevel) to one of the constants.
796
			 * If you set it to the same value, it will work as described but you
797
			 * can also use different modes. For example, if inheritance and
798
			 * aggregation is configured the locale manager but only inheritance
799
			 * in the domain manager because aggregating items makes no sense in
800
			 * this domain, then items wil be only inherited. Thus, you have full
801
			 * control over inheritance and aggregation in each domain.
802
			 *
803
			 * @param int Constant from Aimeos\MShop\Locale\Manager\Base class
804
			 * @category Developer
805
			 * @since 2018.01
806
			 * @see mshop/locale/manager/sitelevel
807
			 */
808
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
809
			$level = $context->config()->get( 'mshop/order/manager/sitemode', $level );
810
811
			/** mshop/order/manager/search/mysql
812
			 * Retrieves the records matched by the given criteria in the database
813
			 *
814
			 * @see mshop/order/manager/search/ansi
815
			 */
816
817
			/** mshop/order/manager/search/ansi
818
			 * Retrieves the records matched by the given criteria in the database
819
			 *
820
			 * Fetches the records matched by the given criteria from the order
821
			 * database. The records must be from one of the sites that are
822
			 * configured via the context item. If the current site is part of
823
			 * a tree of sites, the SELECT statement can retrieve all records
824
			 * from the current site and the complete sub-tree of sites.
825
			 *
826
			 * As the records can normally be limited by criteria from sub-managers,
827
			 * their tables must be joined in the SQL context. This is done by
828
			 * using the "internaldeps" property from the definition of the ID
829
			 * column of the sub-managers. These internal dependencies specify
830
			 * the JOIN between the tables and the used columns for joining. The
831
			 * ":joins" placeholder is then replaced by the JOIN strings from
832
			 * the sub-managers.
833
			 *
834
			 * To limit the records matched, conditions can be added to the given
835
			 * criteria object. It can contain comparisons like column names that
836
			 * must match specific values which can be combined by AND, OR or NOT
837
			 * operators. The resulting string of SQL conditions replaces the
838
			 * ":cond" placeholder before the statement is sent to the database
839
			 * server.
840
			 *
841
			 * If the records that are retrieved should be ordered by one or more
842
			 * columns, the generated string of column / sort direction pairs
843
			 * replaces the ":order" placeholder. In case no ordering is required,
844
			 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
845
			 * markers is removed to speed up retrieving the records. Columns of
846
			 * sub-managers can also be used for ordering the result set but then
847
			 * no index can be used.
848
			 *
849
			 * The number of returned records can be limited and can start at any
850
			 * number between the begining and the end of the result set. For that
851
			 * the ":size" and ":start" placeholders are replaced by the
852
			 * corresponding values from the criteria object. The default values
853
			 * are 0 for the start and 100 for the size value.
854
			 *
855
			 * The SQL statement should conform to the ANSI standard to be
856
			 * compatible with most relational database systems. This also
857
			 * includes using double quotes for table and column names.
858
			 *
859
			 * @param string SQL statement for searching items
860
			 * @since 2014.03
861
			 * @category Developer
862
			 * @see mshop/order/manager/insert/ansi
863
			 * @see mshop/order/manager/update/ansi
864
			 * @see mshop/order/manager/newid/ansi
865
			 * @see mshop/order/manager/delete/ansi
866
			 * @see mshop/order/manager/count/ansi
867
			 */
868
			$cfgPathSearch = 'mshop/order/manager/search';
869
870
			/** mshop/order/manager/count/mysql
871
			 * Counts the number of records matched by the given criteria in the database
872
			 *
873
			 * @see mshop/order/manager/count/ansi
874
			 */
875
876
			/** mshop/order/manager/count/ansi
877
			 * Counts the number of records matched by the given criteria in the database
878
			 *
879
			 * Counts all records matched by the given criteria from the order
880
			 * database. The records must be from one of the sites that are
881
			 * configured via the context item. If the current site is part of
882
			 * a tree of sites, the statement can count all records from the
883
			 * current site and the complete sub-tree of sites.
884
			 *
885
			 * As the records can normally be limited by criteria from sub-managers,
886
			 * their tables must be joined in the SQL context. This is done by
887
			 * using the "internaldeps" property from the definition of the ID
888
			 * column of the sub-managers. These internal dependencies specify
889
			 * the JOIN between the tables and the used columns for joining. The
890
			 * ":joins" placeholder is then replaced by the JOIN strings from
891
			 * the sub-managers.
892
			 *
893
			 * To limit the records matched, conditions can be added to the given
894
			 * criteria object. It can contain comparisons like column names that
895
			 * must match specific values which can be combined by AND, OR or NOT
896
			 * operators. The resulting string of SQL conditions replaces the
897
			 * ":cond" placeholder before the statement is sent to the database
898
			 * server.
899
			 *
900
			 * Both, the strings for ":joins" and for ":cond" are the same as for
901
			 * the "search" SQL statement.
902
			 *
903
			 * Contrary to the "search" statement, it doesn't return any records
904
			 * but instead the number of records that have been found. As counting
905
			 * thousands of records can be a long running task, the maximum number
906
			 * of counted records is limited for performance reasons.
907
			 *
908
			 * The SQL statement should conform to the ANSI standard to be
909
			 * compatible with most relational database systems. This also
910
			 * includes using double quotes for table and column names.
911
			 *
912
			 * @param string SQL statement for counting items
913
			 * @since 2014.03
914
			 * @category Developer
915
			 * @see mshop/order/manager/insert/ansi
916
			 * @see mshop/order/manager/update/ansi
917
			 * @see mshop/order/manager/newid/ansi
918
			 * @see mshop/order/manager/delete/ansi
919
			 * @see mshop/order/manager/search/ansi
920
			 */
921
			$cfgPathCount = 'mshop/order/manager/count';
922
923
			$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount,
924
				$required, $total, $level );
925
926
			try
927
			{
928
				while( ( $row = $results->fetch() ) !== null ) {
929
					$map[$row['order.id']] = $row;
930
				}
931
			}
932
			catch( \Exception $e )
933
			{
934
				$results->finish();
935
				throw $e;
936
			}
937
938
939
		if( in_array( 'order/base', $ref ) )
940
		{
941
			$ids = [];
942
			foreach( $map as $row ) {
943
				$ids[] = $row['order.baseid'];
944
			}
945
946
			$manager = $this->object()->getSubManager( 'base' );
947
			$search = $manager->filter()->slice( 0, count( $ids ) );
948
			$search->setConditions( $search->compare( '==', 'order.base.id', $ids ) );
949
			$baseItems = $manager->search( $search, $ref );
950
		}
951
952
		foreach( $map as $id => $row )
953
		{
954
			$baseItem = $baseItems[$row['order.baseid']] ?? null;
955
956
			if( $item = $this->applyFilter( $this->createItemBase( $row, $baseItem ) ) ) {
957
				$items[$id] = $item;
958
			}
959
		}
960
961
		return map( $items );
962
	}
963
964
965
	/**
966
	 * Returns a new manager for order extensions
967
	 *
968
	 * @param string $manager Name of the sub manager type in lower case
969
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
970
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions, e.g base, etc.
971
	 */
972
	public function getSubManager( string $manager, string $name = null ) : \Aimeos\MShop\Common\Manager\Iface
973
	{
974
		return $this->getSubManagerBase( 'order', $manager, $name );
975
	}
976
977
978
	/**
979
	 * Adds the new payment and delivery values to the order status log.
980
	 *
981
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item object
982
	 */
983
	protected function addStatus( \Aimeos\MShop\Order\Item\Iface $item )
984
	{
985
		$statusManager = \Aimeos\MShop::create( $this->context(), 'order/status' );
986
987
		$statusItem = $statusManager->create();
988
		$statusItem->setParentId( $item->getId() );
989
990
		if( ( $status = $item->get( '.statuspayment' ) ) !== null && $status != $item->getStatusPayment() )
991
		{
992
			$statusItem->setId( null )->setValue( $item->getStatusPayment() )
993
				->setType( \Aimeos\MShop\Order\Item\Status\Base::STATUS_PAYMENT );
994
995
			$statusManager->save( $statusItem, false );
996
		}
997
998
		if( ( $status = $item->get( '.statusdelivery' ) ) !== null && $status != $item->getStatusDelivery() )
999
		{
1000
			$statusItem->setId( null )->setValue( $item->getStatusDelivery() )
1001
				->setType( \Aimeos\MShop\Order\Item\Status\Base::STATUS_DELIVERY );
1002
1003
			$statusManager->save( $statusItem, false );
1004
		}
1005
	}
1006
1007
1008
	/**
1009
	 * Creates a new invoice number for the passed order and site.
1010
	 *
1011
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item with necessary values
1012
	 * @return string Unique invoice number for the current site
1013
	 */
1014
	public function createInvoiceNumber( \Aimeos\MShop\Order\Item\Iface $item ) : string
1015
	{
1016
		$context = $this->context();
1017
		$siteId = $context->locale()->getSiteId();
1018
		$conn = $context->db( 'db-locale', true );
1019
1020
		try
1021
		{
1022
			$conn->query( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE' )->finish();
1023
			$conn->query( 'START TRANSACTION' )->finish();
1024
1025
			$stmt = $conn->query( 'SELECT "invoiceno" FROM "mshop_locale_site" where "siteid" = ?', [$siteId] );
0 ignored issues
show
Unused Code introduced by
The assignment to $stmt is dead and can be removed.
Loading history...
1026
			$row = $result->fetch();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be never defined.
Loading history...
1027
			$result->finish();
1028
1029
			$stmt = $conn->create( 'UPDATE "mshop_locale_site" SET "invoice" = "invoice" + 1 WHERE "siteid" = ?' );
1030
			$stmt->bind( 1, $siteId )->execute()->finish();
1031
1032
			$conn->query( 'COMMIT' )->finish();
1033
		}
1034
		catch( \Exception $e )
1035
		{
1036
			$conn->close();
1037
		}
1038
1039
		return $row['invoiceno'] ?? '';
1040
	}
1041
1042
1043
	/**
1044
	 * Creates a new order item.
1045
	 *
1046
	 * @param array $values List of attributes for order item
1047
	 * @param \Aimeos\MShop\Order\Item\Base\Iface|null $baseItem Order basket if requested and available
1048
	 * @return \Aimeos\MShop\Order\Item\Iface New order item
1049
	 */
1050
	protected function createItemBase( array $values = [], ?\Aimeos\MShop\Order\Item\Base\Iface $baseItem = null ) : \Aimeos\MShop\Order\Item\Iface
1051
	{
1052
		return new \Aimeos\MShop\Order\Item\Standard( $values, $baseItem );
1053
	}
1054
}
1055