Passed
Push — master ( b35ffc...19a63c )
by Aimeos
04:50
created

Standard::createItemBase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 6
rs 10
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 extends Base
22
	implements \Aimeos\MShop\Order\Manager\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
23
{
24
	/** mshop/order/manager/name
25
	 * Class name of the used order manager implementation
26
	 *
27
	 * Each default manager can be replace by an alternative imlementation.
28
	 * To use this implementation, you have to set the last part of the class
29
	 * name as configuration value so the manager factory knows which class it
30
	 * has to instantiate.
31
	 *
32
	 * For example, if the name of the default class is
33
	 *
34
	 *  \Aimeos\MShop\Order\Manager\Standard
35
	 *
36
	 * and you want to replace it with your own version named
37
	 *
38
	 *  \Aimeos\MShop\Order\Manager\Mymanager
39
	 *
40
	 * then you have to set the this configuration option:
41
	 *
42
	 *  mshop/order/manager/name = Mymanager
43
	 *
44
	 * The value is the last part of your own class name and it's case sensitive,
45
	 * so take care that the configuration value is exactly named like the last
46
	 * part of the class name.
47
	 *
48
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
49
	 * characters are possible! You should always start the last part of the class
50
	 * name with an upper case character and continue only with lower case characters
51
	 * or numbers. Avoid chamel case names like "MyManager"!
52
	 *
53
	 * @param string Last part of the class name
54
	 * @since 2014.03
55
	 * @category Developer
56
	 */
57
58
	/** mshop/order/manager/decorators/excludes
59
	 * Excludes decorators added by the "common" option from the order manager
60
	 *
61
	 * Decorators extend the functionality of a class by adding new aspects
62
	 * (e.g. log what is currently done), executing the methods of the underlying
63
	 * class only in certain conditions (e.g. only for logged in users) or
64
	 * modify what is returned to the caller.
65
	 *
66
	 * This option allows you to remove a decorator added via
67
	 * "mshop/common/manager/decorators/default" before they are wrapped
68
	 * around the order manager.
69
	 *
70
	 *  mshop/order/manager/decorators/excludes = array( 'decorator1' )
71
	 *
72
	 * This would remove the decorator named "decorator1" from the list of
73
	 * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
74
	 * "mshop/common/manager/decorators/default" for the order manager.
75
	 *
76
	 * @param array List of decorator names
77
	 * @since 2014.03
78
	 * @category Developer
79
	 * @see mshop/common/manager/decorators/default
80
	 * @see mshop/order/manager/decorators/global
81
	 * @see mshop/order/manager/decorators/local
82
	 */
83
84
	/** mshop/order/manager/decorators/global
85
	 * Adds a list of globally available decorators only to the order manager
86
	 *
87
	 * Decorators extend the functionality of a class by adding new aspects
88
	 * (e.g. log what is currently done), executing the methods of the underlying
89
	 * class only in certain conditions (e.g. only for logged in users) or
90
	 * modify what is returned to the caller.
91
	 *
92
	 * This option allows you to wrap global decorators
93
	 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the order manager.
94
	 *
95
	 *  mshop/order/manager/decorators/global = array( 'decorator1' )
96
	 *
97
	 * This would add the decorator named "decorator1" defined by
98
	 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the order
99
	 * manager.
100
	 *
101
	 * @param array List of decorator names
102
	 * @since 2014.03
103
	 * @category Developer
104
	 * @see mshop/common/manager/decorators/default
105
	 * @see mshop/order/manager/decorators/excludes
106
	 * @see mshop/order/manager/decorators/local
107
	 */
108
109
	/** mshop/order/manager/decorators/local
110
	 * Adds a list of local decorators only to the order manager
111
	 *
112
	 * Decorators extend the functionality of a class by adding new aspects
113
	 * (e.g. log what is currently done), executing the methods of the underlying
114
	 * class only in certain conditions (e.g. only for logged in users) or
115
	 * modify what is returned to the caller.
116
	 *
117
	 * This option allows you to wrap local decorators
118
	 * ("\Aimeos\MShop\Order\Manager\Decorator\*") around the order manager.
119
	 *
120
	 *  mshop/order/manager/decorators/local = array( 'decorator2' )
121
	 *
122
	 * This would add the decorator named "decorator2" defined by
123
	 * "\Aimeos\MShop\Order\Manager\Decorator\Decorator2" only to the order
124
	 * manager.
125
	 *
126
	 * @param array List of decorator names
127
	 * @since 2014.03
128
	 * @category Developer
129
	 * @see mshop/common/manager/decorators/default
130
	 * @see mshop/order/manager/decorators/excludes
131
	 * @see mshop/order/manager/decorators/global
132
	 */
133
134
135
	private $searchConfig = array(
136
		'order.id' => array(
137
			'code' => 'order.id',
138
			'internalcode' => 'mord."id"',
139
			'label' => 'Invoice ID',
140
			'type' => 'integer',
141
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
142
		),
143
		'order.siteid' => array(
144
			'code' => 'order.siteid',
145
			'internalcode' => 'mord."siteid"',
146
			'label' => 'Invoice site ID',
147
			'type' => 'string',
148
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
149
			'public' => false,
150
		),
151
		'order.invoiceno' => array(
152
			'code' => 'order.invoiceno',
153
			'internalcode' => 'mord."invoiceno"',
154
			'label' => 'Invoice number',
155
			'type' => 'integer',
156
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
157
		),
158
		'order.relatedid' => array(
159
			'code' => 'order.relatedid',
160
			'internalcode' => 'mord."relatedid"',
161
			'label' => 'Related invoice ID',
162
			'type' => 'string',
163
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
164
		),
165
		'order.channel' => array(
166
			'code' => 'order.channel',
167
			'internalcode' => 'mord."channel"',
168
			'label' => 'Order channel',
169
			'type' => 'string',
170
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
171
		),
172
		'order.datepayment' => array(
173
			'code' => 'order.datepayment',
174
			'internalcode' => 'mord."datepayment"',
175
			'label' => 'Purchase date',
176
			'type' => 'datetime',
177
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
178
		),
179
		'order.datedelivery' => array(
180
			'code' => 'order.datedelivery',
181
			'internalcode' => 'mord."datedelivery"',
182
			'label' => 'Delivery date',
183
			'type' => 'datetime',
184
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
185
		),
186
		'order.statusdelivery' => array(
187
			'code' => 'order.statusdelivery',
188
			'internalcode' => 'mord."statusdelivery"',
189
			'label' => 'Delivery status',
190
			'type' => 'integer',
191
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
192
		),
193
		'order.statuspayment' => array(
194
			'code' => 'order.statuspayment',
195
			'internalcode' => 'mord."statuspayment"',
196
			'label' => 'Payment status',
197
			'type' => 'integer',
198
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
199
		),
200
		'order.sitecode' => array(
201
			'code' => 'order.sitecode',
202
			'internalcode' => 'mord."sitecode"',
203
			'label' => 'Order site code',
204
			'type' => 'string',
205
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
206
			'public' => false,
207
		),
208
		'order.customerid' => array(
209
			'code' => 'order.customerid',
210
			'internalcode' => 'mord."customerid"',
211
			'label' => 'Order customer ID',
212
			'type' => 'string',
213
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
214
		),
215
		'order.customerref' => array(
216
			'code' => 'order.customerref',
217
			'internalcode' => 'mord."customerref"',
218
			'label' => 'Order customer reference',
219
			'type' => 'string',
220
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
221
		),
222
		'order.languageid' => array(
223
			'code' => 'order.languageid',
224
			'internalcode' => 'mord."langid"',
225
			'label' => 'Order language code',
226
			'type' => 'string',
227
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
228
		),
229
		'order.currencyid' => array(
230
			'code' => 'order.currencyid',
231
			'internalcode' => 'mord."currencyid"',
232
			'label' => 'Order currencyid code',
233
			'type' => 'string',
234
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
235
		),
236
		'order.price' => array(
237
			'code' => 'order.price',
238
			'internalcode' => 'mord."price"',
239
			'label' => 'Order price amount',
240
			'type' => 'string',
241
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
242
		),
243
		'order.costs' => array(
244
			'code' => 'order.costs',
245
			'internalcode' => 'mord."costs"',
246
			'label' => 'Order shipping amount',
247
			'type' => 'string',
248
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
249
		),
250
		'order.rebate' => array(
251
			'code' => 'order.rebate',
252
			'internalcode' => 'mord."rebate"',
253
			'label' => 'Order rebate amount',
254
			'type' => 'string',
255
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
256
		),
257
		'order.taxvalue' => array(
258
			'code' => 'order.taxvalue',
259
			'internalcode' => 'mord."tax"',
260
			'label' => 'Order tax amount',
261
			'type' => 'string',
262
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
263
		),
264
		'order.taxflag' => array(
265
			'code' => 'order.taxflag',
266
			'internalcode' => 'mord."taxflag"',
267
			'label' => 'Order tax flag (0=net, 1=gross)',
268
			'type' => 'string',
269
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
270
		),
271
		'order.comment' => array(
272
			'code' => 'order.comment',
273
			'internalcode' => 'mord."comment"',
274
			'label' => 'Order comment',
275
			'type' => 'string',
276
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
277
		),
278
		'order.cdate' => array(
279
			'code' => 'order.cdate',
280
			'internalcode' => 'mord."cdate"',
281
			'label' => 'Create date',
282
			'type' => 'string',
283
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
284
			'public' => false,
285
		),
286
		'order.cmonth' => array(
287
			'code' => 'order.cmonth',
288
			'internalcode' => 'mord."cmonth"',
289
			'label' => 'Create month',
290
			'type' => 'string',
291
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
292
			'public' => false,
293
		),
294
		'order.cweek' => array(
295
			'code' => 'order.cweek',
296
			'internalcode' => 'mord."cweek"',
297
			'label' => 'Create week',
298
			'type' => 'string',
299
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
300
			'public' => false,
301
		),
302
		'order.cwday' => array(
303
			'code' => 'order.cwday',
304
			'internalcode' => 'mord."cwday"',
305
			'label' => 'Create weekday',
306
			'type' => 'string',
307
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
308
			'public' => false,
309
		),
310
		'order.chour' => array(
311
			'code' => 'order.chour',
312
			'internalcode' => 'mord."chour"',
313
			'label' => 'Create hour',
314
			'type' => 'string',
315
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
316
			'public' => false,
317
		),
318
		'order.ctime' => array(
319
			'code' => 'order.ctime',
320
			'internalcode' => 'mord."ctime"',
321
			'label' => 'Create date/time',
322
			'type' => 'datetime',
323
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
324
			'public' => false,
325
		),
326
		'order.mtime' => array(
327
			'code' => 'order.mtime',
328
			'internalcode' => 'mord."mtime"',
329
			'label' => 'Modify date/time',
330
			'type' => 'datetime',
331
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
332
			'public' => false,
333
		),
334
		'order.editor' => array(
335
			'code' => 'order.editor',
336
			'internalcode' => 'mord."editor"',
337
			'label' => 'Editor',
338
			'type' => 'string',
339
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
340
			'public' => false,
341
		),
342
		'order:status' => array(
343
			'code' => 'order:status()',
344
			'internalcode' => '( SELECT COUNT(mordst_cs."parentid")
345
				FROM "mshop_order_status" AS mordst_cs
346
				WHERE mord."id" = mordst_cs."parentid" AND :site
347
				AND mordst_cs."type" = $1 AND mordst_cs."value" IN ( $2 ) )',
348
			'label' => 'Number of order status items, parameter(<type>,<value>)',
349
			'type' => 'integer',
350
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
351
			'public' => false,
352
		),
353
	);
