Base   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 304
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 77
c 0
b 0
f 0
dl 0
loc 304
rs 10
wmc 30

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getConfigBE() 0 3 1
A object() 0 7 2
A getPriceByTaxRate() 0 29 5
A getCode() 0 3 1
A checkConfig() 0 4 1
A isAvailable() 0 3 1
A getConfigValue() 0 3 1
A getItem() 0 3 1
A __construct() 0 5 1
A createProduct() 0 18 2
A setObject() 0 4 1
A calcPrice() 0 9 2
A getConfigItems() 0 9 2
A context() 0 3 1
B createRebateProducts() 0 35 7
A checkConfigBE() 0 3 1
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2012
6
 * @copyright Aimeos (aimeos.org), 2015-2024
7
 * @package MShop
8
 * @subpackage Coupon
9
 */
10
11
12
namespace Aimeos\MShop\Coupon\Provider;
13
14
15
/**
16
 * Abstract model for coupons.
17
 *
18
 * @package MShop
19
 * @subpackage Coupon
20
 */
21
abstract class Base
22
	implements Iface, \Aimeos\Macro\Iface
23
{
24
	use \Aimeos\Macro\Macroable;
25
26
	private ?\Aimeos\MShop\Coupon\Provider\Iface $object = null;
27
	private \Aimeos\MShop\Coupon\Item\Iface $item;
28
	private \Aimeos\MShop\ContextIface $context;
29
	private string $code;
30
31
	/**
32
	 * Initializes the coupon model.
33
	 *
34
	 * @param \Aimeos\MShop\ContextIface $context Context object
35
	 * @param \Aimeos\MShop\Coupon\Item\Iface $item Coupon item to set
36
	 * @param string $code Coupon code entered by the customer
37
	 */
38
	public function __construct( \Aimeos\MShop\ContextIface $context, \Aimeos\MShop\Coupon\Item\Iface $item, string $code )
39
	{
40
		$this->context = $context;
41
		$this->item = $item;
42
		$this->code = $code;
43
	}
44
45
46
	/**
47
	 * Returns the price the discount should be applied to
48
	 *
49
	 * The result depends on the configured restrictions and it must be less or
50
	 * equal to the passed price.
51
	 *
52
	 * @param \Aimeos\MShop\Order\Item\Iface $base Basic order of the customer
53
	 * @return \Aimeos\MShop\Price\Item\Iface New price that should be used
54
	 */
55
	public function calcPrice( \Aimeos\MShop\Order\Item\Iface $base ) : \Aimeos\MShop\Price\Item\Iface
56
	{
57
		$price = \Aimeos\MShop::create( $this->context, 'price' )->create();
58
59
		foreach( $base->getProducts() as $product ) {
60
			$price = $price->addItem( $product->getPrice(), $product->getQuantity() );
61
		}
62
63
		return $price;
64
	}
65
66
67
	/**
68
	 * Checks the backend configuration attributes for validity.
69
	 *
70
	 * @param array $attributes Attributes added by the shop owner in the administraton interface
71
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
72
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
73
	 */
74
	public function checkConfigBE( array $attributes ) : array
75
	{
76
		return [];
77
	}
78
79
80
	/**
81
	 * Returns the configuration attribute definitions of the provider to generate a list of available fields and
82
	 * rules for the value of each field in the administration interface.
83
	 *
84
	 * @return array List of attribute definitions implementing \Aimeos\Base\Critera\Attribute\Iface
85
	 */
86
	public function getConfigBE() : array
87
	{
88
		return [];
89
	}
90
91
92
	/**
93
	 * Tests if a valid coupon code should be granted
94
	 *
95
	 * The result depends on the configured restrictions and it doesn't test
96
	 * again if the coupon or the code itself are still available.
97
	 *
98
	 * @param \Aimeos\MShop\Order\Item\Iface $base Basic order of the customer
99
	 * @return bool True of coupon can be granted, false if not
100
	 */
101
	public function isAvailable( \Aimeos\MShop\Order\Item\Iface $base ) : bool
102
	{
103
		return true;
104
	}
105
106
107
	/**
108
	 * Injects the reference of the outmost object
109
	 *
110
	 * @param \Aimeos\MShop\Coupon\Provider\Iface $object Reference to the outmost provider or decorator
111
	 * @return \Aimeos\MShop\Coupon\Provider\Iface Coupon object for chaining method calls
112
	 */
113
	public function setObject( \Aimeos\MShop\Coupon\Provider\Iface $object ) : \Aimeos\MShop\Coupon\Provider\Iface
114
	{
115
		$this->object = $object;
116
		return $this;
117
	}
118
119
120
	/**
121
	 * Checks required fields and the types of the given data map
122
	 *
123
	 * @param array $criteria Multi-dimensional associative list of criteria configuration
124
	 * @param array $map Values to check agains the criteria
125
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
126
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
127
	 */
128
	protected function checkConfig( array $criteria, array $map ) : array
129
	{
130
		$helper = new \Aimeos\MShop\Common\Helper\Config\Standard( $this->getConfigItems( $criteria ) );
131
		return $helper->check( $map );
132
	}
133
134
135
	/**
136
	 * Returns the coupon code the provider is responsible for.
137
	 *
138
	 * @return string Coupon code
139
	 */
140
	protected function getCode() : string
141
	{
142
		return $this->code;
143
	}
144
145
146
	/**
147
	 * Returns the criteria attribute items for the backend configuration
148
	 *
149
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of criteria attribute items
150
	 */
151
	protected function getConfigItems( array $configList ) : array
152
	{
153
		$list = [];
154
155
		foreach( $configList as $key => $config ) {
156
			$list[$key] = new \Aimeos\Base\Criteria\Attribute\Standard( $config );
157
		}
158
159
		return $list;
160
	}
161
162
163
	/**
164
	 * Returns the configuration value from the service item specified by its key.
165
	 *
166
	 * @param string $key Configuration key
167
	 * @param mixed $default Default value if configuration key isn't available
168
	 * @return mixed Value from service item configuration
169
	 */
170
	protected function getConfigValue( string $key, $default = null )
171
	{
172
		return $this->item->getConfigValue( $key, $default );
173
	}
174
175
176
	/**
177
	 * Returns the stored context object.
178
	 *
179
	 * @return \Aimeos\MShop\ContextIface Context object
180
	 */
181
	protected function context() : \Aimeos\MShop\ContextIface
182
	{
183
		return $this->context;
184
	}
185
186
187
	/**
188
	 * Returns the stored coupon item.
189
	 *
190
	 * @return \Aimeos\MShop\Coupon\Item\Iface Coupon item
191
	 */
192
	protected function getItem() : \Aimeos\MShop\Coupon\Item\Iface
193
	{
194
		return $this->item;
195
	}
196
197
198
	/**
199
	 * Returns the outmost decorator of the decorator stack
200
	 *
201
	 * @return \Aimeos\MShop\Coupon\Provider\Iface Outmost decorator object
202
	 */
203
	protected function object() : \Aimeos\MShop\Coupon\Provider\Iface
204
	{
205
		if( $this->object !== null ) {
206
			return $this->object;
207
		}
208
209
		return $this;
210
	}
211
212
213
	/**
214
	 * Creates an order product for the given product code
215
	 *
216
	 * @param string $prodcode Unique product code
217
	 * @param float $quantity Number of products
218
	 * @param string $stocktype Unique stock type code for the order product
219
	 * @return \Aimeos\MShop\Order\Item\Product\Iface Order product
220
	 */
221
	protected function createProduct( string $prodcode, float $quantity = 1,
222
		string $stocktype = 'default' ) : \Aimeos\MShop\Order\Item\Product\Iface
223
	{
224
		$productManager = \Aimeos\MShop::create( $this->context, 'product' );
225
		$product = $productManager->find( $prodcode, ['text', 'media', 'price'] );
226
227
		$priceManager = \Aimeos\MShop::create( $this->context, 'price' );
228
		$prices = $product->getRefItems( 'price', 'default', 'default' );
229
230
		if( !$prices->isEmpty() ) {
231
			$price = $priceManager->getLowestPrice( $prices, $quantity );
232
		} else {
233
			$price = $priceManager->create();
234
		}
235
236
		return \Aimeos\MShop::create( $this->context, 'order/product' )->create()
237
			->copyFrom( $product )->setQuantity( $quantity )->setStockType( $stocktype )->setPrice( $price )
238
			->setFlags( \Aimeos\MShop\Order\Item\Product\Base::FLAG_IMMUTABLE );
239
	}
240
241
242
	/**
243
	 * Creates the order products for monetary rebates.
244
	 *
245
	 * @param \Aimeos\MShop\Order\Item\Iface $base Basket object
246
	 * @param string $prodcode Unique product code
247
	 * @param float $rebate Rebate amount that should be granted, will contain the remaining rebate if not fully used
248
	 * @param float $quantity Number of products in basket
249
	 * @param string $stockType Unique code of the stock type the product is from
250
	 * @return \Aimeos\MShop\Order\Item\Product\Iface[] Order products with monetary rebates
251
	 */
252
	protected function createRebateProducts( \Aimeos\MShop\Order\Item\Iface $base,
253
		string $prodcode, float &$rebate, float $quantity = 1, string $stockType = 'default' ) : array
254
	{
255
		$orderProducts = [];
256
257
		if( ( $prices = $this->getPriceByTaxRate( $base ) )->isEmpty() ) {
258
			$prices = ['0.00' => \Aimeos\MShop::create( $this->context(), 'price' )->create()];
259
		}
260
261
		foreach( $prices as $taxrate => $price )
262
		{
263
			if( $rebate < 0.01 ) {
264
				break;
265
			}
266
267
			if( ( $amount = $price->getValue() + $price->getCosts() ) < 0.01 ) {
268
				continue;
269
			}
270
271
			if( $amount <= $rebate ) {
272
				$value = $price->getValue(); $costs = $price->getCosts(); $rebate -= $amount;
273
			} elseif( ( $amount = $price->getValue() ) <= $rebate ) {
274
				$value = $amount; $costs = $rebate - $amount; $rebate = 0;
275
			} else {
276
				$value = $rebate; $costs = 0; $rebate = 0;
277
			}
278
279
			$orderProduct = $this->createProduct( $prodcode, $quantity, $stockType );
280
			$price = $orderProduct->getPrice()->setTaxRate( $taxrate )
281
				->setValue( -$value )->setCosts( -$costs )->setRebate( $value + $costs );
282
283
			$orderProducts[] = $orderProduct->setPrice( $price );
284
		}
285
286
		return $orderProducts;
287
	}
288
289
290
	/**
291
	 * Returns a list of tax rates and their price items for the given basket.
292
	 *
293
	 * @param \Aimeos\MShop\Order\Item\Iface $basket Basket containing the products, services, etc.
294
	 * @return \Aimeos\Map Associative list of tax rates as key and price items implementing \Aimeos\MShop\Price\Item\Iface
295
	 */
296
	protected function getPriceByTaxRate( \Aimeos\MShop\Order\Item\Iface $basket ) : \Aimeos\Map
297
	{
298
		$prices = map();
299
		$manager = \Aimeos\MShop::create( $this->context(), 'price' );
300
301
		$map = $basket->getCoupons();
302
		$products = $map[$this->getCode()] ?? [];
303
304
		foreach( $basket->getProducts() as $key => $item )
305
		{
306
			if( !in_array( $item, $products, true ) )
307
			{
308
				$price = $item->getPrice();
309
				$rate = $price->getTaxRate();
310
				$prices[$rate] = $prices->get( $rate, $manager->create() )->addItem( $price, $item->getQuantity() );
311
			}
312
		}
313
314
		foreach( $basket->getServices() as $services )
315
		{
316
			foreach( $services as $item )
317
			{
318
				$price = $item->getPrice();
319
				$rate = $price->getTaxRate();
320
				$prices[$rate] = $prices->get( $rate, $manager->create() )->addItem( $price );
321
			}
322
		}
323
324
		return $prices->krsort();
325
	}
326
}
327