Passed
Push — master ( 64e7d7...5c5569 )
by Aimeos
04:35
created

Standard::bind()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 25
rs 9.6666
cc 2
nc 2
nop 3
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2024
6
 * @package MShop
7
 * @subpackage Order
8
 */
9
10
11
namespace Aimeos\MShop\Order\Manager;
12
13
14
/**
15
 * Default order manager implementation.
16
 *
17
 * @package MShop
18
 * @subpackage Order
19
 */
20
class Standard extends Base
21
	implements \Aimeos\MShop\Order\Manager\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
22
{
23
	use Session;
24
	use Update;
25
26
27
	private array $searchConfig = [
28
		'order.invoiceno' => [
29
			'label' => 'Invoice number',
30
			'internalcode' => 'invoiceno',
31
		],
32
		'order.relatedid' => [
33
			'label' => 'Related invoice ID',
34
			'internalcode' => 'relatedid',
35
		],
36
		'order.channel' => [
37
			'label' => 'Order channel',
38
			'internalcode' => 'channel',
39
		],
40
		'order.datepayment' => [
41
			'label' => 'Purchase date',
42
			'internalcode' => 'datepayment',
43
			'type' => 'datetime',
44
		],
45
		'order.datedelivery' => [
46
			'label' => 'Delivery date',
47
			'internalcode' => 'datedelivery',
48
			'type' => 'datetime',
49
		],
50
		'order.statusdelivery' => [
51
			'label' => 'Delivery status',
52
			'internalcode' => 'statusdelivery',
53
			'type' => 'int',
54
		],
55
		'order.statuspayment' => [
56
			'label' => 'Payment status',
57
			'internalcode' => 'statuspayment',
58
			'type' => 'int',
59
		],
60
		'order.customerid' => [
61
			'label' => 'Order customer ID',
62
			'internalcode' => 'customerid',
63
		],
64
		'order.customerref' => [
65
			'label' => 'Order customer reference',
66
			'internalcode' => 'customerref',
67
		],
68
		'order.comment' => [
69
			'label' => 'Order comment',
70
			'internalcode' => 'comment',
71
		],
72
	];
73
74
75
	/**
76
	 * Counts the number items that are available for the values of the given key.
77
	 *
78
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
79
	 * @param array|string $key Search key or list of key to aggregate items for
80
	 * @param string|null $value Search key for aggregating the value column
81
	 * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
82
	 * @return \Aimeos\Map List of the search keys as key and the number of counted items as value
83
	 */
84
	public function aggregate( \Aimeos\Base\Criteria\Iface $search, $key, string $value = null, string $type = null ) : \Aimeos\Map
85
	{
86
		/** mshop/order/manager/aggregate/mysql
87
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
88
		 *
89
		 * @see mshop/order/manager/aggregate/ansi
90
		 */
91
92
		/** mshop/order/manager/aggregate/ansi
93
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
94
		 *
95
		 * Groups all records by the values in the key column and counts their
96
		 * occurence. The matched records can be limited by the given criteria
97
		 * from the order database. The records must be from one of the sites
98
		 * that are configured via the context item. If the current site is part
99
		 * of a tree of sites, the statement can count all records from the
100
		 * current site and the complete sub-tree of sites.
101
		 *
102
		 * As the records can normally be limited by criteria from sub-managers,
103
		 * their tables must be joined in the SQL context. This is done by
104
		 * using the "internaldeps" property from the definition of the ID
105
		 * column of the sub-managers. These internal dependencies specify
106
		 * the JOIN between the tables and the used columns for joining. The
107
		 * ":joins" placeholder is then replaced by the JOIN strings from
108
		 * the sub-managers.
109
		 *
110
		 * To limit the records matched, conditions can be added to the given
111
		 * criteria object. It can contain comparisons like column names that
112
		 * must match specific values which can be combined by AND, OR or NOT
113
		 * operators. The resulting string of SQL conditions replaces the
114
		 * ":cond" placeholder before the statement is sent to the database
115
		 * server.
116
		 *
117
		 * This statement doesn't return any records. Instead, it returns pairs
118
		 * of the different values found in the key column together with the
119
		 * number of records that have been found for that key values.
120
		 *
121
		 * The SQL statement should conform to the ANSI standard to be
122
		 * compatible with most relational database systems. This also
123
		 * includes using double quotes for table and column names.
124
		 *
125
		 * @param string SQL statement for aggregating order items
126
		 * @since 2014.09
127
		 * @see mshop/order/manager/insert/ansi
128
		 * @see mshop/order/manager/update/ansi
129
		 * @see mshop/order/manager/newid/ansi
130
		 * @see mshop/order/manager/delete/ansi
131
		 * @see mshop/order/manager/search/ansi
132
		 * @see mshop/order/manager/count/ansi
133
		 */
134
135
		/** mshop/order/manager/aggregateavg/mysql
136
		 * Computes the average of all values grouped by the key column and matched by the given criteria
137
		 *
138
		 * @param string SQL statement for aggregating the order items and computing the average value
139
		 * @since 2017.10
140
		 * @see mshop/order/manager/aggregateavg/ansi
141
		 * @see mshop/order/manager/aggregate/mysql
142
		 */
143
144
		/** mshop/order/manager/aggregateavg/ansi
145
		 * Computes the average of all values grouped by the key column and matched by the given criteria
146
		 *
147
		 * @param string SQL statement for aggregating the order items and computing the average value
148
		 * @since 2017.10
149
		 * @see mshop/order/manager/aggregate/ansi
150
		 */
151
152
		/** mshop/order/manager/aggregatesum/mysql
153
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
154
		 *
155
		 * @param string SQL statement for aggregating the order items and computing the sum
156
		 * @since 2017.10
157
		 * @see mshop/order/manager/aggregatesum/ansi
158
		 * @see mshop/order/manager/aggregate/mysql
159
		 */
160
161
		/** mshop/order/manager/aggregatesum/ansi
162
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
163
		 *
164
		 * @param string SQL statement for aggregating the order items and computing the sum
165
		 * @since 2017.10
166
		 * @see mshop/order/manager/aggregate/ansi
167
		 */
168
169
		$cfgkey = 'mshop/order/manager/aggregate';
170
		return $this->aggregateBase( $search, $key, $cfgkey, ['order'], $value, $type );
171
	}
172
173
174
	/**
175
	 * Creates a new empty item instance
176
	 *
177
	 * @param array $values Values the item should be initialized with
178
	 * @return \Aimeos\MShop\Order\Item\Iface New order item object
179
	 */
180
	public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
181
	{
182
		$context = $this->context();
183
		$locale = $context->locale();
184
185
		$values['.locale'] = $values['.locale'] ?? $locale;
186
		$values['.price'] = $values['.price'] ?? \Aimeos\MShop::create( $context, 'price' )->create();
187
		$values['order.siteid'] = $values['order.siteid'] ?? $locale->getSiteId();
188
189
		$item = new \Aimeos\MShop\Order\Item\Standard( 'order.', $values );
190
		\Aimeos\MShop::create( $context, 'plugin' )->register( $item, 'order' );
191
192
		return $item;
193
	}
194
195
196
	/**
197
	 * Creates a new address item instance
198
	 *
199
	 * @param array $values Values the item should be initialized with
200
	 * @return \Aimeos\MShop\Order\Item\Address\Iface New order address item object
201
	 */
202
	public function createAddress( array $values = [] ) : \Aimeos\MShop\Order\Item\Address\Iface
203
	{
204
		return $this->object()->getSubManager( 'address' )->create( $values );
205
	}
206
207
208
	/**
209
	 * Creates a new coupon item instance
210
	 *
211
	 * @param array $values Values the item should be initialized with
212
	 * @return \Aimeos\MShop\Order\Item\Coupon\Iface New order coupon item object
213
	 */
214
	public function createCoupon( array $values = [] ) : \Aimeos\MShop\Order\Item\Coupon\Iface
215
	{
216
		return $this->object()->getSubManager( 'coupon' )->create( $values );
217
	}
218
219
220
	/**
221
	 * Creates a new product item instance
222
	 *
223
	 * @param array $values Values the item should be initialized with
224
	 * @return \Aimeos\MShop\Order\Item\Product\Iface New order product item object
225
	 */
226
	public function createProduct( array $values = [] ) : \Aimeos\MShop\Order\Item\Product\Iface
227
	{
228
		return $this->object()->getSubManager( 'product' )->create( $values );
229
	}
230
231
232
	/**
233
	 * Creates a new product attribute item instance
234
	 *
235
	 * @param array $values Values the item should be initialized with
236
	 * @return \Aimeos\MShop\Order\Item\Product\Attribute\Iface New order product attribute item object
237
	 */
238
	public function createProductAttribute( array $values = [] ) : \Aimeos\MShop\Order\Item\Product\Attribute\Iface
239
	{
240
		return $this->object()->getSubManager( 'product' )->getSubManager( 'attribute' )->create( $values );
241
	}
242
243
244
	/**
245
	 * Creates a new service item instance
246
	 *
247
	 * @param array $values Values the item should be initialized with
248
	 * @return \Aimeos\MShop\Order\Item\Service\Iface New order service item object
249
	 */
250
	public function createService( array $values = [] ) : \Aimeos\MShop\Order\Item\Service\Iface
251
	{
252
		return $this->object()->getSubManager( 'service' )->create( $values );
253
	}
254
255
256
	/**
257
	 * Creates a new service attribute item instance
258
	 *
259
	 * @param array $values Values the item should be initialized with
260
	 * @return \Aimeos\MShop\Order\Item\Service\Attribute\Iface New order service attribute item object
261
	 */
262
	public function createServiceAttribute( array $values = [] ) : \Aimeos\MShop\Order\Item\Service\Attribute\Iface
263
	{
264
		return $this->object()->getSubManager( 'service' )->getSubManager( 'attribute' )->create( $values );
265
	}
266
267
268
	/**
269
	 * Creates a new service transaction item instance
270
	 *
271
	 * @param array $values Values the item should be initialized with
272
	 * @return \Aimeos\MShop\Order\Item\Service\Transaction\Iface New order service transaction item object
273
	 */
274
	public function createServiceTransaction( array $values = [] ) : \Aimeos\MShop\Order\Item\Service\Transaction\Iface
275
	{
276
		return $this->object()->getSubManager( 'service' )->getSubManager( 'transaction' )->create( $values );
277
	}
278
279
280
	/**
281
	 * Creates a new status item instance
282
	 *
283
	 * @param array $values Values the item should be initialized with
284
	 * @return \Aimeos\MShop\Order\Item\Status\Iface New order item object
285
	 */
286
	public function createStatus( array $values = [] ) : \Aimeos\MShop\Order\Item\Status\Iface
287
	{
288
		return $this->object()->getSubManager( 'status' )->create( $values );
289
	}
290
291
292
	/**
293
	 * Creates a search critera object
294
	 *
295
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
296
	 * @param bool $site TRUE to add site criteria to show orders with available products only
297
	 * @return \Aimeos\Base\Criteria\Iface New search criteria object
298
	 */
299
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
300
	{
301
		$search = parent::filter( $default );
302
303
		if( $default !== false ) {
304
			$search->add( ['order.customerid' => $this->context()->user()] );
305
		}
306
307
		if( $site === true )
308
		{
309
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE;
310
			$search->add( $this->siteCondition( 'order.product.siteid', $level ) );
311
		}
312
313
		return $search;
314
	}
315
316
317
	/**
318
	 * Returns the additional column/search definitions
319
	 *
320
	 * @return array Associative list of column names as keys and items implementing \Aimeos\Base\Criteria\Attribute\Iface
321
	 */
322
	public function getSaveAttributes() : array
323
	{
324
		return $this->createAttributes( $this->searchConfig );
325
	}
326
327
328
	/**
329
	 * Returns the attributes that can be used for searching.
330
	 *
331
	 * @param bool $withsub Return also attributes of sub-managers if true
332
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of search attribute items
333
	 */
334
	public function getSearchAttributes( bool $withsub = true ) : array
335
	{
336
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
337
		$level = $this->context()->config()->get( 'mshop/order/manager/sitemode', $level );
338
		$expr = $this->siteString( 'mordst_cs."siteid"', $level );
339
340
		return array_replace( parent::getSearchAttributes( $withsub ), $this->createAttributes( [
341
			'order.sitecode' => [
342
				'label' => 'Order site code',
343
				'internalcode' => 'sitecode',
344
				'public' => false,
345
			],
346
			'order.languageid' => [
347
				'label' => 'Order language code',
348
				'internalcode' => 'langid',
349
			],
350
			'order.currencyid' => [
351
				'label' => 'Order currencyid code',
352
				'internalcode' => 'currencyid',
353
			],
354
			'order.price' => [
355
				'label' => 'Order price amount',
356
				'internalcode' => 'price',
357
			],
358
			'order.costs' => [
359
				'label' => 'Order shipping amount',
360
				'internalcode' => 'costs',
361
			],
362
			'order.rebate' => [
363
				'label' => 'Order rebate amount',
364
				'internalcode' => 'rebate',
365
			],
366
			'order.taxvalue' => [
367
				'label' => 'Order tax amount',
368
				'internalcode' => 'tax',
369
			],
370
			'order.taxflag' => [
371
				'label' => 'Order tax flag (0=net, 1=gross)',
372
				'internalcode' => 'taxflag',
373
			],
374
			'order.cdate' => [
375
				'label' => 'Create date',
376
				'internalcode' => 'cdate',
377
			],
378
			'order.cmonth' => [
379
				'label' => 'Create month',
380
				'internalcode' => 'cmonth',
381
			],
382
			'order.cweek' => [
383
				'label' => 'Create week',
384
				'internalcode' => 'cweek',
385
			],
386
			'order.cwday' => [
387
				'label' => 'Create weekday',
388
				'internalcode' => 'cwday',
389
			],
390
			'order.chour' => [
391
				'label' => 'Create hour',
392
				'internalcode' => 'chour',
393
			],
394
			'order:status' => [
395
				'code' => 'order:status()',
396
				'internalcode' => '( SELECT COUNT(mordst_cs."parentid")
397
					FROM "mshop_order_status" AS mordst_cs
398
					WHERE mord."id" = mordst_cs."parentid" AND ' . $expr . '
399
					AND mordst_cs."type" = $1 AND mordst_cs."value" IN ( $2 ) )',
400
				'label' => 'Number of order status items, parameter(<type>,<value>)',
401
				'type' => 'int',
402
				'public' => false,
403
			],
404
		] ) );
405
	}
