Completed
Push — master ( ec14f8...6eabca )
by Joachim
13:25
created

StockMovement::validate()   C

Complexity

Conditions 7
Paths 32

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 28
nc 32
nop 0
1
<?php
2
3
namespace Loevgaard\DandomainStock\Entity;
4
5
use Assert\Assert;
6
use Brick\Math\BigDecimal;
7
use Brick\Math\BigInteger;
8
use Doctrine\ORM\Mapping as ORM;
9
use Knp\DoctrineBehaviors\Model\Blameable\Blameable;
10
use Knp\DoctrineBehaviors\Model\Timestampable\Timestampable;
11
use Loevgaard\DandomainFoundation\Entity\Generated\OrderLineInterface;
12
use Loevgaard\DandomainFoundation\Entity\Generated\ProductInterface;
13
use Loevgaard\DandomainStock\Entity\Generated\StockMovementInterface;
14
use Loevgaard\DandomainStock\Entity\Generated\StockMovementTrait;
15
use Loevgaard\DandomainStock\Exception\CurrencyMismatchException;
16
use Loevgaard\DandomainStock\Exception\StockMovementProductMismatchException;
17
use Loevgaard\DandomainStock\Exception\UndefinedPriceForCurrencyException;
18
use Loevgaard\DandomainStock\Exception\UnsetCurrencyException;
19
use Loevgaard\DandomainStock\Exception\UnsetProductException;
20
use Money\Currency;
21
use Money\Money;
22
use Symfony\Component\Validator\Constraints as FormAssert;
23
24
/**
25
 * @method Money getRetailPriceInclVat()
26
 * @method Money getTotalRetailPriceInclVat()
27
 * @method Money getPriceInclVat()
28
 * @method Money getTotalPriceInclVat()
29
 * @method Money getDiscountInclVat()
30
 * @method Money getTotalDiscountInclVat()
31
 *
32
 * @ORM\Entity()
33
 * @ORM\Table(name="lds_stock_movements", indexes={@ORM\Index(name="type_idx", columns={"`type`"})})
34
 * @ORM\HasLifecycleCallbacks()
35
 **/
