Passed
Push — master ( 08267b...db956f )
by Jan
04:49
created

Pricedetail::getPriceRelatedQuantity()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 4
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 9
rs 9.6111
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published
9
 * by the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
declare(strict_types=1);
22
23
/**
24
 * part-db version 0.1
25
 * Copyright (C) 2005 Christoph Lechner
26
 * http://www.cl-projects.de/.
27
 *
28
 * part-db version 0.2+
29
 * Copyright (C) 2009 K. Jacobs and others (see authors.php)
30
 * http://code.google.com/p/part-db/
31
 *
32
 * Part-DB Version 0.4+
33
 * Copyright (C) 2016 - 2019 Jan Böhmer
34
 * https://github.com/jbtronics
35
 *
36
 * This program is free software; you can redistribute it and/or
37
 * modify it under the terms of the GNU General Public License
38
 * as published by the Free Software Foundation; either version 2
39
 * of the License, or (at your option) any later version.
40
 *
41
 * This program is distributed in the hope that it will be useful,
42
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
44
 * GNU General Public License for more details.
45
 *
46
 * You should have received a copy of the GNU General Public License
47
 * along with this program; if not, write to the Free Software
48
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
49
 */
50
51
namespace App\Entity\PriceInformations;
52
53
use App\Entity\Base\AbstractDBElement;
54
use App\Entity\Base\TimestampTrait;
55
use App\Entity\Contracts\TimeStampableInterface;
56
use App\Validator\Constraints\BigDecimal\BigDecimalPositive;
57
use App\Validator\Constraints\Selectable;
58
use Brick\Math\BigDecimal;
59
use Brick\Math\RoundingMode;
60
use DateTime;
61
use Doctrine\ORM\Mapping as ORM;
62
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
63
use Symfony\Component\Validator\Constraints as Assert;
64
65
/**
66
 * Class Pricedetail.
67
 *
68
 * @ORM\Entity()
69
 * @ORM\Table("`pricedetails`")
70
 * @ORM\HasLifecycleCallbacks()
71
 * @UniqueEntity(fields={"min_discount_quantity", "orderdetail"})
72
 */