406
407
408
	/**
409
	 * Binds additional values to the statement before execution.
410
	 *
411
	 * @param \Aimeos\MShop\Common\Item\Iface $item Item object
412
	 * @param \Aimeos\Base\DB\Statement\Iface $stmt Database statement object
413
	 * @param int $idx Current bind index
414
	 * @return \Aimeos\Base\DB\Statement\Iface Database statement object with bound values
415
	 */
416
	protected function bind( \Aimeos\MShop\Common\Item\Iface $item, \Aimeos\Base\DB\Statement\Iface $stmt, int &$idx ) : \Aimeos\Base\DB\Statement\Iface
417
	{
418
		$price = $item->getPrice();
419
		$context = $this->context();
420
421
		$stmt->bind( $idx++, $context->locale()->getSiteItem()->getCode() );
422
		$stmt->bind( $idx++, $item->locale()->getLanguageId() );
423
		$stmt->bind( $idx++, $price->getCurrencyId() );
424
		$stmt->bind( $idx++, $price->getValue() );
425
		$stmt->bind( $idx++, $price->getCosts() );
426
		$stmt->bind( $idx++, $price->getRebate() );
427
		$stmt->bind( $idx++, $price->getTaxValue() );
428
		$stmt->bind( $idx++, $price->getTaxFlag(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
429
430
		if( $item->getId() === null )
431
		{
432
			$date = date_create_from_format( 'Y-m-d H:i:s', $context->datetime() );
433
			$stmt->bind( $idx++, $date->format( 'Y-m-d' ) ); // cdate
434
			$stmt->bind( $idx++, $date->format( 'Y-m' ) ); // cmonth
435
			$stmt->bind( $idx++, $date->format( 'Y-W' ) ); // cweek
436
			$stmt->bind( $idx++, $date->format( 'w' ) ); // cwday
437
			$stmt->bind( $idx++, $date->format( 'G' ) ); // chour
438
		}
439
440
		return $stmt;
441
	}
442
443
444
	/**
445
	 * Creates a one-time order in the storage from the given invoice object.
446
	 *
447
	 * @param \Aimeos\MShop\Common\Item\Iface $item Order item with necessary values
448
	 * @param bool $fetch True if the new ID should be returned in the item
449
	 * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
450
	 */
451
	protected function saveBase( \Aimeos\MShop\Common\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Common\Item\Iface
452
	{
453
		if( !$item->isModified() )
454
		{
455
			$this->saveAddresses( $item )->saveServices( $item )->saveProducts( $item )->saveCoupons( $item )->saveStatuses( $item );
456
			return $item;
457
		}
458
459
		if( empty( $item->getInvoiceNumber() ) && $item->getStatusPayment() >= \Aimeos\MShop\Order\Item\Base::PAY_PENDING )
460
		{
461
			try {
462
				$item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
463
			} catch( \Exception $e ) { // redo on transaction deadlock
464
				$item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
465
			}
466
		}
467
468
		$item = parent::saveBase( $item, $fetch );
469
470
		$this->addStatus( $item )
471
			->saveStatuses( $item )
472
			->saveAddresses( $item )
473
			->saveServices( $item )
474
			->saveProducts( $item )
475
			->saveCoupons( $item );
476
477
		return $item;
478
	}
479
480
481
	/**
482
	 * Fetches the rows from the database statement and returns the list of items.
483
	 *
484
	 * @param \Aimeos\Base\DB\Result\Iface $stmt Database statement object
485
	 * @param array $ref List of domains whose items should be fetched too
486
	 * @param string $prefix Prefix for the property names
487
	 * @param array $attrs List of attributes that should be decoded
488
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Common\Item\Iface
489
	 */
490
	protected function fetch( \Aimeos\Base\DB\Result\Iface $results, array $ref, string $prefix = '', array $attrs = [] ) : \Aimeos\Map
491
	{
492
		$map = [];
493
		$context = $this->context();
494
495
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
496
		$localeManager = \Aimeos\MShop::create( $context, 'locale' );
497
498
		while( $row = $results->fetch() )
499
		{
500
			$row['.price'] = $priceManager->create( [
501
				'price.currencyid' => $row['order.currencyid'],
502
				'price.value' => $row['order.price'],
503
				'price.costs' => $row['order.costs'],
504
				'price.rebate' => $row['order.rebate'],
505
				'price.taxflag' => $row['order.taxflag'],
506
				'price.taxvalue' => $row['order.taxvalue'],
507
				'price.siteid' => $row['order.siteid'],
508
			] );
509
510
			// you may need the site object! take care!
511
			$row['.locale'] = $localeManager->create( [
512
				'locale.currencyid' => $row['order.currencyid'],
513
				'locale.languageid' => $row['order.languageid'],
514
				'locale.siteid' => $row['order.siteid'],
515
			] );
516
517
			$map[$row['order.id']] = $row;
518
		}
519
520
		$ids = array_keys( $map );
521
		$items = $addresses = $customers = $coupons = $products = $services = $statuses = [];
522
523
		if( $this->hasRef( $ref, 'customer' ) && !( $cids = map( $map )->col( 'order.customerid' )->filter() )->empty() )
524
		{
525
			$manager = \Aimeos\MShop::create( $this->context(), 'customer' );
526
			$search = $manager->filter()->slice( 0, 0x7fffffff )->add( ['customer.id' => $cids] );
527
			$customers = $manager->search( $search, $ref );
528
		}
529
530
		if( $this->hasRef( $ref, 'order/address' ) ) {
531
			$addresses = $this->getAddresses( $ids, $ref );
532
		}
533
534
		if( $this->hasRef( $ref, 'order/product' ) || $this->hasRef( $ref, 'order/coupon' ) ) {
535
			$products = $this->getProducts( $ids, $ref );
536
		}
537
538
		if( $this->hasRef( $ref, 'order/coupon' ) ) {
539
			$coupons = $this->getCoupons( $ids, $ref );
540
		}
541
542
		if( $this->hasRef( $ref, 'order/service' ) ) {
543
			$services = $this->getServices( $ids, $ref );
544
		}
545
546
		if( $this->hasRef( $ref, 'order/status' ) ) {
547
			$statuses = $this->getStatuses( $ids, $ref );
548
		}
549
550
		foreach( $map as $id => $row )
551
		{
552
			$row['.customer'] = $customers[$row['order.customerid']] ?? null;
553
			$row['.addresses'] = $addresses[$id] ?? [];
554
			$row['.coupons'] = $coupons[$id] ?? [];
555
			$row['.products'] = $products[$id] ?? [];
556
			$row['.services'] = $services[$id] ?? [];
557
			$row['.statuses'] = $statuses[$id] ?? [];
558
559
			if( $item = $this->applyFilter( $item = $this->create( $row ) ) ) {
560
				$items[$id] = $item;
561
			}
562
		}
563
564
		return map( $items );
565
	}
566
567
568
	/**
569
	 * Adds the new payment and delivery values to the order status log.
570
	 *
571
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item object
572
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
573
	 */
574
	protected function addStatus( \Aimeos\MShop\Order\Item\Iface $item ) : \Aimeos\MShop\Order\Manager\Iface
575
	{
576
		$object = $this->object();
577
578
		if( ( $status = $item->get( '.statuspayment' ) ) !== null && $status != $item->getStatusPayment() ) {
579
			$item->addStatus( $object->createStatus()->setType( 'status-payment' )->setValue( $item->getStatusPayment() ) );
580
		}
581
582
		if( ( $status = $item->get( '.statusdelivery' ) ) !== null && $status != $item->getStatusDelivery() ) {
583
			$item->addStatus( $object->createStatus()->setType( 'status-delivery' )->setValue( $item->getStatusDelivery() ) );
584
		}
585
586
		return $this;
587
	}
588
589
590
	/**
591
	 * Creates a new invoice number for the passed order and site.
592
	 *
593
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item with necessary values
594
	 * @return string Unique invoice number for the current site
595
	 */
596
	protected function createInvoiceNumber( \Aimeos\MShop\Order\Item\Iface $item ) : string
597
	{
598
		$context = $this->context();
599
		$siteId = $context->locale()->getSiteId();
600
		$conn = $context->db( 'db-locale', true );
601
602
		try
603
		{
604
			$conn->query( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE' )->finish();
605
			$conn->query( 'START TRANSACTION' )->finish();
606
607
			$result = $conn->query( 'SELECT "invoiceno" FROM "mshop_locale_site" where "siteid" = ?', [$siteId] );
608
			$row = $result->fetch();
609
			$result->finish();
610
611
			$conn->create( 'UPDATE "mshop_locale_site" SET "invoiceno" = "invoiceno" + 1 WHERE "siteid" = ?' )
612
				->bind( 1, $siteId )->execute()->finish();
613
614
			$conn->query( 'COMMIT' )->finish();
615
		}
616
		catch( \Exception $e )
617
		{
618
			$conn->close();
619
			throw $e;
620
		}
621
622
		return $row['invoiceno'] ?? '';
623
	}
624
625
626
	/**
627
	 * Returns the prefix for the item properties and search keys.
628
	 *
629
	 * @return string Prefix for the item properties and search keys
630
	 */
631
	protected function prefix() : string
632
	{
633
		return 'order.';
634
	}
635
636
637
	/** mshop/order/manager/name
638
	 * Class name of the used order manager implementation
639
	 *
640
	 * Each default manager can be replace by an alternative imlementation.
641
	 * To use this implementation, you have to set the last part of the class
642
	 * name as configuration value so the manager factory knows which class it
643
	 * has to instantiate.
644
	 *
645
	 * For example, if the name of the default class is
646
	 *
647
	 *  \Aimeos\MShop\Order\Manager\Standard
648
	 *
649
	 * and you want to replace it with your own version named
650
	 *
651
	 *  \Aimeos\MShop\Order\Manager\Mymanager
652
	 *
653
	 * then you have to set the this configuration option:
654
	 *
655
	 *  mshop/order/manager/name = Mymanager
656
	 *
657
	 * The value is the last part of your own class name and it's case sensitive,
658
	 * so take care that the configuration value is exactly named like the last
659
	 * part of the class name.
660
	 *
661
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
662
	 * characters are possible! You should always start the last part of the class
663
	 * name with an upper case character and continue only with lower case characters
664
	 * or numbers. Avoid chamel case names like "MyManager"!
665
	 *
666
	 * @param string Last part of the class name
667
	 * @since 2015.10
668
	 */
669
670
	/** mshop/order/manager/decorators/excludes
671
	 * Excludes decorators added by the "common" option from the order manager
672
	 *
673
	 * Decorators extend the functionality of a class by adding new aspects
674
	 * (e.g. log what is currently done), executing the methods of the underlying
675
	 * class only in certain conditions (e.g. only for logged in users) or
676
	 * modify what is returned to the caller.
677
	 *
678
	 * This option allows you to remove a decorator added via
679
	 * "mshop/common/manager/decorators/default" before they are wrapped
680
	 * around the order manager.
681
	 *
682
	 *  mshop/order/manager/decorators/excludes = array( 'decorator1' )
683
	 *
684
	 * This would remove the decorator named "decorator1" from the list of
685
	 * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
686
	 * "mshop/common/manager/decorators/default" for the order manager.
687
	 *
688
	 * @param array List of decorator names
689
	 * @since 2015.10
690
	 * @see mshop/common/manager/decorators/default
691
	 * @see mshop/order/manager/decorators/global
692
	 * @see mshop/order/manager/decorators/local
693
	 */
694
695
	/** mshop/order/manager/decorators/global
696
	 * Adds a list of globally available decorators only to the order manager
697
	 *
698
	 * Decorators extend the functionality of a class by adding new aspects
699
	 * (e.g. log what is currently done), executing the methods of the underlying
700
	 * class only in certain conditions (e.g. only for logged in users) or
701
	 * modify what is returned to the caller.
702
	 *
703
	 * This option allows you to wrap global decorators
704
	 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the order manager.
705
	 *
706
	 *  mshop/order/manager/decorators/global = array( 'decorator1' )
707
	 *
708
	 * This would add the decorator named "decorator1" defined by
709
	 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the order
710
	 * manager.
711
	 *
712
	 * @param array List of decorator names
713
	 * @since 2015.10
714
	 * @see mshop/common/manager/decorators/default
715
	 * @see mshop/order/manager/decorators/excludes
716
	 * @see mshop/order/manager/decorators/local
717
	 */
718
719
	/** mshop/order/manager/decorators/local
720
	 * Adds a list of local decorators only to the order manager
721
	 *
722
	 * Decorators extend the functionality of a class by adding new aspects
723
	 * (e.g. log what is currently done), executing the methods of the underlying
724
	 * class only in certain conditions (e.g. only for logged in users) or
725
	 * modify what is returned to the caller.
726
	 *
727
	 * This option allows you to wrap local decorators
728
	 * ("\Aimeos\MShop\Order\Manager\Decorator\*") around the order manager.
729
	 *
730
	 *  mshop/order/manager/decorators/local = array( 'decorator2' )
731
	 *
732
	 * This would add the decorator named "decorator2" defined by
733
	 * "\Aimeos\MShop\Order\Manager\Decorator\Decorator2" only to the order
734
	 * manager.
735
	 *
736
	 * @param array List of decorator names
737
	 * @since 2015.10
738
	 * @see mshop/common/manager/decorators/default
739
	 * @see mshop/order/manager/decorators/excludes
740
	 * @see mshop/order/manager/decorators/global
741
	 */
742
743
	/** mshop/order/manager/resource
744
	 * Name of the database connection resource to use
745
	 *
746
	 * You can configure a different database connection for each data domain
747
	 * and if no such connection name exists, the "db" connection will be used.
748
	 * It's also possible to use the same database connection for different
749
	 * data domains by configuring the same connection name using this setting.
750
	 *
751
	 * @param string Database connection name
752
	 * @since 2023.04
753
	 */
754
755
756
	/** mshop/order/manager/delete/mysql
757
	 * Deletes the items matched by the given IDs from the database
758
	 *
759
	 * @see mshop/order/manager/delete/ansi
760
	 */
761
762
	/** mshop/order/manager/delete/ansi
763
	 * Deletes the items matched by the given IDs from the database
764
	 *
765
	 * Removes the records specified by the given IDs from the order database.
766
	 * The records must be from the site that is configured via the
767
	 * context item.
768
	 *
769
	 * The ":cond" placeholder is replaced by the name of the ID column and
770
	 * the given ID or list of IDs while the site ID is bound to the question
771
	 * mark.
772
	 *
773
	 * The SQL statement should conform to the ANSI standard to be
774
	 * compatible with most relational database systems. This also
775
	 * includes using double quotes for table and column names.
776
	 *
777
	 * @param string SQL statement for deleting items
778
	 * @since 2015.10
779
	 * @see mshop/order/manager/insert/ansi
780
	 * @see mshop/order/manager/update/ansi
781
	 * @see mshop/order/manager/newid/ansi
782
	 * @see mshop/order/manager/search/ansi
783
	 * @see mshop/order/manager/count/ansi
784
	 */
785
786
	/** mshop/order/manager/submanagers
787
	 * List of manager names that can be instantiated by the order manager
788
	 *
789
	 * Managers provide a generic interface to the underlying storage.
790
	 * Each manager has or can have sub-managers caring about particular
791
	 * aspects. Each of these sub-managers can be instantiated by its
792
	 * parent manager using the getSubManager() method.
793
	 *
794
	 * The search keys from sub-managers can be normally used in the
795
	 * manager as well. It allows you to search for items of the manager
796
	 * using the search keys of the sub-managers to further limit the
797
	 * retrieved list of items.
798
	 *
799
	 * @param array List of sub-manager names
800
	 * @since 2015.10
801
	 */
802
803
	/** mshop/order/manager/insert/mysql
804
	 * Inserts a new order record into the database table
805
	 *
806
	 * @see mshop/order/manager/insert/ansi
807
	 */
808
809
	/** mshop/order/manager/insert/ansi
810
	 * Inserts a new order record into the database table
811
	 *
812
	 * Items with no ID yet (i.e. the ID is NULL) will be created in
813
	 * the database and the newly created ID retrieved afterwards
814
	 * using the "newid" SQL statement.
815
	 *
816
	 * The SQL statement must be a string suitable for being used as
817
	 * prepared statement. It must include question marks for binding
818
	 * the values from the order item to the statement before they are
819
	 * sent to the database server. The number of question marks must
820
	 * be the same as the number of columns listed in the INSERT
821
	 * statement. The catalog of the columns must correspond to the
822
	 * catalog in the save() method, so the correct values are
823
	 * bound to the columns.
824
	 *
825
	 * The SQL statement should conform to the ANSI standard to be
826
	 * compatible with most relational database systems. This also
827
	 * includes using double quotes for table and column names.
828
	 *
829
	 * @param string SQL statement for inserting records
830
	 * @since 2015.10
831
	 * @see mshop/order/manager/update/ansi
832
	 * @see mshop/order/manager/newid/ansi
833
	 * @see mshop/order/manager/delete/ansi
834
	 * @see mshop/order/manager/search/ansi
835
	 * @see mshop/order/manager/count/ansi
836
	 */
837
838
	/** mshop/order/manager/update/mysql
839
	 * Updates an existing order record in the database
840
	 *
841
	 * @see mshop/order/manager/update/ansi
842
	 */
843
844
	/** mshop/order/manager/update/ansi
845
	 * Updates an existing order record in the database
846
	 *
847
	 * Items which already have an ID (i.e. the ID is not NULL) will
848
	 * be updated in the database.
849
	 *
850
	 * The SQL statement must be a string suitable for being used as
851
	 * prepared statement. It must include question marks for binding
852
	 * the values from the order item to the statement before they are
853
	 * sent to the database server. The catalog of the columns must
854
	 * correspond to the catalog in the save() method, so the
855
	 * correct values are bound to the columns.
856
	 *
857
	 * The SQL statement should conform to the ANSI standard to be
858
	 * compatible with most relational database systems. This also
859
	 * includes using double quotes for table and column names.
860
	 *
861
	 * @param string SQL statement for updating records
862
	 * @since 2015.10
863
	 * @see mshop/order/manager/insert/ansi
864
	 * @see mshop/order/manager/newid/ansi
865
	 * @see mshop/order/manager/delete/ansi
866
	 * @see mshop/order/manager/search/ansi
867
	 * @see mshop/order/manager/count/ansi
868
	 */
869
870
	/** mshop/order/manager/newid/mysql
871
	 * Retrieves the ID generated by the database when inserting a new record
872
	 *
873
	 * @see mshop/order/manager/newid/ansi
874
	 */
875
876
	/** mshop/order/manager/newid/ansi
877
	 * Retrieves the ID generated by the database when inserting a new record
878
	 *
879
	 * As soon as a new record is inserted into the database table,
880
	 * the database server generates a new and unique identifier for
881
	 * that record. This ID can be used for retrieving, updating and
882
	 * deleting that specific record from the table again.
883
	 *
884
	 * For MySQL:
885
	 *  SELECT LAST_INSERT_ID()
886
	 * For PostgreSQL:
887
	 *  SELECT currval('seq_mrul_id')
888
	 * For SQL Server:
889
	 *  SELECT SCOPE_IDENTITY()
890
	 * For Oracle:
891
	 *  SELECT "seq_mrul_id".CURRVAL FROM DUAL
892
	 *
893
	 * There's no way to retrive the new ID by a SQL statements that
894
	 * fits for most database servers as they implement their own
895
	 * specific way.
896
	 *
897
	 * @param string SQL statement for retrieving the last inserted record ID
898
	 * @since 2015.10
899
	 * @see mshop/order/manager/insert/ansi
900
	 * @see mshop/order/manager/update/ansi
901
	 * @see mshop/order/manager/delete/ansi
902
	 * @see mshop/order/manager/search/ansi
903
	 * @see mshop/order/manager/count/ansi
904
	 */
905
906
	/** mshop/order/manager/sitemode
907
	 * Mode how items from levels below or above in the site tree are handled
908
	 *
909
	 * By default, only items from the current site are fetched from the
910
	 * storage. If the ai-sites extension is installed, you can create a
911
	 * tree of sites. Then, this setting allows you to define for the
912
	 * whole order domain if items from parent sites are inherited,
913
	 * sites from child sites are aggregated or both.
914
	 *
915
	 * Available constants for the site mode are:
916
	 * * 0 = only items from the current site
917
	 * * 1 = inherit items from parent sites
918
	 * * 2 = aggregate items from child sites
919
	 * * 3 = inherit and aggregate items at the same time
920
	 *
921
	 * You also need to set the mode in the locale manager
922
	 * (mshop/locale/manager/sitelevel) to one of the constants.
923
	 * If you set it to the same value, it will work as described but you
924
	 * can also use different modes. For example, if inheritance and
925
	 * aggregation is configured the locale manager but only inheritance
926
	 * in the domain manager because aggregating items makes no sense in
927
	 * this domain, then items wil be only inherited. Thus, you have full
928
	 * control over inheritance and aggregation in each domain.
929
	 *
930
	 * @param int Constant from Aimeos\MShop\Locale\Manager\Base class
931
	 * @since 2018.01
932
	 * @see mshop/locale/manager/sitelevel
933
	 */
934
935
	/** mshop/order/manager/search/mysql
936
	 * Retrieves the records matched by the given criteria in the database
937
	 *
938
	 * @see mshop/order/manager/search/ansi
939
	 */
940
941
	/** mshop/order/manager/search/ansi
942
	 * Retrieves the records matched by the given criteria in the database
943
	 *
944
	 * Fetches the records matched by the given criteria from the order
945
	 * database. The records must be from one of the sites that are
946
	 * configured via the context item. If the current site is part of
947
	 * a tree of sites, the SELECT statement can retrieve all records
948
	 * from the current site and the complete sub-tree of sites.
949
	 *
950
	 * As the records can normally be limited by criteria from sub-managers,
951
	 * their tables must be joined in the SQL context. This is done by
952
	 * using the "internaldeps" property from the definition of the ID
953
	 * column of the sub-managers. These internal dependencies specify
954
	 * the JOIN between the tables and the used columns for joining. The
955
	 * ":joins" placeholder is then replaced by the JOIN strings from
956
	 * the sub-managers.
957
	 *
958
	 * To limit the records matched, conditions can be added to the given
959
	 * criteria object. It can contain comparisons like column names that
960
	 * must match specific values which can be combined by AND, OR or NOT
961
	 * operators. The resulting string of SQL conditions replaces the
962
	 * ":cond" placeholder before the statement is sent to the database
963
	 * server.
964
	 *
965
	 * If the records that are retrieved should be cataloged by one or more
966
	 * columns, the generated string of column / sort direction pairs
967
	 * replaces the ":catalog" placeholder. In case no cataloging is required,
968
	 * the complete ORDER BY part including the "\/*-catalogby*\/...\/*catalogby-*\/"
969
	 * markers is removed to speed up retrieving the records. Columns of
970
	 * sub-managers can also be used for cataloging the result set but then
971
	 * no index can be used.
972
	 *
973
	 * The number of returned records can be limited and can start at any
974
	 * number between the begining and the end of the result set. For that
975
	 * the ":size" and ":start" placeholders are replaced by the
976
	 * corresponding values from the criteria object. The default values
977
	 * are 0 for the start and 100 for the size value.
978
	 *
979
	 * The SQL statement should conform to the ANSI standard to be
980
	 * compatible with most relational database systems. This also
981
	 * includes using double quotes for table and column names.
982
	 *
983
	 * @param string SQL statement for searching items
984
	 * @since 2015.10
985
	 * @see mshop/order/manager/insert/ansi
986
	 * @see mshop/order/manager/update/ansi
987
	 * @see mshop/order/manager/newid/ansi
988
	 * @see mshop/order/manager/delete/ansi
989
	 * @see mshop/order/manager/count/ansi
990
	 */
991
992
	/** mshop/order/manager/count/mysql
993
	 * Counts the number of records matched by the given criteria in the database
994
	 *
995
	 * @see mshop/order/manager/count/ansi
996
	 */
997
998
	/** mshop/order/manager/count/ansi
999
	 * Counts the number of records matched by the given criteria in the database
1000
	 *
1001
	 * Counts all records matched by the given criteria from the order
1002
	 * database. The records must be from one of the sites that are
1003
	 * configured via the context item. If the current site is part of
1004
	 * a tree of sites, the statement can count all records from the
1005
	 * current site and the complete sub-tree of sites.
1006
	 *
1007
	 * As the records can normally be limited by criteria from sub-managers,
1008
	 * their tables must be joined in the SQL context. This is done by
1009
	 * using the "internaldeps" property from the definition of the ID
1010
	 * column of the sub-managers. These internal dependencies specify
1011
	 * the JOIN between the tables and the used columns for joining. The
1012
	 * ":joins" placeholder is then replaced by the JOIN strings from
1013
	 * the sub-managers.
1014
	 *
1015
	 * To limit the records matched, conditions can be added to the given
1016
	 * criteria object. It can contain comparisons like column names that
1017
	 * must match specific values which can be combined by AND, OR or NOT
1018
	 * operators. The resulting string of SQL conditions replaces the
1019
	 * ":cond" placeholder before the statement is sent to the database
1020
	 * server.
1021
	 *
1022
	 * Both, the strings for ":joins" and for ":cond" are the same as for
1023
	 * the "search" SQL statement.
1024
	 *
1025
	 * Contrary to the "search" statement, it doesn't return any records
1026
	 * but instead the number of records that have been found. As counting
1027
	 * thousands of records can be a long running task, the maximum number
1028
	 * of counted records is limited for performance reasons.
1029
	 *
1030
	 * The SQL statement should conform to the ANSI standard to be
1031
	 * compatible with most relational database systems. This also
1032
	 * includes using double quotes for table and column names.
1033
	 *
1034
	 * @param string SQL statement for counting items
1035
	 * @since 2015.10
1036
	 * @see mshop/order/manager/insert/ansi
1037
	 * @see mshop/order/manager/update/ansi
1038
	 * @see mshop/order/manager/newid/ansi
1039
	 * @see mshop/order/manager/delete/ansi
1040
	 * @see mshop/order/manager/search/ansi
1041
	 */
1042
}
1043