Passed
Pull Request — master (#99)
by Nic
03:05
created

Variation::calculateFinalWeight()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 1
b 0
f 0
nc 4
nop 0
dl 0
loc 13
rs 10
1
<?php
2
3
namespace Dynamic\Foxy\Model;
4
5
use Bummzack\SortableFile\Forms\SortableUploadField;
6
use SilverStripe\Assets\File;
7
use SilverStripe\Forms\CheckboxField;
8
use SilverStripe\Forms\CurrencyField;
9
use SilverStripe\Forms\DropdownField;
10
use SilverStripe\Forms\FieldGroup;
11
use SilverStripe\Forms\FieldList;
12
use SilverStripe\Forms\NumericField;
13
use SilverStripe\Forms\ReadonlyField;
14
use SilverStripe\Forms\TextField;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\ORM\ManyManyList;
17
use SilverStripe\ORM\ValidationResult;
18
19
/**
20
 * Class Variation
21
 * @package Dynamic\Foxy\Model
22
 *
23
 * @property string $Title
24
 * @property bool $UseProductContent
25
 * @property string Content
26
 * @property float $WeightModifier
27
 * @property string $CodeModifier
28
 * @property float $PriceModifier
29
 * @property string $WeightModifierAction
30
 * @property string $CodeModifierAction
31
 * @property string $PriceModifierAction
32
 * @property bool $Available
33
 * @property int $Type
34
 * @property string $OptionModifierKey
35
 * @property int $SortOrder
36
 * @property float $FinalPrice
37
 * @property float $FinalWeight
38
 * @property string $FinalCode
39
 * @property int $VariationTypeID
40
 *
41
 * @method VariationType VariationType()
42
 *
43
 * @method ManyManyList Images()
44
 */
45
class Variation extends DataObject
46
{
47
    /**
48
     * @var string
49
     */
50
    private static $table_name = 'Variation';
51
52
    /**
53
     * @var string
54
     */
55
    private static $singular_name = 'Variation';
56
57
    /**
58
     * @var string
59
     */
60
    private static $plural_name = 'Variations';
61
62
    /**
63
     * @var string[]
64
     */
65
    private static $db = [
66
        'Title' => 'Varchar(255)',
67
        'UseProductContent' => 'Boolean',
68
        'Content' => 'HTMLText',
69
        'WeightModifier' => 'Decimal(9,3)',
70
        'CodeModifier' => 'Text',
71
        'PriceModifier' => 'Currency',
72
        'WeightModifierAction' => "Enum('Add,Subtract,Set', null)",
73
        'CodeModifierAction' => "Enum('Add,Subtract,Set', null)",
74
        'PriceModifierAction' => "Enum('Add,Subtract,Set', null)",
75
        'Available' => 'Boolean',
76
        'Type' => 'Int',
77
        'OptionModifierKey' => 'Varchar(255)',
78
        'SortOrder' => 'Int',
79
        'FinalPrice' => 'Currency',
80
        'FinalWeight' => 'Decimal(9,3)',
81
        'FinalCode' => 'Text',
82
    ];
83
84
    /**
85
     * @var string[]
86
     */
87
    private static $indexes = [
88
        'FinalPrice' => true,
89
        'FinalWeight' => true,
90
        'FinalCode' => true,
91
    ];
92
93
    /**
94
     * @var string[]
95
     */
96
    private static $has_one = [
97
        'VariationType' => VariationType::class,
98
    ];
99
100
    /**
101
     * @var array
102
     */
103
    private static $many_many = [
104
        'Images' => File::class,
105
    ];
106
107
    /**
108
     * @var \string[][]
109
     */
110
    private static $many_many_extraFields = [
111
        'Images' => [
112
            'SortOrder' => 'Int',
113
        ],
114
    ];
115
116
    /**
117
     * @var string[]
118
     */
119
    private static $owns = [
120
        'Images',
121
    ];
122
123
    /**
124
     * @var string[]
125
     */
126
    private static $default_sort = 'SortOrder';
127
128
    /**
129
     * The relation name was established before requests for videos.
130
     * The relation has subsequently been updated from Image::class to File::class
131
     * to allow for additional file types such as mp4
132
     *
133
     * @var array
134
     */
135
    private static $allowed_images_extensions = [
136
        'gif',
137
        'jpeg',
138
        'jpg',
139
        'png',
140
        'bmp',
141
        'ico',
142
        'mp4',
143
    ];
144
145
    /**
146
     * @return FieldList
147
     */
148
    public function getCMSFields()
149
    {
150
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
151
            $fields->removeByName([
152
                'Images',
153
                'WeightModifier',
154
                'CodeModifier',
155
                'PriceModifier',
156
                'WeightModifierAction',
157
                'CodeModifierAction',
158
                'PriceModifierAction',
159
                'Available',
160
                'Type',
161
                'OptionModifierKey',
162
                'SortOrder',
163
                'ProductID',
164
                'FinalPrice',
165
                'FinalWeight',
166
                'FinalCode',
167
            ]);
168
169
            $fields->insertBefore(
170
                'Content',
171
                CheckboxField::create('Available')
172
                    ->setTitle('Available for purchase')
173
            );
174
175
            $fields->insertBefore(
176
                'Content',
177
                $fields->dataFieldByName('VariationTypeID')
178
            );
179
180
            $fields->insertBefore(
181
                'Content',
182
                $fields->dataFieldByName('UseProductContent')
183
            );
184
185
            $content = $fields->dataFieldByName('Content');
186
187
            $content->hideUnless('UseProductContent')->isNotChecked()->end();
188
189
            if ($this->exists()) {
190
                $fields->addFieldToTab(
191
                    'Root.ProductModifications',
192
                    ReadonlyField::create('OptionModifierKey')
193
                        ->setTitle(_t('Variation.ModifierKey', 'Modifier Key'))
194
                );
195
            }
196
197
            if ($this->Product()->hasDatabaseField('Weight')) {
0 ignored issues
show
Bug introduced by
The method Product() does not exist on Dynamic\Foxy\Model\Variation. 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

197
            if ($this->/** @scrutinizer ignore-call */ Product()->hasDatabaseField('Weight')) {
Loading history...
198
                $fields->addFieldtoTab(
199
                    'Root.ProductModifications',
200
                    FieldGroup::create(
201
                        DropdownField::create(
202
                            'WeightModifierAction',
203
                            _t('Variation.WeightModifierAction', 'Weight Modification Type'),
204
                            [
205
                                'Add' => _t(
206
                                    'Variation.WeightAdd',
207
                                    'Add to Base Weight',
208
                                    'Add to weight'
209
                                ),
210
                                'Subtract' => _t(
211
                                    'Variation.WeightSubtract',
212
                                    'Subtract from Base Weight',
213
                                    'Subtract from weight'
214
                                ),
215
                                'Set' => _t('Variation.WeightSet', 'Set as a new Weight'),
216
                            ]
217
                        )
218
                            ->setEmptyString('')
219
                            ->setDescription(_t(
220
                                'Variation.WeightDescription',
221
                                'Does weight modify or replace base weight?'
222
                            )),
223
                        NumericField::create("WeightModifier")
224
                            ->setTitle(_t('Variation.WeightModifier', 'Weight'))
225
                            ->setScale(3)
226
                            ->setDescription(_t(
227
                                'Variation.WeightDescription',
228
                                'Only supports up to 3 decimal places'
229
                            ))->displayIf('WeightModifierAction')->isNotEmpty()->end(),
230
                        NumericField::create('FinalWeight')
231
                            ->setTitle('Final Modified Weight')
232
                            ->setDescription("Product's weight is {$this->Product()->Weight}")
233
                            ->performDisabledTransformation()
234
                    )->setTitle('Weight Modification')
235
                );
236
            }
237
238
            $fields->addFieldsToTab(
239
                'Root.ProductModifications',
240
                [
241
                    // Price Modifier Fields
242
                    //HeaderField::create('PriceHD', _t('Variation.PriceHD', 'Modify Price'), 4),
243
                    FieldGroup::create(
244
                        DropdownField::create(
245
                            'PriceModifierAction',
246
                            _t('Variation.PriceModifierAction', 'Price Modification Type'),
247
                            [
248
                                'Add' => _t(
249
                                    'Variation.PriceAdd',
250
                                    'Add to Base Price',
251
                                    'Add to price'
252
                                ),
253
                                'Subtract' => _t(
254
                                    'Variation.PriceSubtract',
255
                                    'Subtract from Base Price',
256
                                    'Subtract from price'
257
                                ),
258
                                'Set' => _t('Variation.PriceSet', 'Set as a new Price'),
259
                            ]
260
                        )
261
                            ->setEmptyString('')
262
                            ->setDescription(_t('Variation.PriceDescription', 'Does price modify or replace base price?')),
263
                        CurrencyField::create('PriceModifier')
264
                            ->setTitle(_t('Variation.PriceModifier', 'Price'))
265
                            ->displayIf('PriceModifierAction')->isNotEmpty()->end(),
266
                        CurrencyField::create('FinalPrice')
267
                            ->setTitle('Final Modified Price')
268
                            ->setDescription("Product's price is {$this->Product()->Price}")
269
                            ->performDisabledTransformation()
270
                    )->setTitle('Price Modifications'),
271
272
                    // Code Modifier Fields
273
                    //HeaderField::create('CodeHD', _t('Variation.CodeHD', 'Modify Code'), 4),
274
                    FieldGroup::create(
275
                        DropdownField::create(
276
                            'CodeModifierAction',
277
                            _t('Variation.CodeModifierAction', 'Code Modification Type'),
278
                            [
279
                                'Add' => _t(
280
                                    'Variation.CodeAdd',
281
                                    'Add to Base Code',
282
                                    'Add to code'
283
                                ),
284
                                'Subtract' => _t(
285
                                    'Variation.CodeSubtract',
286
                                    'Subtract from Base Code',
287
                                    'Subtract from code'
288
                                ),
289
                                'Set' => _t('Variation.CodeSet', 'Set as a new Code'),
290
                            ]
291
                        )
292
                            ->setEmptyString('')
293
                            ->setDescription(_t('Variation.CodeDescription', 'Does code modify or replace base code?')),
294
                        TextField::create('CodeModifier')
295
                            ->setTitle(_t('Variation.CodeModifier', 'Code'))
296
                            ->displayIf('CodeModifierAction')->isNotEmpty()->end(),
297
                        TextField::create('FinalCode')
298
                            ->setTitle('Final Modified Code')
299
                            ->setDescription("Product's code is {$this->Product()->Code}")
300
                            ->performDisabledTransformation()
301
                    )->setTitle('Code Modification'),
302
                ]
303
            );
304
305
            // Images tab
306
            $images = SortableUploadField::create('Images')
307
                ->setSortColumn('SortOrder')
308
                ->setIsMultiUpload(true)
309
                ->setAllowedExtensions($this->config()->get('allowed_images_extensions'))
310
                ->setFolderName('Uploads/Products/Images');
311
312
            $fields->addFieldsToTab('Root.Images', [
313
                $images,
314
            ]);
315
        });
316
317
        return parent::getCMSFields();
318
    }
319
320
    /**
321
     *
322
     */
323
    public function onBeforeWrite()
324
    {
325
        parent::onBeforeWrite();
326
327
        $modifierKeyField = 'OptionModifierKey';
328
        $this->{$modifierKeyField} = $this->getGeneratedValue();
329
330
        $codeModifierField = 'CodeModifier';
331
        switch ($this->CodeModifierAction) {
332
            case 'Subtract':
333
            case 'Add':
334
                if ($this->config()->get('trimAllWhitespace') == false) {
335
                    // trim the right of the code - some companies use spaces to denote options
336
                    $trimmed = rtrim($this->{$codeModifierField});
337
                    // replace duplicate spaces
338
                    $this->{$codeModifierField} = preg_replace('/\s+/', ' ', $trimmed);
339
                    break;
340
                }
341
            /* falls through */
342
            case 'Set':
343
                $trimmed = trim($this->{$codeModifierField});
344
                $this->{$codeModifierField} = preg_replace('/\s+/', ' ', $trimmed);
345
                break;
346
        }
347
348
        $this->FinalPrice = $this->calculateFinalPrice();
349
        $this->FinalCode = $this->calculateFinalCode();
350
351
        if ($this->Product()->hasDatabaseField('Weight')) {
352
            $this->FinalWeight = $this->calculateFinalWeight();
353
        }
354
    }
355
356
    /**
357
     * @return ValidationResult
358
     */
359
    public function validate()
360
    {
361
        $validate = parent::validate();
362
        $product = $this->Product();
363
364
        if (!$this->Title) {
365
            $validate->addFieldError('Title', 'A title is required');
366
        }
367
368
        if (!$this->VariationTypeID) {
369
            $validate->addFieldError('VariationTypeID', 'A variation type is required');
370
        }
371
372
        if ($this->PriceModifierAction == 'Subtract' && $this->PriceModifier > $product->Price) {
373
            $validate->addFieldError('PriceModifier', "You can't subtract more than the price of the product ({$product->Price})");
374
        }
375
376
        if ($this->WeightModifierAction == 'Subtract' && $this->WeightModifier > $product->Weight) {
377
            $validate->addFieldError('WeightModifier', "You can't subtract more than the weight of the product ({$product->Weight})");
378
        }
379
380
        return $validate;
381
    }
382
383
    /**
384
     * @return string
385
     */
386
    public function getGeneratedValue()
387
    {
388
        $modPrice = ($this->PriceModifier) ? (string)$this->PriceModifier : '0';
389
        $modPriceWithSymbol = self::getOptionModifierActionSymbol($this->PriceModifierAction) . $modPrice;
390
        $modWeight = ($this->WeightModifier) ? (string)$this->WeightModifier : '0';
391
        $modWeight = self::getOptionModifierActionSymbol($this->WeightModifierAction) . $modWeight;
392
        $modCode = self::getOptionModifierActionSymbol($this->CodeModifierAction) . $this->CodeModifier;
393
394
        return $this->Title . '{p' . $modPriceWithSymbol . '|w' . $modWeight . '|c' . $modCode . '}';
395
    }
396
397
    /**
398
     * @param $oma
399
     * @param bool $returnWithOnlyPlusMinus
400
     *
401
     * @return string
402
     */
403
    public static function getOptionModifierActionSymbol($oma, $returnWithOnlyPlusMinus = false)
404
    {
405
        switch ($oma) {
406
            case 'Subtract':
407
                $symbol = '-';
408
                break;
409
            case 'Set':
410
                $symbol = ($returnWithOnlyPlusMinus) ? '' : ':';
411
                break;
412
            default:
413
                $symbol = '+';
414
        }
415
416
        return $symbol;
417
    }
418
419
    /**
420
     * @return string
421
     */
422
    protected function getWeightModifierWithSymbol()
423
    {
424
        return $this->getOptionModifierActionSymbol($this->WeightModifierAction) . $this->WeightModifier;
425
    }
426
427
    /**
428
     * @return string
429
     */
430
    protected function getPriceModifierWithSymbol()
431
    {
432
        return $this->getOptionModifierActionSymbol($this->PriceModifierAction) . $this->PriceModifier;
433
    }
434
435
    /**
436
     * @return string
437
     */
438
    protected function getCodeModifierWithSymbol()
439
    {
440
        return $this->getOptionModifierActionSymbol($this->CodeModifierAction) . $this->CodeModifier;
441
    }
442
443
    /**
444
     * @return float
445
     */
446
    protected function calculateFinalPrice()
447
    {
448
        $product = $this->Product();// this relation is set by a developer's data extension
449
450
        if ($this->PriceModifierAction == 'Add') {
451
            return $this->PriceModifier + $product->Price;
452
        } elseif ($this->PriceModifierAction == 'Subtract') {
453
            return $product->Price - $this->PriceModifier;
454
        } elseif ($this->PriceModifierAction == 'Set') {
455
            return $this->PriceModifier;
456
        }
457
458
        return $product->Price;
459
    }
460
461
    /**
462
     * @return float
463
     */
464
    protected function calculateFinalWeight()
465
    {
466
        $product = $this->Product();// this relation is set by a developer's data extension
467
468
        if ($this->WeightModifierAction == 'Add') {
469
            return $this->WeightModifier + $product->Weight;
470
        } elseif ($this->WeightModifierAction == 'Subtract') {
471
            return $product->Weight - $this->WeightModifier;
472
        } elseif ($this->WeightModifierAction == 'Set') {
473
            return $this->WeightModifier;
474
        }
475
476
        return $product->Weight;
477
    }
478
479
    /**
480
     * @return string
481
     */
482
    protected function calculateFinalCode()
483
    {
484
        $product = $this->Product();// this relation is set by a developer's data extension
485
486
        if ($this->CodeModifierAction == 'Add') {
487
            return "{$product->Code}{$this->CodeModifier}";
488
        } elseif ($this->CodeModifierAction == 'Subtract') {
489
            return rtrim($product->Code, $this->CodeModifier);
490
        } elseif ($this->CodeModifierAction == 'Set') {
491
            return $this->CodeModifier;
492
        }
493
494
        return $product->Code;
495
    }
496
497
    /**
498
     * @return mixed|string
499
     */
500
    public function getGeneratedTitle()
501
    {
502
        $modPrice = ($this->PriceModifier) ? (string)$this->PriceModifier : '0';
503
        $title = $this->Title;
504
        $title .= ($this->PriceModifier != 0) ?
505
            ': (' . self::getOptionModifierActionSymbol(
506
                $this->PriceModifierAction,
507
                $returnWithOnlyPlusMinus = true
508
            ) . '$' . $modPrice . ')' :
509
            '';
510
511
        return $title;
512
    }
513
514
    /**
515
     * @return bool
516
     */
517
    public function getAvailability()
518
    {
519
        $available = $this->Available;
520
521
        $this->extend('updateAvailability', $available);
522
523
        return $available;
524
    }
525
}
526