354
355
356
	/**
357
	 * Creates the manager that will use the given context object.
358
	 *
359
	 * @param \Aimeos\MShop\ContextIface $context Context object with required objects
360
	 */
361
	public function __construct( \Aimeos\MShop\ContextIface $context )
362
	{
363
		parent::__construct( $context );
364
		$this->setResourceName( 'db-order' );
365
366
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
367
		$level = $context->config()->get( 'mshop/order/manager/sitemode', $level );
368
369
		$name = 'order:status';
370
		$expr = $this->siteString( 'mordst_cs."siteid"', $level );
371
		$this->searchConfig[$name]['internalcode'] = str_replace( ':site', $expr, $this->searchConfig[$name]['internalcode'] );
372
	}
373
374
375
	/**
376
	 * Counts the number items that are available for the values of the given key.
377
	 *
378
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
379
	 * @param array|string $key Search key or list of key to aggregate items for
380
	 * @param string|null $value Search key for aggregating the value column
381
	 * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
382
	 * @return \Aimeos\Map List of the search keys as key and the number of counted items as value
383
	 */
384
	public function aggregate( \Aimeos\Base\Criteria\Iface $search, $key, string $value = null, string $type = null ) : \Aimeos\Map
385
	{
386
		/** mshop/order/manager/aggregate/mysql
387
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
388
		 *
389
		 * @see mshop/order/manager/aggregate/ansi
390
		 */
391
392
		/** mshop/order/manager/aggregate/ansi
393
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
394
		 *
395
		 * Groups all records by the values in the key column and counts their
396
		 * occurence. The matched records can be limited by the given criteria
397
		 * from the order database. The records must be from one of the sites
398
		 * that are configured via the context item. If the current site is part
399
		 * of a tree of sites, the statement can count all records from the
400
		 * current site and the complete sub-tree of sites.
401
		 *
402
		 * As the records can normally be limited by criteria from sub-managers,
403
		 * their tables must be joined in the SQL context. This is done by
404
		 * using the "internaldeps" property from the definition of the ID
405
		 * column of the sub-managers. These internal dependencies specify
406
		 * the JOIN between the tables and the used columns for joining. The
407
		 * ":joins" placeholder is then replaced by the JOIN strings from
408
		 * the sub-managers.
409
		 *
410
		 * To limit the records matched, conditions can be added to the given
411
		 * criteria object. It can contain comparisons like column names that
412
		 * must match specific values which can be combined by AND, OR or NOT
413
		 * operators. The resulting string of SQL conditions replaces the
414
		 * ":cond" placeholder before the statement is sent to the database
415
		 * server.
416
		 *
417
		 * This statement doesn't return any records. Instead, it returns pairs
418
		 * of the different values found in the key column together with the
419
		 * number of records that have been found for that key values.
420
		 *
421
		 * The SQL statement should conform to the ANSI standard to be
422
		 * compatible with most relational database systems. This also
423
		 * includes using double quotes for table and column names.
424
		 *
425
		 * @param string SQL statement for aggregating order items
426
		 * @since 2014.09
427
		 * @category Developer
428
		 * @see mshop/order/manager/insert/ansi
429
		 * @see mshop/order/manager/update/ansi
430
		 * @see mshop/order/manager/newid/ansi
431
		 * @see mshop/order/manager/delete/ansi
432
		 * @see mshop/order/manager/search/ansi
433
		 * @see mshop/order/manager/count/ansi
434
		 */
435
436
		/** mshop/order/manager/aggregateavg/mysql
437
		 * Computes the average of all values grouped by the key column and matched by the given criteria
438
		 *
439
		 * @param string SQL statement for aggregating the order items and computing the average value
440
		 * @since 2017.10
441
		 * @category Developer
442
		 * @see mshop/order/manager/aggregateavg/ansi
443
		 * @see mshop/order/manager/aggregate/mysql
444
		 */
445
446
		/** mshop/order/manager/aggregateavg/ansi
447
		 * Computes the average of all values grouped by the key column and matched by the given criteria
448
		 *
449
		 * @param string SQL statement for aggregating the order items and computing the average value
450
		 * @since 2017.10
451
		 * @category Developer
452
		 * @see mshop/order/manager/aggregate/ansi
453
		 */
454
455
		/** mshop/order/manager/aggregatesum/mysql
456
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
457
		 *
458
		 * @param string SQL statement for aggregating the order items and computing the sum
459
		 * @since 2017.10
460
		 * @category Developer
461
		 * @see mshop/order/manager/aggregatesum/ansi
462
		 * @see mshop/order/manager/aggregate/mysql
463
		 */
464
465
		/** mshop/order/manager/aggregatesum/ansi
466
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
467
		 *
468
		 * @param string SQL statement for aggregating the order items and computing the sum
469
		 * @since 2017.10
470
		 * @category Developer
471
		 * @see mshop/order/manager/aggregate/ansi
472
		 */
473
474
		$cfgkey = 'mshop/order/manager/aggregate';
475
		return $this->aggregateBase( $search, $key, $cfgkey, ['order'], $value, $type );
476
	}
