Completed
Push — master ( 6a0a41...ce3ac1 )
by Joachim
18:40
created

StockMovement::setId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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