36
class StockMovement implements StockMovementInterface
37
{
38
    use StockMovementTrait;
39
    use Blameable;
40
    use Timestampable;
41
42
    const TYPE_SALE = 'sale';
43
    const TYPE_RETURN = 'return';
44
    const TYPE_REGULATION = 'regulation';
45
    const TYPE_DELIVERY = 'delivery';
46
47
    /**
48
     * @var int
49
     *
50
     * @ORM\Column(type="integer")
51
     * @ORM\GeneratedValue
52
     * @ORM\Id
53
     */
54
    protected $id;
55
56
    /**
57
     * The number of items.
58
     *
59
     * If the quantity is negative it means an outgoing stock movement, i.e. you've sold a product
60
     * Contrary a positive number means an ingoing stock movement, i.e. you had a return or a delivery
61
     *
62
     * @var int
63
     *
64
     * @FormAssert\NotBlank()
65
     * @FormAssert\NotEqualTo(0)
66
     *
67
     * @ORM\Column(type="integer")
68
     */
69
    protected $quantity;
70
71
    /**
72
     * Whether this stock movement is a complaint.
73
     *
74
     * @var bool
75
     *
76
     * @ORM\Column(name="complaint", type="boolean")
77
     */
78
    protected $complaint;
79
80
    /**
81
     * A small text describing this stock movement.
82
     *
83
     * @var string
84
     *
85
     * @FormAssert\Length(max="191")
86
     *
87
     * @ORM\Column(name="reference", type="string", length=191)
88
     */
89
    protected $reference;
90
91
    /**
92
     * A valid currency code.
93
     *
94
     * @var string
95
     *
96
     * @FormAssert\Currency()
97
     *
98
     * @ORM\Column(type="string", length=3)
99
     */
100
    protected $currency;
101
102
    /**
103
     * This is the retail price of the product on the time when the stock movement was created
104
     * The price is excl vat.
105
     *
106
     * @var int
107
     *
108
     * @FormAssert\NotBlank()
109
     * @FormAssert\GreaterThanOrEqual(0)
110
     *
111
     * @ORM\Column(type="integer")
112
     */
113
    protected $retailPrice;
114
115
    /**
116
     * Effectively this is `$quantity * $retailPrice`.
117
     *
118
     * The price is excl vat
119
     *
120
     * @var int
121
     *
122
     * @FormAssert\NotBlank()
123
     * @FormAssert\GreaterThanOrEqual(0)
124
     *
125
     * @ORM\Column(type="integer")
126
     */
127
    protected $totalRetailPrice;
128
129
    /**
130
     * This is the price excl vat.
131
     *
132
     * @var int
133
     *
134
     * @FormAssert\NotBlank()
135
     * @FormAssert\GreaterThanOrEqual(0)
136
     *
137
     * @ORM\Column(type="integer")
138
     */
139
    protected $price;
140
141
    /**
142
     * Effectively this is `$quantity * $price`.
143
     *
144
     * This is the total price excl vat
145
     *
146
     * @var int
147
     *
148
     * @FormAssert\NotBlank()
149
     * @FormAssert\GreaterThanOrEqual(0)
150
     *
151
     * @ORM\Column(type="integer")
152
     */
153
    protected $totalPrice;
154
155
    /**
156
     * This is the discount on this stock movement.
157
     *
158
     * Effectively this is `$retailPrice - $price`
159
     *
160
     * @var int
161
     *
162
     * @FormAssert\NotBlank()
163
     *
164
     * @ORM\Column(type="integer")
165
     */
166
    protected $discount;
167
168
    /**
169
     * This is the total discount on this stock movement.
170
     *
171
     * Effectively this is `$totalRetailPrice - $totalPrice`
172
     *
173
     * @var int
174
     *
175
     * @FormAssert\NotBlank()
176
     *
177
     * @ORM\Column(type="integer")
178
     */
179
    protected $totalDiscount;
180
181
    /**
182
     * This is the vat percentage.
183
     *
184
     * @var float
185
     *
186
     * @FormAssert\NotBlank()
187
     * @FormAssert\GreaterThanOrEqual(0)
188
     *
189
     * @ORM\Column(type="decimal", precision=5, scale=2)
190
     */
191
    protected $vatPercentage;
192
193
    /**
194
     * This is the type of the stock movement, i.e. 'sale', 'delivery', 'return' etc.
195
     *
196
     * @var string
197
     *
198
     * @FormAssert\Choice(callback="getTypes")
199
     *
200
     * @ORM\Column(name="`type`", type="string", length=191)
201
     */
202
    protected $type;
203
204
    /**
205
     * This is the associated product.
206
     *
207
     * @var ProductInterface
208
     *
209
     * @FormAssert\NotBlank()
210
     *
211
     * @ORM\JoinColumn(nullable=false)
212
     * @ORM\ManyToOne(targetEntity="Loevgaard\DandomainFoundation\Entity\Product")
213
     */
214
    protected $product;
215
216
    /**
217
     * If the type equals 'sale' this will be the associated order line.
218
     *
219
     * @var OrderLineInterface|null
220
     *
221
     * @ORM\ManyToOne(targetEntity="Loevgaard\DandomainFoundation\Entity\OrderLine", inversedBy="stockMovements")
222
     */
223
    protected $orderLine;
224
225
    /**
226
     * When an associated order line is removed this flag must be set
227
     *
228
     * @var bool
229
     *
230
     * @ORM\Column(type="boolean")
231
     */
232
    protected $orderLineRemoved;
233
234
    public function __construct()
235
    {
236
        $this->complaint = false;
237
        $this->orderLineRemoved = false;
238
    }
239
240
    public function __call($name, $arguments)
241
    {
242
        if ('InclVat' !== substr($name, -7)) {
243
            throw new \InvalidArgumentException('This class only accepts magic calls ending with `InclVat`');
244
        }
245
246
        $method = substr($name, 0, -7);
247
248
        if (false === $method || !method_exists($this, $method)) {
249
            throw new \InvalidArgumentException('The method `'.$method.'` does not exist');
250
        }
251
252
        /** @var Money $val */
253
        $val = $this->{$method}();
254
255
        return $val->multiply($this->getVatMultiplier());
256
    }
257
258
    /**
259
     * @ORM\PrePersist()
260
     * @ORM\PreUpdate()
261
     */
262
    public function validate()
263
    {
264
        Assert::that($this->product)->isInstanceOf(ProductInterface::class);
265
        $productNumber = $this->product->getNumber();
266
267
        Assert::that($this->quantity)->integer('quantity needs to be an integer', 'quantity')->notEq(0, 'quantity can never be 0');
268
        Assert::that($this->complaint)->boolean();
269
        Assert::thatNullOr($this->reference)->string()->maxLength(191);
270
        Assert::that($this->currency)->string()->length(3);
271
        Assert::that($this->retailPrice)->integer('retailPrice needs to be an integer', 'retailPrice')->greaterOrEqualThan(0);
272
        Assert::that($this->totalRetailPrice)->integer('totalRetailPrice needs to be an integer', 'totalRetailPrice')->greaterOrEqualThan(0);
273
        Assert::that($this->price)->integer('price needs to be an integer', 'price')->greaterOrEqualThan(0);
274
        Assert::that($this->totalPrice)->integer('totalPrice needs to be an integer', 'totalPrice')->greaterOrEqualThan(0);
275
        Assert::that($this->discount)->integer('discount needs to be an integer', 'discount');
276
        Assert::that($this->totalDiscount)->integer('totalDiscount needs to be an integer', 'totalDiscount');
277
        Assert::that($this->vatPercentage)->float('vatPercent needs to be a float', 'vatPercentage')->greaterOrEqualThan(0);
278
        Assert::that($this->type)->choice(self::getTypes());
279
280
        if ($this->isType(self::TYPE_SALE)) {
281
            if (!$this->isOrderLineRemoved()) {
282
                Assert::that($this->orderLine)->isInstanceOf(OrderLineInterface::class);
283
            }
284
            Assert::that($this->quantity)->lessThan(0);
285
        } elseif ($this->isType(self::TYPE_RETURN)) {
286
            Assert::that($this->quantity)->greaterThan(0, 'quantity should be greater than 0 if the type is a return');
287
        }
288
289
        if($this->price > 0) {
290
            // it is assumed that if a product has a price, then it also has a retail price
291
            Assert::that($this->retailPrice)->greaterThan(0, 'When the price is > 0, then retailPrice also has be > 0');
292
        }
293
294
        if($this->retailPrice === 0) {
295
            Assert::that($this->discount)->eq(0);
296
        }
297
298
        if ($this->complaint) {
299
            // a complaint will always be a product that you remove from your stock
300
            Assert::that($this->quantity)->lessThan(0, 'quantity needs to be negative when the stock movement is a complaint');
301
        }
302
303
        Assert::thatNullOr($this->product->getIsVariantMaster())->false('['.$productNumber.'] Only simple products and variants is allowed as stock movements');
304
    }
305
306
    /**
307
     * @param int              $quantity
308
     * @param Money            $unitPrice
309
     * @param float            $vatPercent
310
     * @param string           $type
311
     * @param ProductInterface $product
312
     * @param string           $reference
313
     *
314
     * @return StockMovementInterface
315
     *
316
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
317
     * @throws \Loevgaard\DandomainStock\Exception\UndefinedPriceForCurrencyException
318
     */
319
    public static function create(int $quantity, Money $unitPrice, float $vatPercent, string $type, ProductInterface $product, string $reference): StockMovementInterface
320
    {
321
        $stockMovement = new StockMovement();
322
        $stockMovement
323
            ->setQuantity($quantity)
324
            ->setPrice($unitPrice)
325
            ->setVatPercentage($vatPercent)
326
            ->setType($type)
327
            ->setProduct($product)
328
            ->setReference($reference)
329
        ;
330
331
        $retailPrice = $unitPrice;
332
        if ($product->getPrices()->count()) {
333
            $retailPrice = $product->findPriceByCurrency($unitPrice->getCurrency());
334
            if (!$retailPrice) {
335
                throw new UndefinedPriceForCurrencyException('The product `'.$product->getNumber().'` does not have a price defined for currency `'.$unitPrice->getCurrency()->getCode().'`');
336
            }
337
338
            $retailPrice = $retailPrice->getUnitPriceExclVat($vatPercent);
339
        }
340
341
        $stockMovement->setRetailPrice($retailPrice);
342
343
        return $stockMovement;
344
    }
345
346
    /**
347
     * @param OrderLineInterface $orderLine
348
     *
349
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
350
     * @throws \Loevgaard\DandomainStock\Exception\UndefinedPriceForCurrencyException
351
     * @throws \Loevgaard\DandomainStock\Exception\UnsetProductException
352
     */
353
    public function populateFromOrderLine(OrderLineInterface $orderLine)
354
    {
355
        if (!$orderLine->getProduct()) {
356
            throw new UnsetProductException('No product set on order line with product number: '.$orderLine->getProductNumber());
357
        }
358
359
        $created = new \DateTime($orderLine->getOrder()->getCreatedDate()->format(\DateTime::ATOM));
360
361
        $this
362
            ->setQuantity(-1 * $orderLine->getQuantity()) // we multiply by -1 because we count an order as 'outgoing' from the stock
363
            ->setPrice($orderLine->getUnitPriceExclVat())
364
            ->setVatPercentage($orderLine->getVatPct())
365
            ->setType(static::TYPE_SALE)
366
            ->setProduct($orderLine->getProduct())
367
            ->setOrderLine($orderLine)
368
            ->setReference('Order '.$orderLine->getOrder()->getExternalId())
369
            ->setCreatedAt($created)
370
            ->setUpdatedAt($created) // for order lines we specifically override the createdAt and updatedAt dates because the stock movement is actually happening when the order comes in and not when the order is synced
371
        ;
372
373
        $retailPrice = $orderLine->getUnitPriceExclVat();
374
        if ($orderLine->getProduct()->getPrices()->count()) {
375
            $retailPrice = $orderLine->getProduct()->findPriceByCurrency($orderLine->getUnitPrice()->getCurrency());
376
            if (!$retailPrice) {
377
                throw new UndefinedPriceForCurrencyException('The product `'.$orderLine->getProduct()->getNumber().'` does not have a price defined for currency `'.$orderLine->getUnitPrice()->getCurrency()->getCode().'`');
378
            }
379
380
            $retailPrice = $retailPrice->getUnitPriceExclVat($orderLine->getVatPct());
381
        }
382
383
        $this->setRetailPrice($retailPrice);
384
    }
385
386
    /**
387
     * @return StockMovement
388
     *
389
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
390
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
391
     */
392
    public function copy(): self
393
    {
394
        $stockMovement = new static();
395
        $stockMovement
396
            ->setQuantity($this->getQuantity())
397
            ->setComplaint($this->isComplaint())
398
            ->setReference($this->getReference())
399
            ->setRetailPrice($this->getRetailPrice())
400
            ->setPrice($this->getPrice())
401
            ->setVatPercentage($this->getVatPercentage())
402
            ->setType($this->getType())
403
            ->setProduct($this->getProduct())
404
            ->setOrderLine($this->getOrderLine())
405
            ->setOrderLineRemoved($this->isOrderLineRemoved())
406
        ;
407
408
        return $stockMovement;
409
    }
410
411
    /**
412
     * @return StockMovementInterface
413
     *
414
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
415
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
416
     */
417
    public function inverse(): StockMovementInterface
418
    {
419
        $stockMovement = $this->copy();
420
        $stockMovement->setQuantity($stockMovement->getQuantity() * -1);
421
422
        return $stockMovement;
423
    }
424
425
    /**
426
     * @param StockMovementInterface $stockMovement
427
     * @return StockMovementInterface
428
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
429
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
430
     * @throws \Loevgaard\DandomainStock\Exception\StockMovementProductMismatchException
431
     */
432
    public function diff(StockMovementInterface $stockMovement): StockMovementInterface
433
    {
434
        if ($this->getProduct()->getId() !== $stockMovement->getProduct()->getId()) {
435
            throw new StockMovementProductMismatchException('Can only compute diff between stock movements where the products equal');
436
        }
437
438
        $qty = -1 * ($this->getQuantity() - $stockMovement->getQuantity());
439
440
        $diff = $stockMovement->copy();
441
        $diff->setQuantity($qty);
442
443
        return $diff;
444
    }
445
446
    /**
447
     * Returns the valid types.
448
     *
449
     * @return array
450
     */
451
    public static function getTypes(): array
452
    {
453
        return [
454
            self::TYPE_DELIVERY => self::TYPE_DELIVERY,
455
            self::TYPE_SALE => self::TYPE_SALE,
456
            self::TYPE_REGULATION => self::TYPE_REGULATION,
457
            self::TYPE_RETURN => self::TYPE_RETURN,
458
        ];
459
    }
460
461
    /**
462
     * Returns true if $type equals the type of the stock movement.
463
     *
464
     * @param string $type
465
     *
466
     * @return bool
467
     */
468
    public function isType(string $type): bool
469
    {
470
        return $this->type === $type;
471
    }
472
473
    /*********************
474
     * Getters / Setters *
475
     ********************/
476
477
    /**
478
     * @return int
479
     */
480
    public function getId(): int
481
    {
482
        return (int) $this->id;
483
    }
484
485
    /**
486
     * @param int $id
487
     *
488
     * @return StockMovement
489
     */
490
    public function setId(int $id): self
491
    {
492
        $this->id = $id;
493
494
        return $this;
495
    }
496
497
    /**
498
     * @return int
499
     */
500
    public function getQuantity(): int
501
    {
502
        return (int) $this->quantity;
503
    }
504
505
    /**
506
     * @param int $quantity
507
     *
508
     * @return StockMovement
509
     */
510
    public function setQuantity(int $quantity): self
511
    {
512
        $this->quantity = $quantity;
513
        $this->updateTotalPrice();
514
        $this->updateTotalRetailPrice();
515
516
        return $this;
517
    }
518
519
    /**
520
     * @return bool
521
     */
522
    public function isComplaint(): bool
523
    {
524
        return (bool) $this->complaint;
525
    }
526
527
    /**
528
     * @param bool $complaint
529
     *
530
     * @return StockMovement
531
     */
532
    public function setComplaint(bool $complaint): self
533
    {
534
        $this->complaint = $complaint;
535
536
        return $this;
537
    }
538
539
    /**
540
     * @return string
541
     */
542
    public function getReference(): string
543
    {
544
        return (string) $this->reference;
545
    }
546
547
    /**
548
     * @param string $reference
549
     *
550
     * @return StockMovement
551
     */
552
    public function setReference(string $reference): self
553
    {
554
        $this->reference = $reference;
555
556
        return $this;
557
    }
558
559
    /**
560
     * @return string
561
     */
562
    public function getCurrency(): string
563
    {
564
        return (string) $this->currency;
565
    }
566
567
    /**
568
     * @return Money
569
     *
570
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
571
     */
572
    public function getRetailPrice(): Money
573
    {
574
        return $this->money((int) $this->retailPrice);
575
    }
576
577
    /**
578
     * @param Money $retailPrice
579
     *
580
     * @return $this
581
     *
582
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
583
     */
584
    public function setRetailPrice(Money $retailPrice): self
585
    {
586
        $this->updateCurrency($retailPrice);
587
        $this->retailPrice = (int) $retailPrice->getAmount();
588
        $this->updateTotalRetailPrice();
589
590
        return $this;
591
    }
592
593
    /**
594
     * @return Money
595
     *
596
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
597
     */
598
    public function getTotalRetailPrice(): Money
599
    {
600
        return $this->money((int) $this->totalRetailPrice);
601
    }
602
603
    /**
604
     * @return Money
605
     *
606
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
607
     */
608
    public function getPrice(): Money
609
    {
610
        return $this->money((int) $this->price);
611
    }
612
613
    /**
614
     * @param Money $price
615
     *
616
     * @return $this
617
     *
618
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
619
     */
620
    public function setPrice(Money $price): self
621
    {
622
        $this->updateCurrency($price);
623
        $this->price = (int) $price->getAmount();
624
        $this->updateTotalPrice();
625
626
        return $this;
627
    }
628
629
    /**
630
     * @return Money
631
     *
632
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
633
     */
634
    public function getTotalPrice(): Money
635
    {
636
        return $this->money((int) $this->totalPrice);
637
    }
638
639
    /**
640
     * @return Money
641
     *
642
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
643
     */
644
    public function getDiscount(): Money
645
    {
646
        return $this->money((int) $this->discount);
647
    }
648
649
    /**
650
     * @return Money
651
     *
652
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
653
     */
654
    public function getTotalDiscount(): Money
655
    {
656
        return $this->money((int) $this->totalDiscount);
657
    }
658
659
    /**
660
     * @return float
661
     */
662
    public function getVatPercentage(): float
663
    {
664
        return (float) $this->vatPercentage;
665
    }
666
667
    /**
668
     * @param float $vatPercentage
669
     *
670
     * @return StockMovement
671
     */
672
    public function setVatPercentage(float $vatPercentage): self
673
    {
674
        $this->vatPercentage = $vatPercentage;
675
676
        return $this;
677
    }
678
679
    /**
680
     * @return string
681
     */
682
    public function getType(): string
683
    {
684
        return (string) $this->type;
685
    }
686
687
    /**
688
     * @param string $type
689
     *
690
     * @return StockMovement
691
     */
692
    public function setType(string $type): self
693
    {
694
        $this->type = $type;
695
696
        return $this;
697
    }
698
699
    /**
700
     * @return ProductInterface
701
     */
702
    public function getProduct(): ?ProductInterface
703
    {
704
        return $this->product;
705
    }
706
707
    /**
708
     * @param ProductInterface $product
709
     *
710
     * @return StockMovement
711
     */
712
    public function setProduct(ProductInterface $product): self
713
    {
714
        $this->product = $product;
715
716
        return $this;
717
    }
718
719
    /**
720
     * @return OrderLineInterface|null
721
     */
722
    public function getOrderLine(): ?OrderLineInterface
723
    {
724
        return $this->orderLine;
725
    }
726
727
    /**
728
     * @param OrderLineInterface|null $orderLine
729
     *
730
     * @return StockMovement
731
     */
732
    public function setOrderLine(?OrderLineInterface $orderLine): self
733
    {
734
        $this->orderLine = $orderLine;
735
736
        return $this;
737
    }
738
739
    /**
740
     * @return bool
741
     */
742
    public function isOrderLineRemoved(): bool
743
    {
744
        return (bool) $this->orderLineRemoved;
745
    }
746
747
    /**
748
     * @param bool $orderLineRemoved
749
     *
750
     * @return StockMovement
751
     */
752
    public function setOrderLineRemoved(bool $orderLineRemoved)
753
    {
754
        $this->orderLineRemoved = $orderLineRemoved;
755
756
        return $this;
757
    }
758
759
    /****************************
760
     * Protected helper methods *
761
     ***************************/
762
763
    protected function updateTotalPrice(): void
764
    {
765
        if (is_int($this->price) && is_int($this->quantity)) {
766
            $this->totalPrice = BigInteger::of($this->price)->multipliedBy(abs($this->quantity))->toInt();
767
768
            $this->updateDiscount();
769
        }
770
    }
771
772
    protected function updateTotalRetailPrice(): void
773
    {
774
        if (is_int($this->retailPrice) && is_int($this->quantity)) {
775
            $this->totalRetailPrice = BigInteger::of($this->retailPrice)->multipliedBy(abs($this->quantity))->toInt();
776
777
            $this->updateDiscount();
778
        }
779
    }
780
781
    protected function updateDiscount(): void
782
    {
783
        if (is_int($this->retailPrice) && is_int($this->totalRetailPrice) && is_int($this->price) && is_int($this->totalPrice)) {
784
            $this->discount = $this->retailPrice - $this->price;
785
            $this->totalDiscount = $this->totalRetailPrice - $this->totalPrice;
786
        }
787
    }
788
789
    /**
790
     * Updates the shared currency.
791
     *
792
     * If the currency is already set and the new currency is not the same, it throws an exception
793
     *
794
     * @param Money $money
795
     *
796
     * @return StockMovement
797
     *
798
     * @throws \Loevgaard\DandomainStock\Exception\CurrencyMismatchException
799
     */
800
    protected function updateCurrency(Money $money): self
801
    {
802
        if ($this->currency && $money->getCurrency()->getCode() !== $this->currency) {
803
            throw new CurrencyMismatchException('The currency on this stock movement is not the same as the one your Money object');
804
        }
805
806
        $this->currency = $money->getCurrency()->getCode();
807
808
        return $this;
809
    }
810
811
    /**
812
     * Returns a new Money object based on the shared currency
813
     * If no currency is set, it throws an exception.
814
     *
815
     * @param int $val
816
     *
817
     * @return Money
818
     *
819
     * @throws \Loevgaard\DandomainStock\Exception\UnsetCurrencyException
820
     */
821
    protected function money(int $val): Money
822
    {
823
        if (!$this->currency) {
824
            throw new UnsetCurrencyException('The currency is not set on this stock movement');
825
        }
826
827
        return new Money($val, new Currency($this->currency));
828
    }
829
830
    /**
831
     * Returns a vat multiplier for this stock movement.
832
     *
833
     * Example: You have a vat percentage of (float)25.0 then this method will return (string)1.25
834
     *
835
     * @return string
836
     */
837
    protected function getVatMultiplier(): string
838
    {
839
        return (string) BigDecimal::of('100')->plus($this->vatPercentage)->exactlyDividedBy(100);
840
    }
841
}
842