477
478
479
	/**
480
	 * Removes old entries from the storage.
481
	 *
482
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
483
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
484
	 */
485
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
486
	{
487
		$path = 'mshop/order/manager/submanagers';
488
		$default = ['address', 'coupon', 'product', 'service', 'status'];
489
490
		foreach( $this->context()->config()->get( $path, $default ) as $domain ) {
491
			$this->object()->getSubManager( $domain )->clear( $siteids );
492
		}
493
494
		return $this->clearBase( $siteids, 'mshop/order/manager/delete' );
495
	}
496
497
498
	/**
499
	 * Creates a new empty item instance
500
	 *
501
	 * @param array $values Values the item should be initialized with
502
	 * @return \Aimeos\MShop\Order\Item\Iface New order item object
503
	 */
504
	public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
505
	{
506
		$context = $this->context();
507
		$locale = $context->locale();
508
509
		$price = \Aimeos\MShop::create( $context, 'price' )->create();
510
		$values['order.siteid'] = $values['order.siteid'] ?? $locale->getSiteId();
511
512
		$base = $this->createItemBase( $price, clone $locale, $values );
513
		\Aimeos\MShop::create( $context, 'plugin' )->register( $base, 'order' );
514
515
		return $base;
516
	}
517
518
519
	/**
520
	 * Removes multiple items.
521
	 *
522
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
523
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
524
	 */
525
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
526
	{
527
		/** mshop/order/manager/delete/mysql
528
		 * Deletes the items matched by the given IDs from the database
529
		 *
530
		 * @see mshop/order/manager/delete/ansi
531
		 */
532
533
		/** mshop/order/manager/delete/ansi
534
		 * Deletes the items matched by the given IDs from the database
535
		 *
536
		 * Removes the records specified by the given IDs from the order database.
537
		 * The records must be from the site that is configured via the
538
		 * context item.
539
		 *
540
		 * The ":cond" placeholder is replaced by the name of the ID column and
541
		 * the given ID or list of IDs while the site ID is bound to the question
542
		 * mark.
543
		 *
544
		 * The SQL statement should conform to the ANSI standard to be
545
		 * compatible with most relational database systems. This also
546
		 * includes using double quotes for table and column names.
547
		 *
548
		 * @param string SQL statement for deleting items
549
		 * @since 2014.03
550
		 * @category Developer
551
		 * @see mshop/order/manager/insert/ansi
552
		 * @see mshop/order/manager/update/ansi
553
		 * @see mshop/order/manager/newid/ansi
554
		 * @see mshop/order/manager/search/ansi
555
		 * @see mshop/order/manager/count/ansi
556
		 */
557
		$path = 'mshop/order/manager/delete';
558
559
		return $this->deleteItemsBase( $itemIds, $path );
560
	}
