Completed
Push — 1.0 ( 28768e...52cc48 )
by Morven
02:43
created

PricingExtension::getRoundedPriceAndTax()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 15
rs 10
1
<?php
2
3
namespace SilverCommerce\TaxAdmin;
4
5
use NumberFormatter;
6
use SilverStripe\i18n\i18n;
7
use SilverStripe\Forms\FieldList;
8
use SilverStripe\Forms\FieldGroup;
9
use SilverStripe\ORM\DataExtension;
10
use SilverStripe\Forms\DropdownField;
11
use SilverStripe\Forms\ReadonlyField;
12
use SilverCommerce\TaxAdmin\Model\TaxRate;
13
use SilverStripe\Core\Config\Configurable;
14
use SilverCommerce\TaxAdmin\Model\TaxCategory;
15
use SilverCommerce\TaxAdmin\Helpers\MathsHelper;
16
17
/**
18
 * Extension that handles the grind work of calculating Tax prices and rendering
19
 * prices for an extended object.
20
 */
21
class PricingExtension extends DataExtension
22
{
23
    use Configurable;
24
25
    /**
26
     * Default behaviour for price with tax (if current instance not set)
27
     *
28
     * @var boolean
29
     */
30
    private static $default_price_with_tax = false;
0 ignored issues
show
introduced by
The private property $default_price_with_tax is not used, and could be removed.
Loading history...
31
32
    /**
33
     * Default behaviour for adding the tax string to the rendered currency.
34
     *
35
     * @var boolean
36
     */
37
    private static $default_tax_string = false;
0 ignored issues
show
introduced by
The private property $default_tax_string is not used, and could be removed.
Loading history...
38
39
    /**
40
     * Default decimal precision used for rounding
41
     *
42
     * @var int
43
     */
44
    private static $default_precision = 2;
0 ignored issues
show
introduced by
The private property $default_precision is not used, and could be removed.
Loading history...
45
46
47
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
48
        'BasePrice' => 'Decimal'
49
    ];
50
51
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
52
        'TaxCategory' => TaxCategory::class,
53
        'TaxRate' => TaxRate::class
54
    ];
55
56
    private static $casting = [
0 ignored issues
show
introduced by
The private property $casting is not used, and could be removed.
Loading history...
57
        'CurrencySymbol'    => 'Varchar(1)',
58
        'Currency'          => 'Varchar(3)',
59
        "NoTaxPrice"        => "Decimal",
60
        "TaxAmount"         => "Decimal",
61
        "TaxPercentage"     => "Decimal",
62
        "PriceAndTax"       => "Decimal",
63
        'TaxString'         => 'Varchar',
64
        'RoundedNoTaxPrice' => 'Decimal',
65
        'RoundedTaxAmount'  => 'Decimal',
66
        'RoundedPriceAndTax'=> 'Decimal',
67
        'ShowPriceWithTax'  => 'Boolean',
68
        'ShowTaxString'     => 'Boolean',
69
        'FormattedPrice'    => 'Varchar',
70
        'NicePrice'         => 'HTMLText'
71
    ];
72
73
    private static $field_labels = [
0 ignored issues
show
introduced by
The private property $field_labels is not used, and could be removed.
Loading history...
74
        'BasePrice' => 'Price'
75
    ];
76
77
    /**
78
     * Filter the results returned by an extension
79
     *
80
     * @param mixed $results Possible results
81
     *
82
     * @return mixed
83
     */
84
    public function filterExtensionResults($results)
85
    {
86
        if (!empty($results) && is_array($results)) {
87
            $results = array_filter(
88
                $results,
89
                function ($v) {
90
                    return !is_null($v);
91
                }
92
            );
93
            if (is_array($results) && count($results) > 0) {
94
                return $results[0];
95
            }
96
        }
97
98
        return;
99
    }
100
101
    /**
102
     * Get should this field automatically show the price including TAX?
103
     *
104
     * @return boolean
105
     */
106
    public function getShowPriceWithTax()
107
    {
108
        return $this->config()->get('default_price_with_tax');
109
    }
110
111
112
    /**
113
     * Get if this field should add a "Tax String" (EG Includes VAT) to the rendered
114
     * currency?
115
     *
116
     * @return boolean|null
117
     */
118
    public function getShowTaxString()
119
    {
120
        return $this->config()->get('default_tax_string');
121
    }
122
123
    /**
124
     * Get current decimal precision for rounding
125
     *
126
     * @return int
127
     */
128
    public function getPrecision()
129
    {
130
        return $this->config()->get('default_precision');
131
    }
132
133
    /**
134
     * Return the currently available locale
135
     * 
136
     * @return string 
137
     */
138
    public function getLocale()
