Completed
Push — master ( 3f5ed8...fd7ecf )
by Joachim
19:34 queued 04:32
created

StockMovement::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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