73
class Pricedetail extends AbstractDBElement implements TimeStampableInterface
74
{
75
    use TimestampTrait;
76
77
    public const PRICE_PRECISION = 5;
78
79
    /**
80
     * @var string The price related to the detail. (Given in the selected currency)
81
     * @ORM\Column(type="big_decimal", precision=11, scale=5)
82
     * @BigDecimalPositive()
83
     */
84
    protected $price;
85
86
    /**
87
     * @var ?Currency The currency used for the current price information.
88
     *                If this is null, the global base unit is assumed.
89
     * @ORM\ManyToOne(targetEntity="Currency", inversedBy="pricedetails")
90
     * @ORM\JoinColumn(name="id_currency", referencedColumnName="id", nullable=true)
91
     * @Selectable()
92
     */
93
    protected $currency;
94
95
    /**
96
     * @var float
97
     * @ORM\Column(type="float")
98
     * @Assert\Positive()
99
     */
100
    protected $price_related_quantity = 1.0;
101
102
    /**
103
     * @var float
104
     * @ORM\Column(type="float")
105
     * @Assert\Positive()
106
     */
107
    protected $min_discount_quantity = 1.0;
108
109
    /**
110
     * @var bool
111
     * @ORM\Column(type="boolean")
112
     */
113
    protected $manual_input = true;
114
115
    /**
116
     * @var Orderdetail|null
117
     * @ORM\ManyToOne(targetEntity="Orderdetail", inversedBy="pricedetails")
118
     * @ORM\JoinColumn(name="orderdetails_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
119
     * @Assert\NotNull()
120
     */
121
    protected $orderdetail;
122
123
    public function __construct()
124
    {
125
        $this->price = BigDecimal::zero()->toScale(self::PRICE_PRECISION);
126
    }
127
128
    public function __clone()
129
    {
130
        if ($this->id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
131
            $this->addedDate = null;
132
        }
133
        parent::__clone();
134
    }
135
136
    /**
137
     * Helper for updating the timestamp. It is automatically called by doctrine before persisting.
138
     *
139
     * @ORM\PrePersist
140
     * @ORM\PreUpdate
141
     */
142
    public function updateTimestamps(): void
143
    {
144
        $this->lastModified = new DateTime('now');
145
        if (null === $this->addedDate) {
146
            $this->addedDate = new DateTime('now');
147
        }
148
149
        if ($this->orderdetail instanceof Orderdetail) {
150
            $this->orderdetail->updateTimestamps();
151
        }
152
    }
153
154
    /********************************************************************************
155
     *
156
     *   Getters
157
     *
158
     *********************************************************************************/
159
160
    /**
161
     *  Get the orderdetail to which this pricedetail belongs to this pricedetails.
162
     *
163
     * @return Orderdetail|null the orderdetail this price belongs to
164
     */
165
    public function getOrderdetail(): ?Orderdetail
166
    {
167
        return $this->orderdetail;
168
    }
169
170
    /**
171
     * Returns the price associated with this pricedetail.
172
     * It is given in current currency and for the price related quantity.
173
     *
174
     * @return BigDecimal the price as BigDecimal object, like returned raw from DB
175
     */
176
    public function getPrice(): BigDecimal
177
    {
178
        return $this->price;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->price returns the type string which is incompatible with the type-hinted return Brick\Math\BigDecimal.
Loading history...
179
    }
180
181
    /**
182
     *  Get the price for a single unit in the currency associated with this price detail.
183
     *
184
     *  @param float|string|BigDecimal $multiplier The returned price (float or string) will be multiplied
185
     *                                  with this multiplier.
186
     *
187
     *      You will get the price for $multiplier parts. If you want the price which is stored
188
     *           in the database, you have to pass the "price_related_quantity" count as $multiplier.
189
     *
190
     * @return BigDecimal the price as a bcmath string
191
     */
192
    public function getPricePerUnit($multiplier = 1.0): BigDecimal
193
    {
194
        $tmp = BigDecimal::of($multiplier);
195
        $tmp = $tmp->multipliedBy($this->price);
196
197
        return $tmp->dividedBy($this->price_related_quantity, static::PRICE_PRECISION, RoundingMode::HALF_UP);
0 ignored issues
show
Unused Code introduced by
The call to Brick\Math\BigInteger::dividedBy() has too many arguments starting with Brick\Math\RoundingMode::HALF_UP. ( Ignorable by Annotation )

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

197
        return $tmp->/** @scrutinizer ignore-call */ dividedBy($this->price_related_quantity, static::PRICE_PRECISION, RoundingMode::HALF_UP);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Unused Code introduced by
The call to Brick\Math\BigRational::dividedBy() has too many arguments starting with static::PRICE_PRECISION. ( Ignorable by Annotation )

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

197
        return $tmp->/** @scrutinizer ignore-call */ dividedBy($this->price_related_quantity, static::PRICE_PRECISION, RoundingMode::HALF_UP);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug Best Practice introduced by
The expression return $tmp->dividedBy($...\RoundingMode::HALF_UP) could return the type Brick\Math\BigInteger|Brick\Math\BigRational which is incompatible with the type-hinted return Brick\Math\BigDecimal. Consider adding an additional type-check to rule them out.
Loading history...
198
    }
199
200
    /**
201
     *  Get the price related quantity.
202
     *
203
     * This is the quantity, for which the price is valid.
204
     * The amount is measured in part unit.
205
     *
206
     * @return float the price related quantity
207
     *
208
     * @see Pricedetail::setPriceRelatedQuantity()
209
     */
210
    public function getPriceRelatedQuantity(): float
211
    {
212
        if ($this->orderdetail && $this->orderdetail->getPart() && ! $this->orderdetail->getPart()->useFloatAmount()) {
213
            $tmp = round($this->price_related_quantity);
214
215
            return $tmp < 1 ? 1 : $tmp;
216
        }
217
218
        return $this->price_related_quantity;
219
    }
220
221
    /**
222
     *  Get the minimum discount quantity.
223
     *
224
     * "Minimum discount quantity" means the minimum order quantity for which the price
225
     * of this orderdetails is valid.
226
     *
227
     * The amount is measured in part unit.
228
     *
229
     * @return float the minimum discount quantity
230
     *
231
     * @see Pricedetail::setMinDiscountQuantity()
232
     */
233
    public function getMinDiscountQuantity(): float
234
    {
235
        if ($this->orderdetail && $this->orderdetail->getPart() && ! $this->orderdetail->getPart()->useFloatAmount()) {
236
            $tmp = round($this->min_discount_quantity);
237
238
            return $tmp < 1 ? 1 : $tmp;
239
        }
240
241
        return $this->min_discount_quantity;
242
    }
243
244
    /**
245
     * Returns the currency associated with this price information.
246
     * Returns null, if no specific currency is selected and the global base currency should be assumed.
247
     */
248
    public function getCurrency(): ?Currency
249
    {
250
        return $this->currency;
251
    }
252
253
    /********************************************************************************
254
     *
255
     *   Setters
256
     *
257
     *********************************************************************************/
258
259
    /**
260
     * Sets the orderdetail to which this pricedetail belongs to.
261
     *
262
     * @return $this
263
     */
264
    public function setOrderdetail(Orderdetail $orderdetail): self
265
    {
266
        $this->orderdetail = $orderdetail;
267
268
        return $this;
269
    }
270
271
    /**
272
     * Sets the currency associated with the price informations.
273
     * Set to null, to use the global base currency.
274
     *
275
     * @return Pricedetail
276
     */
277
    public function setCurrency(?Currency $currency): self
278
    {
279
        $this->currency = $currency;
280
281
        return $this;
282
    }
283
284
    /**
285
     *  Set the price.
286
     *
287
     * @param BigDecimal $new_price the new price as a float number
288
     *
289
     *      * This is the price for "price_related_quantity" parts!!
290
     *              * Example: if "price_related_quantity" is '10',
291
     *                  you have to set here the price for 10 parts!
292
     *
293
     * @return $this
294
     */
295
    public function setPrice(BigDecimal $new_price): self
296
    {
297
        $tmp = $new_price->toScale(self::PRICE_PRECISION, RoundingMode::HALF_UP);
298
        //Only change the object, if the value changes, so that doctrine does not detect it as changed.
299
        if ((string) $tmp !== (string) $this->price) {
300
            $this->price = $tmp;
301
        }
302
        return $this;
303
    }
304
305
    /**
306
     *  Set the price related quantity.
307
     *
308
     * This is the quantity, for which the price is valid.
309
     *
310
     * Example:
311
     * If 100pcs costs 20$, you have to set the price to 20$ and the price related
312
     * quantity to 100. The single price (20$/100 = 0.2$) will be calculated automatically.
313
     *
314
     * @param float $new_price_related_quantity the price related quantity
315
     *
316
     * @return $this
317
     */
318
    public function setPriceRelatedQuantity(float $new_price_related_quantity): self
319
    {
320
        $this->price_related_quantity = $new_price_related_quantity;
321
322
        return $this;
323
    }
324
325
    /**
326
     *  Set the minimum discount quantity.
327
     *
328
     * "Minimum discount quantity" means the minimum order quantity for which the price
329
     * of this orderdetails is valid. This way, you're able to use different prices
330
     * for different order quantities (quantity discount!).
331
     *
332
     *  Example:
333
     *      - 1-9pcs costs 10$: set price to 10$/pcs and minimum discount quantity to 1
334
     *      - 10-99pcs costs 9$: set price to 9$/pcs and minimum discount quantity to 10
335
     *      - 100pcs or more costs 8$: set price/pcs to 8$ and minimum discount quantity to 100
336
     *
337
     * (Each of this examples would be an own Pricedetails-object.
338
     * So the orderdetails would have three Pricedetails for one supplier.)
339
     *
340
     * @param float $new_min_discount_quantity the minimum discount quantity
341
     *
342
     * @return $this
343
     */
344
    public function setMinDiscountQuantity(float $new_min_discount_quantity): self
345
    {
346
        $this->min_discount_quantity = $new_min_discount_quantity;
347
348
        return $this;
349
    }
350
}
351