139
    {
140
        return i18n::get_locale();
141
    }
142
143
    /**
144
     * Get currency formatter
145
     *
146
     * @return NumberFormatter
147
     */
148
    public function getFormatter()
149
    {
150
        return NumberFormatter::create(
151
            $this->getOwner()->getLocale(),
152
            NumberFormatter::CURRENCY
153
        );
154
    }
155
156
    /**
157
     * Get a currency symbol from the current site local
158
     *
159
     * @return string
160
     */
161
    public function getCurrencySymbol()
162
    {
163
        return $this
164
            ->getOwner()
165
            ->getFormatter()
166
            ->getSymbol(NumberFormatter::CURRENCY_SYMBOL);
167
    }
168
169
    /**
170
     * Get ISO 4217 currency code from curent locale
171
     *
172
     * @return string
173
     */
174
    public function getCurrency()
175
    {
176
        return $this
177
            ->getOwner()
178
            ->getFormatter()
179
            ->getTextAttribute(NumberFormatter::CURRENCY_CODE);
180
    }
181
182
    /**
183
     * Shortcut to get the price of this product without tax
184
     *
185
     * @return float
186
     */
187
    public function getNoTaxPrice()
188
    {
189
        $price = $this->getOwner()->BasePrice;
190
        $result = $this->getOwner()->filterExtensionResults(
191
            $this->getOwner()->extend("updateNoTaxPrice", $price)
192
        );
193
194
        if (!empty($result)) {
195
            return $result;
196
        }
197
198
        return $price;
199
    }
200
201
    /**
202
     * Get the current amount rounded to the desired precision
203
     *
204
     * @return float
205
     */
206
    public function getRoundedNoTaxPrice()
207
    {
208
        return number_format(
209
            $this->getOwner()->BasePrice,
210
            $this->getOwner()->getPrecision()
211
        );
212
    }
213
214
    /**
215
     * Find a tax rate based on the selected ID, or revert to using the valid tax
216
     * from the current category
217
     *
218
     * @return \SilverCommerce\TaxAdmin\Model\TaxRate
219
     */
220
    public function getTaxRate()
221
    {
222
        $tax = TaxRate::get()->byID($this->getOwner()->TaxRateID);
223
224
        // If no tax explicity set, try to get from category
225
        if (empty($tax)) {
226
            $category = TaxCategory::get()->byID($this->getOwner()->TaxCategoryID);
227
228
            $tax = (!empty($category)) ? $category->ValidTax() : null ;
229
        }
230
231
        if (empty($tax)) {
232
            $tax = TaxRate::create();
233
            $tax->ID = -1;
234
        }
235
236
        return $tax;
237
    }
238
239
    /**
240
     * Get the percentage tax rate assotiated with this field
241
     *
242
     * @return float
243
     */
244
    public function getTaxPercentage()
245
    {
246
        $percent = $this->getOwner()->getTaxRate()->Rate;
247
248
        $result = $this->getOwner()->filterExtensionResults(
249
            $this->getOwner()->extend("updateTaxPercentage", $percent)
250
        );
251
252
        if (!empty($result)) {
253
            return $result;
254
        }
255
256
        return $percent;
257
    }
258
259
    /**
260
     * Get a final tax amount for this object. You can extend this
261
     * method using "UpdateTax" allowing third party modules to alter
262
     * tax amounts dynamically.
263
     *
264
     * @return float
265
     */
266
    public function getTaxAmount()
267
    {
268
        if (!$this->getOwner()->exists()) {
269
            return 0;
270
        }
271
272
        // Round using default rounding defined on MathsHelper
273
        $tax = MathsHelper::round(
274
            ($this->getOwner()->BasePrice / 100) * $this->getOwner()->TaxPercentage,
275
            4
276
        );
277
278
        $result = $this->getOwner()->filterExtensionResults(
279
            $this->getOwner()->extend("updateTaxAmount", $tax)
280
        );
281
282
        if (!empty($result)) {
283
            return $result;
284
        }
285
286
        return $tax;
287
    }
288
289
    /**
290
     * Get the tax amount rounded to the desired precision
291
     *
292
     * @return float
293
     */
294
    public function getRoundedTaxAmount()
295
    {
296
        return number_format(
297
            $this->getOwner()->TaxAmount,
298
            $this->getPrecision()
299
        );
300
    }
301
302
    /**
303
     * Get the Tax Rate object applied to this product
304
     *
305
     * @return float
306
     */
307
    public function getPriceAndTax()
308
    {
309
        $notax = $this->getOwner()->NoTaxPrice;
310
        $tax = $this->getOwner()->TaxAmount;
311
        $price = $notax + $tax;
312
313
        $result = $this->getOwner()->filterExtensionResults(
314
            $this->getOwner()->extend("updatePriceAndTax", $price)
315
        );
316
317
        if (!empty($result)) {
318
            return $result;
319
        }
320
321
        return $price;
322
    }