561
562
563
	/**
564
	 * Creates a search critera object
565
	 *
566
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
567
	 * @param bool $site TRUE to add site criteria to show orders with available products only
568
	 * @return \Aimeos\Base\Criteria\Iface New search criteria object
569
	 */
570
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
571
	{
572
		$search = parent::filter( $default );
573
		$context = $this->context();
574
575
		if( $default !== false ) {
576
			$search->add( ['order.customerid' => $context->user()] );
577
		}
578
579
		if( $site === true )
580
		{
581
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE;
582
			$search->add( $this->siteCondition( 'order.product.siteid', $level ) );
583
		}
584
585
		return $search;
586
	}
587
588
589
	/**
590
	 * Returns an order invoice item built from database values.
591
	 *
592
	 * @param string $id Unique id of the order invoice
593
	 * @param string[] $ref List of domains to fetch list items and referenced items for
594
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
595
	 * @return \Aimeos\MShop\Order\Item\Iface Returns order invoice item of the given id
596
	 * @throws \Aimeos\MShop\Order\Exception If item couldn't be found
597
	 */
598
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
599
	{
600
		return $this->getItemBase( 'order.id', $id, $ref, $default );
601
	}
602
603
604
	/**
605
	 * Returns the available manager types
606
	 *
607
	 * @param bool $withsub Return also the resource type of sub-managers if true
608
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
609
	 */
610
	public function getResourceType( bool $withsub = true ) : array
611
	{
612
		$path = 'mshop/order/manager/submanagers';
613
		$default = ['address', 'coupon', 'product', 'service', 'status'];
614
615
		return $this->getResourceTypeBase( 'order', $path, $default, $withsub );
616
	}
617
618
619
	/**
620
	 * Returns the attributes that can be used for searching.
621
	 *
622
	 * @param bool $withsub Return also attributes of sub-managers if true
623
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of search attribute items
624
	 */
625
	public function getSearchAttributes( bool $withsub = true ) : array
626
	{
627
		/** mshop/order/manager/submanagers
628
		 * List of manager names that can be instantiated by the order manager
629
		 *
630
		 * Managers provide a generic interface to the underlying storage.
631
		 * Each manager has or can have sub-managers caring about particular
632
		 * aspects. Each of these sub-managers can be instantiated by its
633
		 * parent manager using the getSubManager() method.
634
		 *
635
		 * The search keys from sub-managers can be normally used in the
636
		 * manager as well. It allows you to search for items of the manager
637
		 * using the search keys of the sub-managers to further limit the
638
		 * retrieved list of items.
639
		 *
640
		 * @param array List of sub-manager names
641
		 * @since 2014.03
642
		 * @category Developer
643
		 */
644
		$path = 'mshop/order/manager/submanagers';
645
		$default = ['address', 'coupon', 'product', 'service', 'status'];
646
647
		return $this->getSearchAttributesBase( $this->searchConfig, $path, $default, $withsub );
648
	}
649
650
651
	/**
652
	 * Creates a one-time order in the storage from the given invoice object.
653
	 *
654
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item with necessary values
655
	 * @param bool $fetch True if the new ID should be returned in the item
656
	 * @return \Aimeos\MShop\Order\Item\Iface $item Updated item including the generated ID
657
	 */
