Completed
Branch 1.3 (ca63b5)
by Morven
03:44
created

LineItemFactory::write()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 7
rs 10
1
<?php
2
3
namespace SilverCommerce\OrdersAdmin\Factory;
4
5
use SilverStripe\ORM\DataObject;
6
use SilverStripe\ORM\ValidationException;
7
use SilverStripe\Core\Config\Configurable;
8
use SilverStripe\Core\Injector\Injectable;
9
use SilverCommerce\OrdersAdmin\Model\LineItem;
10
use SilverCommerce\TaxableCurrency\DBTaxableCurrency;
0 ignored issues
show
Bug introduced by
The type SilverCommerce\TaxableCurrency\DBTaxableCurrency was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use SilverCommerce\OrdersAdmin\Model\LineItemCustomisation;
12
13
/**
14
 * Factory that handles setting up line items based on submitted data
15
 */
16
class LineItemFactory
17
{
18
    use Injectable, Configurable;
19
20
    const ITEM_CLASS = LineItem::class;
21
22
    const CUSTOM_CLASS = LineItemCustomisation::class;
23
24
    /**
25
     * Data that will be added to a customisation
26
     *
27
     * @var array
28
     */
29
    private static $custom_map = [
30
        "Title",
31
        "Value",
32
        "Price"
33
    ];
34
35
    /**
36
     * Should the stock stock levels be globally checked on items added?
37
     * Using this setting will ignore individual product "Stocked" settings.
38
     *
39
     * @var string
40
     */
41
    private static $force_check_stock = false;
42
43
    /**
44
     * Current line item
45
     *
46
     * @var DataObject
47
     */
48
    protected $item;
49
50
    /**
51
     * DataObject that will act as the product
52
     *
53
     * @var \SilverStripe\ORM\DataObject
54
     */
55
    protected $product;
56
57
    /**
58
     * The number of product to add/update for this line item
59
     *
60
     * @var int
61
     */
62
    protected $quantity;
63
64
    /**
65
     * Should this item be locked (cannot be updated, only removed)?
66
     *
67
     * @var bool
68
     */
69
    protected $lock;
70
71
    /**
72
     * List of customisation data that will need to be setup
73
     *
74
     * @var array
75
     */
76
    protected $customisations = [];
77
78
    /**
79
     * The name of the param used on product to determin if stock level should
80
     * be checked.
81
     *
82
     * @var string
83
     */
84
    protected $product_stocked_param = "Stocked";
85
86
    /**
87
     * The name of the param used on product to track Stock Level.
88
     *
89
     * @var string
90
     */
91
    protected $product_stock_param = "StockLevel";
92
93
    /**
94
     * The name of the param used on product to determin if item is deliverable
95
     *
96
     * @var string
97
     */
98
    protected $product_deliverable_param = "Deliverable";
99
100
    /**
101
     * Either find an existing line item (based on the submitted data),
102
     * or return a new one.
103
     *
104
     * @return DataObject
105
     */
106
    public function makeItem()
107
    {
108
        $custom = $this->getCustomisations();
109
        $class = self::ITEM_CLASS;
110
111
        // Setup initial line item
112
        $item = $class::create($this->getItemArray());
113
114
        // Find any item customisation associations
115
        $custom_association = null;
116
        $associations = array_merge(
117
            $item->hasMany(),
118
            $item->manyMany()
119
        );
120
121
        // Define association of item to customisations
122
        foreach ($associations as $key => $value) {
123
            $class = $value::create();
124
            if (is_a($class, self::CUSTOM_CLASS)) {
125
                $custom_association = $key;
126
                break;
127
            }
128
        }
129
130
        // Map any customisations to the current item
131
        if (isset($custom_association)) {
132
            foreach ($custom as $custom_data) {
133
                $customisation = $this->createCustomisation($custom_data);
134
                $customisation->write();
135
                $item->{$custom_association}()->add($customisation);
136
            }
137
        }
138
139
        // Setup Key
140
        $item->Key = $item->generateKey();
141
        $this->setItem($item);
142
        
143
        return $this;
144
    }
145
146
    /**
147
     * Update the current line item
148
     *
149
     * @return self
150
     */
151
    public function update()
152
    {
153
        $item = $this->getItem();
154
        $item->update($this->getItemArray());
155
        $item->Key = $item->generateKey();
156
        $this->setItem($item);
157
158
        return $this;
159
    }
160
161
    /**
162
     * Get an array of data for the line item
163
     *
164
     * @return array
165
     */
166
    protected function getItemArray()
167
    {
168
        $product = $this->getProduct();
169
        $qty = $this->getQuantity();
170
        $lock = $this->getLock();
171
172
        if (empty($product)) {
173
            throw new ValidationException(
174
                _t(
175
                    __CLASS__ . "NoProductSet",
176
                    "No product set"
177
                )
178
            );
179
        }
180
181
        // ensure that object price is something we can work with
182
        if (empty($product->BasePrice)) {
183
            throw new ValidationException("Product needs a 'BasePrice' param");
184
        }
185
186
        // Check if deliverable and stocked
187
        $stocked_param = $this->getProductStockedParam();
188
        $deliver_param = $this->getProductDeliverableParam();
189
190
        if (isset($product->{$deliver_param})) {
191
            $deliverable = $product->{$deliver_param};
192
        } else {
193
            $deliverable = true;
194
        }
195
196
        if (isset($product->{$stocked_param})) {
197
            $stocked = $product->{$stocked_param};
198
        } else {
199
            $stocked = false;
200
        }
201
202
        $price = $product->dbObject('Price');
0 ignored issues
show
Unused Code introduced by
The assignment to $price is dead and can be removed.
Loading history...
203
204
        // Setup initial line item
205
        return [
206
            "Title" => $product->Title,
207
            "BasePrice" => $product->BasePrice,
208
            "TaxRateID" => $product->getTaxRate()->ID,
209
            "StockID" => $product->StockID,
210
            "ProductClass" => $product->ClassName,
211
            "Quantity" => $qty,
212
            "Stocked" => $stocked,
213
            "Deliverable" => $deliverable,
214
            'Locked' => $lock
215
        ];
216
    }
217
218
    /**
219
     * Shortcut to get the item key from the item in this factory
220
     *
221
     * @return string
222
     */
223
    public function getKey()
224
    {
225
        $item = $this->getItem();
226
        if (!empty($item) && !empty($item->Key)) {
227
            return $item->Key;
228
        }
229
230
        return "";
231
    }
232
233
    /**
234
     * Create a customisation object to be added to the current order
235
     *
236
     * @param array $data An array of data to add to the customisation
237
     *
238
     * @return DataObject
239
     */
240
    protected function createCustomisation(array $data)
241
    {
242
        $mapped_data = [];
243
        $class = self::CUSTOM_CLASS;
244
245
        foreach ($data as $key => $value) {
246
            if (in_array($key, $this->config()->get('custom_map'))) {
247
                $mapped_data[$key] = $value;
248
            }
249
        }
250
251
        return $class::create($mapped_data);
252
    }
253
254
    /**
255
     * Check the available stock for the current line item. If stock checking
256
     * is disabled then returns true
257
     *
258
     * @return bool
259
     */
260
    public function checkStockLevel()
261
    {
262
        $qty = $this->getQuantity();
263
        $force = $this->config()->get('force_check_stock');
264
        $stock_item = $this->getItem()->findStockItem();
265
        $param = $this->getProductStockParam();
266
        $item = $this->getItem();
267
268
        // If we are checking stock and there is not enough, return false
269
        if (isset($stock_item)
270
            && ($force || isset($stock_item->{$param}) && $stock_item->{$param})
271
            && ($item->checkStockLevel($qty) < 0)
272
        ) {
273
            return false;
274
        }
275
276
        return true;
277
    }
278
279
    /**
280
     * Write the current line item
281
     *
282
     * @return self
283
     */
284
    public function write()
285
    {
286
        $item = $this->getItem();
287
        if (!empty($item)) {
288
            $item->write();
289
        }
290
        return $this;
291
    }
292
293
    /**
294
     * Remove the current item from the DB
295
     *
296
     * @return self
297
     */
298
    public function delete()
299
    {
300
        $item = $this->getItem();
301
        if (!empty($item)) {
302
            $item->delete();
303
        }
304
        return $this;
305
    }
306
307
    /**
308
     * Get current line item
309
     *
310
     * @return  DataObject
311
     */
312
    public function getItem()
313
    {
314
        return $this->item;
315
    }
316
317
    /**
318
     * Set current line item
319
     *
320
     * @param LineItem $item  Item to add
321
     * @param boolean  $setup Should we setup this factory based on the item?
322
     *
323
     * @return self
324
     */
325
    public function setItem(LineItem $item, $setup = true)
326
    {
327
        // If item has an assigned product, add it as well
328
        $this->item = $item;
329
330
        if (!$setup) {
331
            return $this;
332
        }
333
334
        $product = $item->FindStockItem();
335
        if (!empty($product)) {
336
            $this->setProduct($product);
337
        }
338
339
        $this->setQuantity($item->Quantity);
0 ignored issues
show
Bug Best Practice introduced by
The property Quantity does not exist on SilverCommerce\OrdersAdmin\Model\LineItem. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug introduced by
It seems like $item->Quantity can also be of type null; however, parameter $quantity of SilverCommerce\OrdersAdm...mFactory::setQuantity() does only seem to accept integer, 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

339
        $this->setQuantity(/** @scrutinizer ignore-type */ $item->Quantity);
Loading history...
340
        $this->setLock($item->Locked);
0 ignored issues
show
Bug Best Practice introduced by
The property Locked does not exist on SilverCommerce\OrdersAdmin\Model\LineItem. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug introduced by
It seems like $item->Locked can also be of type null; however, parameter $lock of SilverCommerce\OrdersAdm...eItemFactory::setLock() does only seem to accept boolean, 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

340
        $this->setLock(/** @scrutinizer ignore-type */ $item->Locked);
Loading history...
341
        $this->setCustomisations($item->Customisations()->toArray());
0 ignored issues
show
Bug introduced by
The method Customisations() does not exist on SilverCommerce\OrdersAdmin\Model\LineItem. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

341
        $this->setCustomisations($item->/** @scrutinizer ignore-call */ Customisations()->toArray());
Loading history...
342
343
        return $this;
344
    }
345
346
    /**
347
     * Get dataObject that will act as the product
348
     *
349
     * @return DataObject
350
     */
351
    public function getProduct()
352
    {
353
        return $this->product;
354
    }
355
356
    /**
357
     * Set dataObject that will act as the product
358
     *
359
     * @param DataObject $product product object
360
     *
361
     * @return self
362
     */
363
    public function setProduct(DataObject $product)
364
    {
365
        $this->product = $product;
366
        return $this;
367
    }
368
369
    /**
370
     * Get list of customisation data that will need to be setup
371
     *
372
     * @return array
373
     */
374
    public function getCustomisations()
375
    {
376
        return $this->customisations;
377
    }
378
379
    /**
380
     * Set list of customisation data that will need to be setup
381
     *
382
     * @param array $customisations customisation data
383
     *
384
     * @return self
385
     */
386
    public function setCustomisations(array $customisations)
387
    {
388
        $this->customisations = $customisations;
389
        return $this;
390
    }
391
392
    /**
393
     * Get the number of products to add/update for this line item
394
     *
395
     * @return int
396
     */
397
    public function getQuantity()
398
    {
399
        return $this->quantity;
400
    }
401
402
    /**
403
     * Set the number of products to add/update for this line item
404
     *
405
     * @param int $quantity number of products
406
     *
407
     * @return self
408
     */
409
    public function setQuantity(int $quantity)
410
    {
411
        $this->quantity = $quantity;
412
        return $this;
413
    }
414
415
    /**
416
     * Get should this item be locked (cannot be updated, only removed)?
417
     *
418
     * @return bool
419
     */
420
    public function getLock()
421
    {
422
        $item = $this->getItem();
423
        if (empty($this->lock) && isset($item)) {
424
            return $item->Locked;
425
        }
426
427
        return $this->lock;
428
    }
429
430
    /**
431
     * Set should this item be locked (cannot be updated, only removed)?
432
     *
433
     * @param bool $lock Is item locked?
434
     *
435
     * @return self
436
     */
437
    public function setLock(bool $lock)
438
    {
439
        $this->lock = $lock;
440
        return $this;
441
    }
442
443
    /**
444
     * Get name of stocked parameter
445
     *
446
     * @return string
447
     */
448
    public function getProductStockedParam()
449
    {
450
        return $this->product_stocked_param;
451
    }
452
453
    /**
454
     * Get name of stocked parameter
455
     *
456
     * @param string $param Param name.
457
     *
458
     * @return self
459
     */
460
    public function setProductStockedParam(string $param)
461
    {
462
        $this->product_stocked_param = $param;
463
        return $this;
464
    }
465
466
    /**
467
     * Get the name of the param used on product to track Stock Level.
468
     *
469
     * @return string
470
     */
471
    public function getProductStockParam()
472
    {
473
        return $this->product_stock_param;
474
    }
475
476
    /**
477
     * Set the name of the param used on product to track Stock Level.
478
     *
479
     * @param string $param param name
480
     *
481
     * @return self
482
     */
483
    public function setProductStockParam(string $param)
484
    {
485
        $this->product_stock_param = $param;
486
        return $this;
487
    }
488
489
    /**
490
     * Get the name of the param used on product to determin if item is deliverable
491
     *
492
     * @return string
493
     */
494
    public function getProductDeliverableParam()
495
    {
496
        return $this->product_deliverable_param;
497
    }
498
499
    /**
500
     * Set the name of the param used on product to determin if item is deliverable
501
     *
502
     * @param string $param The param name
503
     *
504
     * @return self
505
     */
506
    public function setProductDeliverableParam(string $param)
507
    {
508
        $this->product_deliverable_param = $param;
509
        return $this;
510
    }
511
}
512