323
324
    /**
325
     * Get the price and tax amount rounded to the desired precision
326
     *
327
     * @return float
328
     */
329
    public function getRoundedPriceAndTax()
330
    {
331
        $amount = $this->getOwner()->RoundedNoTaxPrice;
332
        $tax = $this->getOwner()->RoundedTaxAmount;
333
        $price = $amount + $tax;
334
335
        $result = $this->getOwner()->filterExtensionResults(
336
            $this->getOwner()->extend("updateRoundedPriceAndTax", $price)
337
        );
338
339
        if (!empty($result)) {
340
            return $result;
341
        }
342
343
        return $price;
344
    }
345
346
    /**
347
     * Generate a string to go with the the product price. We can
348
     * overwrite the wording of this by using Silverstripes language
349
     * files
350
     *
351
     * @param bool|null $include_tax Should this include tax or not?
352
     *
353
     * @return string
354
     */
355
    public function getTaxString($include_tax = null)
356
    {
357
        $string = "";
358
        $rate = $this->getOwner()->getTaxRate();
359
360
        if (empty($include_tax)) {
361
            $include_tax = $this->getOwner()->ShowPriceWithTax;
362
        }
363
364
        if ($rate->exists() && $include_tax) {
365
            $string = _t(
366
                self::class . ".TaxIncludes",
367
                "inc. {title}",
368
                ["title" => $rate->Title]
369
            );
370
        } elseif ($rate->exists() && !$include_tax) {
371
            $string = _t(
372
                self::class . ".TaxExcludes",
373
                "ex. {title}",
374
                ["title" => $rate->Title]
375
            );
376
        }
377
    
378
        $result = $this->getOwner()->filterExtensionResults(
379
            $this->getOwner()->extend("updateTaxString", $string)
380
        );
381
382
        if (!empty($result)) {
383
            return $result;
384
        }
385
386
        return $string;
387
    }
388
389
    /**
390
     * Return a formatted price (based on locale)
391
     *
392
     * @param bool $include_tax Should the formatted price include tax?
393
     *
394
     * @return string
395
     */
396
    public function getFormattedPrice($include_tax = false)
397
    {
398
        $currency = $this->getOwner()->Currency;
399
        $formatter = $this->getOwner()->getFormatter();
400
401
        if ($include_tax) {
402
            $amount = $this->getOwner()->RoundedPriceAndTax;
403
        } else {
404
            $amount = $this->getOwner()->RoundedNoTaxPrice;
405
        }
406
407
        // Without currency, format as basic localised number
408
        if (!$currency) {
409
            return $formatter->format($amount);
410
        }
411
412
        return $formatter->formatCurrency($amount, $currency);
413
    }
414
415
    /**
416
     * Get nicely formatted currency (based on current locale)
417
     *
418
     * @return string
419
     */
420
    public function getNicePrice()
421
    {
422
        return $this->getOwner()->renderWith(__CLASS__ . "_NicePrice");
423
    }
424
425
    /**
426
     * Create a field group to hold tax info.
427
     * 
428
     * @return FieldList
429
     */
430
    public function updateCMSFields(FieldList $fields)
431
    {
432
        $fields->removeByName(['BasePrice', 'TaxCategoryID', 'TaxRateID']);
433
434
        $field = FieldGroup::create(
435
            $this->getOwner()->dbObject("BasePrice")->scaffoldFormField(''),
436
            DropdownField::create('TaxCategoryID', "", TaxCategory::get())
437
                ->setEmptyString(
438
                    _t(self::class . '.SelectTaxCategory', 'Select a Tax Category')
439
                ),
440
            ReadonlyField::create("PriceOr", "")
441
                ->addExtraClass("text-center")
442
                ->setValue(_t(self::class . '.OR', ' OR ')),
443
            DropdownField::create(
444
                'TaxRateID',
445
                "",
446
                TaxRate::get()
447
            )->setEmptyString(
448
                _t(self::class . '.SelectTaxRate', 'Select a Tax Rate')
449
            )
450
        )->setName('PriceFields')
451
        ->setTitle($this->getOwner()->fieldLabel('Price'));
452
453
        // If we have a content field, load these fields before
454
        $content = $fields->dataFieldByName('Content');
455
        $base_field = null;
456
457
        if (!empty($content)) {
458
            $base_field = 'Content';
459
        }
460
461
        $fields->addFieldToTab(
462
            'Root.Main',
463
            $field,
464
            $base_field
465
        );
466
    }
467
}
468