658
	protected function saveItem( \Aimeos\MShop\Order\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Order\Item\Iface
659
	{
660
		if( !$item->isModified() ) {
661
			return $this->saveBasket( $item );
662
		}
663
664
		if( empty( $item->getInvoiceNumber() ) && $item->getStatusPayment() >= \Aimeos\MShop\Order\Item\Base::PAY_AUTHORIZED )
665
		{
666
			try {
667
				$item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
668
			} catch( \Exception $e ) { // redo on transaction deadlock
669
				$item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
670
			}
671
		}
672
673
		$context = $this->context();
674
		$conn = $context->db( $this->getResourceName() );
675
676
		$id = $item->getId();
677
		$date = date( 'Y-m-d H:i:s' );
678
		$columns = $this->object()->getSaveAttributes();
679
680
		if( $id === null )
681
		{
682
			/** mshop/order/manager/insert/mysql
683
			 * Inserts a new order record into the database table
684
			 *
685
			 * @see mshop/order/manager/insert/ansi
686
			 */
687
688
			/** mshop/order/manager/insert/ansi
689
			 * Inserts a new order record into the database table
690
			 *
691
			 * Items with no ID yet (i.e. the ID is NULL) will be created in
692
			 * the database and the newly created ID retrieved afterwards
693
			 * using the "newid" SQL statement.
694
			 *
695
			 * The SQL statement must be a string suitable for being used as
696
			 * prepared statement. It must include question marks for binding
697
			 * the values from the order item to the statement before they are
698
			 * sent to the database server. The number of question marks must
699
			 * be the same as the number of columns listed in the INSERT
700
			 * statement. The order of the columns must correspond to the
701
			 * order in the save() method, so the correct values are
702
			 * bound to the columns.
703
			 *
704
			 * The SQL statement should conform to the ANSI standard to be
705
			 * compatible with most relational database systems. This also
706
			 * includes using double quotes for table and column names.
707
			 *
708
			 * @param string SQL statement for inserting records
709
			 * @since 2014.03
710
			 * @category Developer
711
			 * @see mshop/order/manager/update/ansi
712
			 * @see mshop/order/manager/newid/ansi
713
			 * @see mshop/order/manager/delete/ansi
714
			 * @see mshop/order/manager/search/ansi
715
			 * @see mshop/order/manager/count/ansi
716
			 */
717
			$path = 'mshop/order/manager/insert';
718
			$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

718
			$sql = $this->addSqlColumns( array_keys( $columns ), /** @scrutinizer ignore-type */ $this->getSqlConfig( $path ) );
Loading history...
719
		}
720
		else
721
		{
722
			/** mshop/order/manager/update/mysql
723
			 * Updates an existing order record in the database
724
			 *
725
			 * @see mshop/order/manager/update/ansi
726
			 */
727
728
			/** mshop/order/manager/update/ansi
729
			 * Updates an existing order record in the database
730
			 *
731
			 * Items which already have an ID (i.e. the ID is not NULL) will
732
			 * be updated in the database.
733
			 *
734
			 * The SQL statement must be a string suitable for being used as
735
			 * prepared statement. It must include question marks for binding
736
			 * the values from the order item to the statement before they are
737
			 * sent to the database server. The order of the columns must
738
			 * correspond to the order in the save() method, so the
739
			 * correct values are bound to the columns.
740
			 *
741
			 * The SQL statement should conform to the ANSI standard to be
742
			 * compatible with most relational database systems. This also
743
			 * includes using double quotes for table and column names.
744
			 *
745
			 * @param string SQL statement for updating records
746
			 * @since 2014.03
747
			 * @category Developer
748
			 * @see mshop/order/manager/insert/ansi
749
			 * @see mshop/order/manager/newid/ansi
750
			 * @see mshop/order/manager/delete/ansi
751
			 * @see mshop/order/manager/search/ansi
752
			 * @see mshop/order/manager/count/ansi
753
			 */
754
			$path = 'mshop/order/manager/update';
755
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false );
756
		}
757
758
		$idx = 1;
759
		$priceItem = $item->getPrice();
760
		$stmt = $this->getCachedStatement( $conn, $path, $sql );
761
762
		foreach( $columns as $name => $entry ) {
763
			$stmt->bind( $idx++, $item->get( $name ), $entry->getInternalType() );
764
		}
765
766
		$stmt->bind( $idx++, $item->getInvoiceNumber() );
767
		$stmt->bind( $idx++, $item->getChannel() );
768
		$stmt->bind( $idx++, $item->getDatePayment() );
769
		$stmt->bind( $idx++, $item->getDateDelivery() );
770
		$stmt->bind( $idx++, $item->getStatusDelivery(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
771
		$stmt->bind( $idx++, $item->getStatusPayment(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
772
		$stmt->bind( $idx++, $item->getRelatedId(), \Aimeos\Base\DB\Statement\Base::PARAM_STR );
773
		$stmt->bind( $idx++, $item->getCustomerId() );
774
		$stmt->bind( $idx++, $context->locale()->getSiteItem()->getCode() );
775
		$stmt->bind( $idx++, $item->locale()->getLanguageId() );
776
		$stmt->bind( $idx++, $priceItem->getCurrencyId() );
777
		$stmt->bind( $idx++, $priceItem->getValue() );
778
		$stmt->bind( $idx++, $priceItem->getCosts() );
779
		$stmt->bind( $idx++, $priceItem->getRebate() );
780
		$stmt->bind( $idx++, $priceItem->getTaxValue() );
781
		$stmt->bind( $idx++, $priceItem->getTaxFlag(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
782
		$stmt->bind( $idx++, $item->getCustomerReference() );
783
		$stmt->bind( $idx++, $item->getComment() );
784
		$stmt->bind( $idx++, $date ); // mtime
785
		$stmt->bind( $idx++, $context->editor() );
786
787
		if( $id !== null ) {
788
			$stmt->bind( $idx++, $context->locale()->getSiteId() . '%' );
789
			$stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT );
790
		} else {
791
			$stmt->bind( $idx++, $this->siteId( $item->getSiteId(), \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) );
792
			$stmt->bind( $idx++, $date ); // ctime
793
			$stmt->bind( $idx++, date( 'Y-m-d' ) ); // cdate
794
			$stmt->bind( $idx++, date( 'Y-m' ) ); // cmonth
795
			$stmt->bind( $idx++, date( 'Y-W' ) ); // cweek
796
			$stmt->bind( $idx++, date( 'w' ) ); // cwday
797
			$stmt->bind( $idx++, date( 'H' ) ); // chour
798
		}
799
800
		$stmt->execute()->finish();
801
802
		if( $id === null && $fetch === true )
803
		{
804
			/** mshop/order/manager/newid/mysql
805
			 * Retrieves the ID generated by the database when inserting a new record
806
			 *
807
			 * @see mshop/order/manager/newid/ansi
808
			 */
809
810
			/** mshop/order/manager/newid/ansi
811
			 * Retrieves the ID generated by the database when inserting a new record
812
			 *
813
			 * As soon as a new record is inserted into the database table,
814
			 * the database server generates a new and unique identifier for
815
			 * that record. This ID can be used for retrieving, updating and
816
			 * deleting that specific record from the table again.
817
			 *
818
			 * For MySQL:
819
			 *  SELECT LAST_INSERT_ID()
820
			 * For PostgreSQL:
821
			 *  SELECT currval('seq_mord_id')
822
			 * For SQL Server:
823
			 *  SELECT SCOPE_IDENTITY()
824
			 * For Oracle:
825
			 *  SELECT "seq_mord_id".CURRVAL FROM DUAL
826
			 *
827
			 * There's no way to retrive the new ID by a SQL statements that
828
			 * fits for most database servers as they implement their own
829
			 * specific way.
830
			 *
831
			 * @param string SQL statement for retrieving the last inserted record ID
832
			 * @since 2014.03
833
			 * @category Developer
834
			 * @see mshop/order/manager/insert/ansi
835
			 * @see mshop/order/manager/update/ansi
836
			 * @see mshop/order/manager/delete/ansi
837
			 * @see mshop/order/manager/search/ansi
838
			 * @see mshop/order/manager/count/ansi
839
			 */
840
			$path = 'mshop/order/manager/newid';
841
			$id = $this->newId( $conn, $path );
842
		}
843
844
		$item->setId( $id );
845
846
		$this->addStatus( $item );
847
848
		return $this->saveBasket( $item );
849
	}
850
851
852
	/**
853
	 * Searches for orders based on the given criteria.
854
	 *
855
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
856
	 * @param string[] $ref List of domains to fetch list items and referenced items for
857
	 * @param int|null &$total Number of items that are available in total
858
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Order\Item\Iface with ids as keys
859
	 */
860
	public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
861
	{
862
		$context = $this->context();
863
		$conn = $context->db( $this->getResourceName() );
864
865
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
866
		$localeManager = \Aimeos\MShop::create( $context, 'locale' );
867
868
		$map = $items = $custItems = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $items is dead and can be removed.
Loading history...
869
		$required = ['order'];
870
871
		/** mshop/order/manager/sitemode
872
		 * Mode how items from levels below or above in the site tree are handled
873
		 *
874
		 * By default, only items from the current site are fetched from the
875
		 * storage. If the ai-sites extension is installed, you can create a
876
		 * tree of sites. Then, this setting allows you to define for the
877
		 * whole order domain if items from parent sites are inherited,
878
		 * sites from child sites are aggregated or both.
879
		 *
880
		 * Available constants for the site mode are:
881
		 * * 0 = only items from the current site
882
		 * * 1 = inherit items from parent sites
883
		 * * 2 = aggregate items from child sites
884
		 * * 3 = inherit and aggregate items at the same time
885
		 *
886
		 * You also need to set the mode in the locale manager
887
		 * (mshop/locale/manager/sitelevel) to one of the constants.
888
		 * If you set it to the same value, it will work as described but you
889
		 * can also use different modes. For example, if inheritance and
890
		 * aggregation is configured the locale manager but only inheritance
891
		 * in the domain manager because aggregating items makes no sense in
892
		 * this domain, then items wil be only inherited. Thus, you have full
893
		 * control over inheritance and aggregation in each domain.
894
		 *
895
		 * @param int Constant from Aimeos\MShop\Locale\Manager\Base class
896
		 * @category Developer
897
		 * @since 2018.01
898
		 * @see mshop/locale/manager/sitelevel
899
		 */
900
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
901
		$level = $context->config()->get( 'mshop/order/manager/sitemode', $level );
902
903
		/** mshop/order/manager/search/mysql
904
		 * Retrieves the records matched by the given criteria in the database
905
		 *
906
		 * @see mshop/order/manager/search/ansi
907
		 */
908
909
		/** mshop/order/manager/search/ansi
910
		 * Retrieves the records matched by the given criteria in the database
911
		 *
912
		 * Fetches the records matched by the given criteria from the order
913
		 * database. The records must be from one of the sites that are
914
		 * configured via the context item. If the current site is part of
915
		 * a tree of sites, the SELECT statement can retrieve all records
916
		 * from the current site and the complete sub-tree of sites.
917
		 *
918
		 * As the records can normally be limited by criteria from sub-managers,
919
		 * their tables must be joined in the SQL context. This is done by
920
		 * using the "internaldeps" property from the definition of the ID
921
		 * column of the sub-managers. These internal dependencies specify
922
		 * the JOIN between the tables and the used columns for joining. The
923
		 * ":joins" placeholder is then replaced by the JOIN strings from
924
		 * the sub-managers.
925
		 *
926
		 * To limit the records matched, conditions can be added to the given
927
		 * criteria object. It can contain comparisons like column names that
928
		 * must match specific values which can be combined by AND, OR or NOT
929
		 * operators. The resulting string of SQL conditions replaces the
930
		 * ":cond" placeholder before the statement is sent to the database
931
		 * server.
932
		 *
933
		 * If the records that are retrieved should be ordered by one or more
934
		 * columns, the generated string of column / sort direction pairs
935
		 * replaces the ":order" placeholder. In case no ordering is required,
936
		 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
937
		 * markers is removed to speed up retrieving the records. Columns of
938
		 * sub-managers can also be used for ordering the result set but then
939
		 * no index can be used.
940
		 *
941
		 * The number of returned records can be limited and can start at any
942
		 * number between the begining and the end of the result set. For that
943
		 * the ":size" and ":start" placeholders are replaced by the
944
		 * corresponding values from the criteria object. The default values
945
		 * are 0 for the start and 100 for the size value.
946
		 *
947
		 * The SQL statement should conform to the ANSI standard to be
948
		 * compatible with most relational database systems. This also
949
		 * includes using double quotes for table and column names.
950
		 *
951
		 * @param string SQL statement for searching items
952
		 * @since 2014.03
953
		 * @category Developer
954
		 * @see mshop/order/manager/insert/ansi
955
		 * @see mshop/order/manager/update/ansi
956
		 * @see mshop/order/manager/newid/ansi
957
		 * @see mshop/order/manager/delete/ansi
958
		 * @see mshop/order/manager/count/ansi
959
		 */
960
		$cfgPathSearch = 'mshop/order/manager/search';
961
962
		/** mshop/order/manager/count/mysql
963
		 * Counts the number of records matched by the given criteria in the database
964
		 *
965
		 * @see mshop/order/manager/count/ansi
966
		 */
967
968
		/** mshop/order/manager/count/ansi
969
		 * Counts the number of records matched by the given criteria in the database
970
		 *
971
		 * Counts all records matched by the given criteria from the order
972
		 * database. The records must be from one of the sites that are
973
		 * configured via the context item. If the current site is part of
974
		 * a tree of sites, the statement can count all records from the
975
		 * current site and the complete sub-tree of sites.
976
		 *
977
		 * As the records can normally be limited by criteria from sub-managers,
978
		 * their tables must be joined in the SQL context. This is done by
979
		 * using the "internaldeps" property from the definition of the ID
980
		 * column of the sub-managers. These internal dependencies specify
981
		 * the JOIN between the tables and the used columns for joining. The
982
		 * ":joins" placeholder is then replaced by the JOIN strings from
983
		 * the sub-managers.
984
		 *
985
		 * To limit the records matched, conditions can be added to the given
986
		 * criteria object. It can contain comparisons like column names that
987
		 * must match specific values which can be combined by AND, OR or NOT
988
		 * operators. The resulting string of SQL conditions replaces the
989
		 * ":cond" placeholder before the statement is sent to the database
990
		 * server.
991
		 *
992
		 * Both, the strings for ":joins" and for ":cond" are the same as for
993
		 * the "search" SQL statement.
994
		 *
995
		 * Contrary to the "search" statement, it doesn't return any records
996
		 * but instead the number of records that have been found. As counting
997
		 * thousands of records can be a long running task, the maximum number
998
		 * of counted records is limited for performance reasons.
999
		 *
1000
		 * The SQL statement should conform to the ANSI standard to be
1001
		 * compatible with most relational database systems. This also
1002
		 * includes using double quotes for table and column names.
1003
		 *
1004
		 * @param string SQL statement for counting items
1005
		 * @since 2014.03
1006
		 * @category Developer
1007
		 * @see mshop/order/manager/insert/ansi
1008
		 * @see mshop/order/manager/update/ansi
1009
		 * @see mshop/order/manager/newid/ansi
1010
		 * @see mshop/order/manager/delete/ansi
1011
		 * @see mshop/order/manager/search/ansi
1012
		 */
1013
		$cfgPathCount = 'mshop/order/manager/count';
1014
1015
		$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount,
1016
			$required, $total, $level );
1017
1018
		try
1019
		{
1020
			while( ( $row = $results->fetch() ) !== null ) {
1021
				$map[$row['order.id']] = $row;
1022
			}
1023
		}
1024
		catch( \Exception $e )
1025
		{
1026
			$results->finish();
1027
			throw $e;
1028
		}
1029
1030
1031
		if( ( isset( $ref['customer'] ) || in_array( 'customer', $ref ) )
1032
			&& !( $ids = map( $map )->col( 'order.customerid' )->filter() )->empty()
1033
		) {
1034
			$manager = \Aimeos\MShop::create( $context, 'customer' );
1035
			$search = $manager->filter()->slice( 0, count( $ids ) )->add( ['customer.id' => $ids] );
1036
			$custItems = $manager->search( $search, $ref )->all();
1037
		}
1038
1039
		foreach( $map as $id => $row )
1040
		{
1041
			// don't use fromArray() or set*() methods to avoid recalculation of tax value
1042
			$price = $priceManager->create( [
1043
				'price.currencyid' => $row['order.currencyid'],
1044
				'price.value' => $row['order.price'],
1045
				'price.costs' => $row['order.costs'],
1046
				'price.rebate' => $row['order.rebate'],
1047
				'price.taxflag' => $row['order.taxflag'],
1048
				'price.taxvalue' => $row['order.taxvalue'],
1049
			] );
1050
1051
			// you may need the site object! take care!
1052
			$localeItem = $localeManager->create( [
1053
				'locale.currencyid' => $row['order.currencyid'],
1054
				'locale.languageid' => $row['order.languageid'],
1055
				'locale.siteid' => $row['order.siteid'],
1056
			] );
1057
1058
			$map[$id] = [$price, $localeItem, $row, $custItems[$row['order.customerid'] ?? null] ?? null];
1059
		}
1060
1061
		return $this->buildItems( $map, $ref );
1062
	}
1063
1064
1065
	/**
1066
	 * Returns a new manager for order extensions
1067
	 *
1068
	 * @param string $manager Name of the sub manager type in lower case
1069
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
1070
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions, e.g base, etc.
1071
	 */
1072
	public function getSubManager( string $manager, string $name = null ) : \Aimeos\MShop\Common\Manager\Iface
1073
	{
1074
		return $this->getSubManagerBase( 'order', $manager, $name );
1075
	}
1076
1077
1078
	/**
1079
	 * Adds the new payment and delivery values to the order status log.
1080
	 *
1081
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item object
1082
	 */
1083
	protected function addStatus( \Aimeos\MShop\Order\Item\Iface $item )
1084
	{
1085
		$statusManager = \Aimeos\MShop::create( $this->context(), 'order/status' );
1086
1087
		$statusItem = $statusManager->create();
1088
		$statusItem->setParentId( $item->getId() );
1089
1090
		if( ( $status = $item->get( '.statuspayment' ) ) !== null && $status != $item->getStatusPayment() )
1091
		{
1092
			$statusItem->setId( null )->setValue( $item->getStatusPayment() )
1093
				->setType( \Aimeos\MShop\Order\Item\Status\Base::STATUS_PAYMENT );
1094
1095
			$statusManager->save( $statusItem, false );
1096
		}
1097
1098
		if( ( $status = $item->get( '.statusdelivery' ) ) !== null && $status != $item->getStatusDelivery() )
1099
		{
1100
			$statusItem->setId( null )->setValue( $item->getStatusDelivery() )
1101
				->setType( \Aimeos\MShop\Order\Item\Status\Base::STATUS_DELIVERY );
1102
1103
			$statusManager->save( $statusItem, false );
1104
		}
1105
	}
1106
1107
1108
	/**
1109
	 * Creates the order base item objects from the map and adds the referenced items
1110
	 *
1111
	 * @param array $map Associative list of order base IDs as keys and list of price/locale/row as values
1112
	 * @param string[] $ref Domain items that should be added as well, e.g.
1113
	 *	"order/address", "order/coupon", "order/product", "order/service"
1114
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Order\Item\Iface with IDs as keys
1115
	 */
1116
	protected function buildItems( array $map, array $ref ) : \Aimeos\Map
1117
	{
1118
		$items = [];
1119
		$baseIds = array_keys( $map );
1120
		$addressMap = $couponMap = $productMap = $serviceMap = [];
1121
1122
		if( in_array( 'order/address', $ref ) ) {
1123
			$addressMap = $this->getAddresses( $baseIds );
1124
		}
1125
1126
		if( in_array( 'order/product', $ref ) ) {
1127
			$productMap = $this->getProducts( $baseIds );
1128
		}
1129
1130
		if( in_array( 'order/coupon', $ref ) ) {
1131
			$couponMap = $this->getCoupons( $baseIds, false, $productMap );
1132
		}
1133
1134
		if( in_array( 'order/service', $ref ) ) {
1135
			$serviceMap = $this->getServices( $baseIds );
1136
		}
1137
1138
		foreach( $map as $id => $list )
1139
		{
1140
			list( $price, $locale, $row, $custItem ) = $list;
1141
1142
			$addresses = $addressMap[$id] ?? [];
1143
			$coupons = $couponMap[$id] ?? [];
1144
			$products = $productMap[$id] ?? [];
1145
			$services = $serviceMap[$id] ?? [];
1146
1147
			$item = $this->createItemBase( $price, $locale, $row, $products, $addresses, $services, $coupons, $custItem );
1148
1149
			if( $item = $this->applyFilter( $item ) ) {
1150
				$items[$id] = $item;
1151
			}
1152
		}
1153
1154
		return map( $items );
1155
	}
1156
1157
1158
	/**
1159
	 * Creates a new invoice number for the passed order and site.
1160
	 *
1161
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item with necessary values
1162
	 * @return string Unique invoice number for the current site
1163
	 */
1164
	public function createInvoiceNumber( \Aimeos\MShop\Order\Item\Iface $item ) : string
1165
	{
1166
		$context = $this->context();
1167
		$siteId = $context->locale()->getSiteId();
1168
		$conn = $context->db( 'db-locale', true );
1169
1170
		try
1171
		{
1172
			$conn->query( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE' )->finish();
1173
			$conn->query( 'START TRANSACTION' )->finish();
1174
1175
			$result = $conn->query( 'SELECT "invoiceno" FROM "mshop_locale_site" where "siteid" = ?', [$siteId] );
1176
			$row = $result->fetch();
1177
			$result->finish();
1178
1179
			$conn->create( 'UPDATE "mshop_locale_site" SET "invoiceno" = "invoiceno" + 1 WHERE "siteid" = ?' )
1180
				->bind( 1, $siteId )->execute()->finish();
1181
1182
			$conn->query( 'COMMIT' )->finish();
1183
		}
1184
		catch( \Exception $e )
1185
		{
1186
			$conn->close();
1187
			throw $e;
1188
		}
1189
1190
		return $row['invoiceno'] ?? '';
1191
	}
1192
1193
1194
	/**
1195
	 * Returns a new and empty order base item (shopping basket).
1196
	 *
1197
	 * @param \Aimeos\MShop\Price\Item\Iface $price Default price of the basket (usually 0.00)
1198
	 * @param \Aimeos\MShop\Locale\Item\Iface $locale Locale item containing the site, language and currency
1199
	 * @param array $values Associative list of key/value pairs containing, e.g. the order or user ID
1200
	 * @param \Aimeos\MShop\Order\Item\Product\Iface[] $products List of ordered product items
1201
	 * @param \Aimeos\MShop\Order\Item\Address\Iface[] $addresses List of order address items
1202
	 * @param \Aimeos\MShop\Order\Item\Service\Iface[] $services List of order serviceitems
1203
	 * @param \Aimeos\MShop\Order\Item\Product\Iface[] $coupons Associative list of coupon codes as keys and items as values
1204
	 * @param \Aimeos\MShop\Customer\Item\Iface|null $custItem Customer item object if requested
1205
	 * @return \Aimeos\MShop\Order\Item\Iface Order base object
1206
	 */
1207
	protected function createItemBase( \Aimeos\MShop\Price\Item\Iface $price, \Aimeos\MShop\Locale\Item\Iface $locale,
1208
		array $values = [], array $products = [], array $addresses = [], array $services = [], array $coupons = [],
1209
		?\Aimeos\MShop\Customer\Item\Iface $custItem = null ) : \Aimeos\MShop\Order\Item\Iface
1210
	{
1211
		return new \Aimeos\MShop\Order\Item\Standard( $price, $locale,
1212
			$values, $products, $addresses, $services, $coupons, $custItem );
1213
	}
1214
1215
1216
	/**
1217
	 * Creates a new basket containing the items from the order excluding the coupons.
1218
	 * If the last parameter is ture, the items will be marked as new and
1219
	 * modified so an additional order is stored when the basket is saved.
1220
	 *
1221
	 * @param string $id Base ID of the order to load
1222
	 * @param array $ref Basket parts that should be loaded too
1223
	 * @param bool $fresh Create a new basket by copying the existing one and remove IDs
1224
	 * @param bool $default True to use default criteria, false for no limitation
1225
	 * @return \Aimeos\MShop\Order\Item\Iface Basket including all items
1226
	 */
1227
	public function load( string $id, array $ref = ['order/address', 'order/coupon', 'order/product', 'order/service'],
1228
		bool $fresh = false, bool $default = false ) : \Aimeos\MShop\Order\Item\Iface
1229
	{
1230
		$search = $this->object()->filter( $default );
1231
		$expr = [
1232
			$search->compare( '==', 'order.id', $id ),
1233
			$search->getConditions(),
1234
		];
1235
		$search->setConditions( $search->and( $expr ) );
1236
1237
		$context = $this->context();
1238
		$conn = $context->db( $this->getResourceName() );
1239
1240
		$sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
1241
		$sitelevel = $context->config()->get( 'mshop/order/manager/sitemode', $sitelevel );
1242
1243
		$cfgPathSearch = 'mshop/order/manager/search';
1244
		$cfgPathCount = 'mshop/order/manager/count';
1245
		$required = array( 'order' );
1246
		$total = null;
1247
1248
		$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $sitelevel );
1249
1250
		if( ( $row = $results->fetch() ) === null )
1251
		{
1252
			$msg = $this->context()->translate( 'mshop', 'Order item with order ID "%1$s" not found' );
1253
			throw new \Aimeos\MShop\Order\Exception( sprintf( $msg, $id ) );
1254
		}
1255
		$results->finish();
1256
1257
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
1258
		$localeManager = \Aimeos\MShop::create( $context, 'locale' );
1259
1260
		$price = $priceManager->create( [
1261
			'price.currencyid' => $row['order.currencyid'],
1262
			'price.value' => $row['order.price'],
1263
			'price.costs' => $row['order.costs'],
1264
			'price.rebate' => $row['order.rebate'],
1265
			'price.taxflag' => $row['order.taxflag'],
1266
			'price.taxvalue' => $row['order.taxvalue'],
1267
		] );
1268
1269
		// you may need the site object! take care!
1270
		$localeItem = $localeManager->create( [
1271
			'locale.languageid' => $row['order.languageid'],
1272
			'locale.currencyid' => $row['order.currencyid'],
1273
			'locale.siteid' => $row['order.siteid'],
1274
		] );
1275
1276
		if( $fresh === false ) {
1277
			$basket = $this->loadItems( $id, $price, $localeItem, $row, $ref );
1278
		} else {
1279
			$basket = $this->loadFresh( $id, $price, $localeItem, $row, $ref );
1280
		}
1281
1282
		return $basket;
1283
	}
1284
1285
1286
	/**
1287
	 * Saves the modified basket content
1288
	 *
1289
	 * @param \Aimeos\MShop\Order\Item\Iface $basket Basket content
1290
	 * @return \Aimeos\MShop\Order\Item\Iface Saved basket content
1291
	 */
1292
	protected function saveBasket( \Aimeos\MShop\Order\Item\Iface $basket ) : \Aimeos\MShop\Order\Item\Iface
1293
	{
1294
		$this->saveAddresses( $basket );
1295
		$this->saveServices( $basket );
1296
		$this->saveProducts( $basket );
1297
		$this->saveCoupons( $basket );
1298
1299
		return $basket;
1300
	}
1301
}
1302