src/AtechNet/CoreBundle/Entity/StockMovement.php line 24

Open in your IDE?
  1. <?php
  2. namespace App\AtechNet\CoreBundle\Entity;
  3. use App\AtechNet\CoreBundle\DTO\MovementItemWithPrice;
  4. use Doctrine\Common\Collections\ArrayCollection;
  5. use Doctrine\ORM\Mapping\Column;
  6. use Doctrine\ORM\Mapping\Entity;
  7. use Doctrine\ORM\Mapping\JoinColumn;
  8. use Doctrine\ORM\Mapping\ManyToOne;
  9. use Doctrine\ORM\Mapping\OneToMany;
  10. use Doctrine\ORM\Mapping\OneToOne;
  11. use Doctrine\ORM\Mapping\OrderBy;
  12. use Doctrine\ORM\Mapping\Table as Table;
  13. use Generator;
  14. use Symfony\Component\Validator\Constraints as Assert;
  15. /**
  16.  * StockMovement
  17.  *
  18.  * @Table(name="stock_movements")
  19.  * @Entity(repositoryClass="App\AtechNet\CoreBundle\Repository\StockMovementRepository")
  20.  */
  21. class StockMovement extends AbstractSignedEntity
  22. {
  23.     public const int TYPE_IN = 1;
  24.     public const int TYPE_OUT = 2;
  25.     public const int TYPE_NEUTRAL = 3;
  26.     //IN_SUB_TYPE ->
  27.     public const int SUB_TYPE_IN_SHIPMENT = 1;
  28.     public const int SUB_TYPE_IN_FROM_OTHER_STOCK = 2;
  29.     public const int SUB_TYPE_IN_EXCESS_AT_INVENTORY = 4;
  30.     public const int SUB_TYPE_IN_CONFIG_CHANGE = 20;
  31.     public const int SUB_TYPE_IN_USED_IN_COMPANY = 26;
  32.     public const int SUB_TYPE_IN_ORDER_CONFIG_CHANGE = 29;
  33.     public const int SUB_TYPE_IN_CREDIT_NOTE = 32;
  34.     public const int SUB_TYPE_IN_PRODUCTION = 34;
  35.     public const int SUB_TYPE_IN_BUYBACK = 36;
  36.     public const int SUB_TYPE_IN_RETURNED_FROM_SUPPLIER = 37;
  37.     public const int SUB_TYPE_IN_V2_MIGRATION = 39;
  38.     //OUT_SUB_TYPE ->
  39.     public const int SUB_TYPE_OUT_BASIC = 7;
  40.     public const int SUB_TYPE_OUT_TO_OTHER_STOCK = 8;
  41.     public const int SUB_TYPE_OUT_RETURNED_TO_SUPPLIER = 9;
  42.     public const int SUB_TYPE_OUT_SHORTAGE_AT_INVENTORY = 10;
  43.     public const int SUB_TYPE_OUT_CONSUMPTION = 11;
  44.     public const int SUB_TYPE_OUT_CONFIG_CHANGE = 21;
  45.     public const int SUB_TYPE_OUT_REMOVED_FROM_STOCK = 22;
  46.     public const int SUB_TYPE_OUT_CLAIM = 24;
  47.     public const int SUB_TYPE_OUT_USED_IN_COMPANY = 25;
  48.     public const int SUB_TYPE_OUT_PRODUCTION = 35;
  49.     public const int SUB_TYPE_OUT_CONSUMPTION_IN_PREPARATION = 27;
  50.     public const int SUB_TYPE_OUT_ORDER_CONFIG_CHANGE = 30;
  51.     public const int SUB_TYPE_OUT_V2_MIGRATION = 40;
  52.     public const int SUB_TYPE_OUT_LOSS_IN_TRANSIT = 41;
  53.     //NEUTRAL_SUB_TYPE ->
  54.     public const int SUB_TYPE_NEUTRAL_RESERVATION = 14;
  55.     public const int SUB_TYPE_NEUTRAL_COMMISSION = 15;
  56.     public const int SUB_TYPE_NEUTRAL_IN_STOCK = 17;
  57.     public const int SUB_TYPE_NEUTRAL_QUARANTINE = 19;
  58.     public const int SUB_TYPE_NEUTRAL_USED_WITHIN_COMPANY = 23;
  59.     public const int SUB_TYPE_NEUTRAL_IN_PRODUCTION = 31;
  60.     public const int SUB_TYPE_NEUTRAL_RE_AIKEN = 33;
  61.     public const int SUB_TYPE_NEUTRAL_DONOR = 38;
  62.     //LAST_SUB_TYPE_ID = 39
  63.     public const string PRODUCT_STATUS_CHANGE_TYPE_INCOMING_TO_IN_STOCK = 'incoming-to-in-stock';
  64.     public const string PRODUCT_STATUS_CHANGE_TYPE_IN_STOCK_TO_IN_PRODUCTION = 'in-stock-to-in-production';
  65.     /**
  66.      * @var Stock
  67.      *
  68.      * @Assert\NotNull
  69.      *
  70.      * @ManyToOne(targetEntity="Stock", inversedBy="movements")
  71.      * @JoinColumn(name="stock_id", referencedColumnName="id")
  72.      */
  73.     private $stock;
  74.     /**
  75.      * @var Order
  76.      *
  77.      * @ManyToOne(targetEntity="Order", inversedBy="stockMovements")
  78.      * @JoinColumn(name="order_id", referencedColumnName="id", nullable=true)
  79.      */
  80.     private $order;
  81.     /**
  82.      * @var Order
  83.      *
  84.      * @ManyToOne(targetEntity="Order", inversedBy="configChangeStockMovements")
  85.      * @JoinColumn(name="order_config_change_id", referencedColumnName="id", nullable=true)
  86.      */
  87.     private $orderConfigChange;
  88.     /**
  89.      * @var Customer
  90.      *
  91.      * @Assert\NotNull
  92.      *
  93.      * @ManyToOne(targetEntity="Customer", inversedBy="movements")
  94.      * @JoinColumn(name="merchant_id", referencedColumnName="id")
  95.      */
  96.     private $merchant;
  97.     /**
  98.      * @var StockReview
  99.      *
  100.      * @ManyToOne(targetEntity="StockReview", inversedBy="stockMovements")
  101.      * @JoinColumn(name="stock_review_id", referencedColumnName="id", nullable=true)
  102.      */
  103.     private $stockReview;
  104.     /**
  105.      * @var ArrayCollection
  106.      *
  107.      * @OneToMany(targetEntity="StockMovementItem", mappedBy="movement", cascade={"persist"}, orphanRemoval=true)
  108.      * @OrderBy({"created" = "DESC"})
  109.      */
  110.     private $movementItems;
  111.     /**
  112.      * @var int
  113.      *
  114.      * @Column(name="type", type="integer")
  115.      */
  116.     private $type;
  117.     /**
  118.      * @var int
  119.      *
  120.      * @Column(name="sub_type", type="integer")
  121.      */
  122.     private $subType;
  123.     /**
  124.      * @var int
  125.      *
  126.      * @Column(name="quantity", type="integer")
  127.      */
  128.     private $quantity = 0;
  129.     /**
  130.      * @var string
  131.      *
  132.      * @Column(name="extra_price", type="decimal", precision=10, scale=2)
  133.      */
  134.     private $extraPrice = 0;
  135.     /**
  136.      * @var string
  137.      *
  138.      * @Column(name="total_price", type="decimal", precision=10, scale=2)
  139.      */
  140.     private $totalPrice = 0;
  141.     /**
  142.      * @var ArrayCollection
  143.      *
  144.      * @OneToMany(targetEntity="Document", mappedBy="stockMovement", orphanRemoval=true)
  145.      * @OrderBy({"created" = "DESC"})
  146.      */
  147.     private $documents;
  148.     /**
  149.      * @var ArrayCollection
  150.      *
  151.      * @OneToMany(targetEntity="CustomerBillingoData", mappedBy="stockMovement", orphanRemoval=true)
  152.      * @OrderBy({"created" = "DESC"})
  153.      */
  154.     private $billingoDocuments;
  155.     /**
  156.      * @var ArrayCollection
  157.      *
  158.      * @OneToMany(targetEntity="ProductCommission", mappedBy="stockMovement", orphanRemoval=true)
  159.      * @OrderBy({"created" = "DESC"})
  160.      */
  161.     private $commissions;
  162.     /**
  163.      * @var float
  164.      *
  165.      * @Column(name="vat", type="decimal", precision=10, scale=2)
  166.      */
  167.     private $vat = 0;
  168.     /**
  169.      * @var string
  170.      *
  171.      * @Column(name="note", type="text", nullable=true)
  172.      */
  173.     private $note;
  174.     /**
  175.      * @var string
  176.      *
  177.      * @Column(name="shipment_code", type="string", length=255, nullable=true)
  178.      */
  179.     private $shipmentCode;
  180.     /**
  181.      * @var float
  182.      *
  183.      * @Column(name="exchange_rate", type="float")
  184.      */
  185.     private $exchangeRate = 1;
  186.     /**
  187.      * @var bool
  188.      *
  189.      * @Column(name="locked_for_non_admins", type="boolean")
  190.      */
  191.     private $lockedForNonAdmins = false;
  192.     /**
  193.      * @var StockMovement
  194.      *
  195.      * @OneToOne(targetEntity="StockMovement")
  196.      * @JoinColumn(name="related_movement_id", referencedColumnName="id")
  197.      */
  198.     private $relatedMovement;
  199.     /**
  200.      * @var ArrayCollection
  201.      *
  202.      * @OneToMany(targetEntity="StockMovementLog", mappedBy="stockMovement", orphanRemoval=true)
  203.      * @OrderBy({"created" = "ASC"})
  204.      */
  205.     private $logs;
  206.     /**
  207.      * @var Shipment|null
  208.      *
  209.      * @ManyToOne(targetEntity="Shipment", inversedBy="stockMovements")
  210.      * @JoinColumn(name="shipment_id", referencedColumnName="id")
  211.      */
  212.     private ?Shipment $shipment = null;
  213.     /**
  214.      * @var bool
  215.      *
  216.      * @Column(name="shipment_ready", type="boolean")
  217.      */
  218.     private $shipmentReady = false;
  219.     public function __construct()
  220.     {
  221.         parent::__construct();
  222.         $this->movementItems = new ArrayCollection();
  223.         $this->logs = new ArrayCollection();
  224.     }
  225.     /**
  226.      * List of IN sub types on which stock items are added to stock
  227.      *
  228.      * @return array
  229.      */
  230.     public static function getStockInSubTypes(): array
  231.     {
  232.         return [
  233.             'smSubTypeInBasic'              => self::SUB_TYPE_IN_SHIPMENT,
  234.             'smSubTypeInConsumption'        => self::SUB_TYPE_IN_FROM_OTHER_STOCK,
  235.             'smSubTypeInReturnedToSupplier' => self::SUB_TYPE_IN_EXCESS_AT_INVENTORY,
  236.         ];
  237.     }
  238.     /**
  239.      * List of OUT sub types on which stock items are no longer in stock
  240.      *
  241.      * @return array
  242.      */
  243.     public static function getStockOutSubTypes(): array
  244.     {
  245.         return [
  246.             'smSubTypeOutBasic'              => self::SUB_TYPE_OUT_BASIC,
  247.             'smSubTypeOutConsumption'        => self::SUB_TYPE_OUT_CONSUMPTION,
  248.             'smSubTypeOutRemovedFromStock'   => self::SUB_TYPE_OUT_REMOVED_FROM_STOCK,
  249.             'smSubTypeOutReturnedToSupplier' => self::SUB_TYPE_OUT_RETURNED_TO_SUPPLIER,
  250.             'smSubTypeOutToOtherStock'       => self::SUB_TYPE_OUT_TO_OTHER_STOCK,
  251.         ];
  252.     }
  253.     /**
  254.      * @param $key
  255.      * @return string[]
  256.      */
  257.     public static function getSubTypeChoicesForType($key): array
  258.     {
  259.         $subTypesChoicesWithHierarchy = self::getSubTypeChoicesWithHierarchy();
  260.         if (isset($subTypesChoicesWithHierarchy[$key])) {
  261.             $subTypesChoicesWithHierarchy[$key];
  262.         }
  263.         return self::getSubTypeChoicesWithHierarchy()[$key];
  264.     }
  265.     /**
  266.      * @param bool $withTypeTitles
  267.      * @return array
  268.      */
  269.     public static function getSubTypeChoicesWithHierarchy($withTypeTitles = false): array
  270.     {
  271.         $subTypeArrayMainKeys = [];
  272.         $typeChoices = self::getTypeChoices();
  273.         foreach ($typeChoices as $key => $typeChoiceTitle) {
  274.             if ($withTypeTitles) {
  275.                 $subTypeArrayMainKeys[$key] = $typeChoiceTitle;
  276.             } else {
  277.                 $subTypeArrayMainKeys[$key] = $key;
  278.             }
  279.         }
  280.         return [
  281.             $subTypeArrayMainKeys[self::TYPE_IN]            => [
  282.                 self::SUB_TYPE_IN_SHIPMENT                  => 'Shipment',
  283.                 self::SUB_TYPE_IN_CREDIT_NOTE               => 'Credit note',
  284.                 self::SUB_TYPE_IN_FROM_OTHER_STOCK          => 'From other stock',
  285.                 self::SUB_TYPE_IN_EXCESS_AT_INVENTORY       => 'Excess at inventory',
  286.                 self::SUB_TYPE_IN_CONFIG_CHANGE             => 'Config change',
  287.                 self::SUB_TYPE_IN_USED_IN_COMPANY           => 'Used in company',
  288.                 self::SUB_TYPE_IN_ORDER_CONFIG_CHANGE       => 'Order config change',
  289.                 self::SUB_TYPE_IN_PRODUCTION                => 'Production',
  290.                 self::SUB_TYPE_IN_BUYBACK                   => 'Buyback',
  291.                 self::SUB_TYPE_IN_RETURNED_FROM_SUPPLIER    => 'Returned from supplier',
  292.                 self::SUB_TYPE_IN_V2_MIGRATION              => 'V2 Migration'
  293.             ],
  294.             $subTypeArrayMainKeys[self::TYPE_OUT]     => [
  295.                 self::SUB_TYPE_OUT_BASIC                      => 'Basic',
  296.                 self::SUB_TYPE_OUT_TO_OTHER_STOCK             => 'To other stock',
  297.                 self::SUB_TYPE_OUT_RETURNED_TO_SUPPLIER       => 'Returned to supplier',
  298.                 self::SUB_TYPE_OUT_SHORTAGE_AT_INVENTORY      => 'Shortage at inventory',
  299.                 self::SUB_TYPE_OUT_CONSUMPTION                => 'Consumption',
  300.                 self::SUB_TYPE_OUT_CONFIG_CHANGE              => 'Config change',
  301.                 self::SUB_TYPE_OUT_REMOVED_FROM_STOCK         => 'Removed from stock',
  302.                 self::SUB_TYPE_OUT_CLAIM                      => 'Claim',
  303.                 self::SUB_TYPE_OUT_USED_IN_COMPANY            => 'Used in company',
  304.                 self::SUB_TYPE_OUT_CONSUMPTION_IN_PREPARATION => 'Consumption in preparation',
  305.                 self::SUB_TYPE_OUT_ORDER_CONFIG_CHANGE        => 'Order config change',
  306.                 self::SUB_TYPE_OUT_PRODUCTION                 => 'Production',
  307.                 self::SUB_TYPE_OUT_V2_MIGRATION               => 'V2 Migration',
  308.                 self::SUB_TYPE_OUT_LOSS_IN_TRANSIT            => 'Loss in transit',
  309.             ],
  310.             $subTypeArrayMainKeys[self::TYPE_NEUTRAL] => [
  311.                 self::SUB_TYPE_NEUTRAL_RESERVATION         => 'Reservation',
  312.                 self::SUB_TYPE_NEUTRAL_COMMISSION          => 'Commission',
  313.                 self::SUB_TYPE_NEUTRAL_QUARANTINE          => 'Quarantine',
  314.                 self::SUB_TYPE_NEUTRAL_USED_WITHIN_COMPANY => 'Used in company',
  315.                 self::SUB_TYPE_NEUTRAL_IN_PRODUCTION       => 'In Production',
  316.                 self::SUB_TYPE_NEUTRAL_RE_AIKEN            => 'ReAiken',
  317.                 self::SUB_TYPE_NEUTRAL_DONOR               => 'Donor',
  318.             ],
  319.         ];
  320.     }
  321.     /**
  322.      * @return array
  323.      */
  324.     public static function getTypeChoices(): array
  325.     {
  326.         return [
  327.             self::TYPE_IN      => 'In',
  328.             self::TYPE_OUT     => 'Out',
  329.             self::TYPE_NEUTRAL => 'Neutral',
  330.         ];
  331.     }
  332.     /**
  333.      * @param int $subTypeId
  334.      * @return bool|int
  335.      */
  336.     public static function getTypeOfSubType($subTypeId)
  337.     {
  338.         if (is_numeric($subTypeId)) {
  339.             foreach (self::getSubTypeChoicesWithHierarchy() as $typeKey => $typeArray) {
  340.                 if (array_key_exists($subTypeId, $typeArray)) {
  341.                     return $typeKey;
  342.                 }
  343.             }
  344.         }
  345.         return false;
  346.     }
  347.     /**
  348.      * @return int[]
  349.      */
  350.     public static function getProductStatusChangePairs(): array
  351.     {
  352.         return [
  353.             self::PRODUCT_STATUS_CHANGE_TYPE_INCOMING_TO_IN_STOCK      => [
  354.                 15250 => 22258, // PC
  355.                 15251 => 22256, // Monitor
  356.                 15252 => 22257, // Notebook
  357.                 15253 => 22259, // PC accessory
  358.                 22181 => 22260, // Smartphone
  359.             ],
  360.             self::PRODUCT_STATUS_CHANGE_TYPE_IN_STOCK_TO_IN_PRODUCTION => [
  361.                 22258 => 22265, // PC
  362.                 22256 => 22263, // Monitor
  363.                 22257 => 22264, // Notebook
  364.                 22259 => 22266, // PC accessory
  365.                 22260 => 22262, // Smartphone
  366.             ],
  367.         ];
  368.     }
  369.     /**
  370.      * List of product IDs in product production phase
  371.      *
  372.      * @return int[]
  373.      */
  374.     public static function getProductStatusChangeIDs(): array
  375.     {
  376.         return [
  377.             15250,
  378.             15251,
  379.             15252,
  380.             15253,
  381.             22181,
  382.             22258,
  383.             22256,
  384.             22257,
  385.             22259,
  386.             22260,
  387.             22262,
  388.             22263,
  389.             22264,
  390.             22265,
  391.             22266,
  392.             22180,
  393.             3099,
  394.             3098,
  395.             3097,
  396.             3100,
  397.         ];
  398.     }
  399.     /**
  400.      * @return StockReview|null
  401.      */
  402.     public function getStockReview(): ?StockReview
  403.     {
  404.         return $this->stockReview;
  405.     }
  406.     /**
  407.      * @param StockReview $stockReview
  408.      */
  409.     public function setStockReview(StockReview $stockReview): void
  410.     {
  411.         $this->stockReview = $stockReview;
  412.     }
  413.     /**
  414.      * Add StockMovementItem
  415.      *
  416.      * @param StockMovementItem $movementItem
  417.      */
  418.     public function addMovementItem($movementItem)
  419.     {
  420.         if (!$this->movementItems->contains($movementItem)) {
  421.             $this->movementItems->add($movementItem);
  422.         }
  423.     }
  424.     /**
  425.      * Remove StockMovementItem
  426.      *
  427.      * @param StockMovementItem $movementItem
  428.      */
  429.     public function removeMovementItem($movementItem)
  430.     {
  431.         $this->movementItems->removeElement($movementItem);
  432.     }
  433.     /**
  434.      * @return Generator<MovementItemWithPrice>
  435.      */
  436.     public function getMovementItemsWithPrice(): Generator
  437.     {
  438.         foreach ($this->getMovementItems() as $stockMovementItem) {
  439.             yield MovementItemWithPrice::fromEntity($stockMovementItem, $this->getExchangeRate(), $this->getVat());
  440.         }
  441.     }
  442.     /**
  443.      * @return ArrayCollection
  444.      */
  445.     public function getMovementItems()
  446.     {
  447.         return $this->movementItems;
  448.     }
  449.     /**
  450.      * @param ArrayCollection $movementItems
  451.      */
  452.     public function setMovementItems($movementItems)
  453.     {
  454.         $this->movementItems = $movementItems;
  455.     }
  456.     /**
  457.      * @return float
  458.      */
  459.     public function getExchangeRate()
  460.     {
  461.         return $this->exchangeRate;
  462.     }
  463.     /**
  464.      * @param float $exchangeRate
  465.      */
  466.     public function setExchangeRate($exchangeRate)
  467.     {
  468.         $this->exchangeRate = $exchangeRate;
  469.     }
  470.     /**
  471.      * @return float
  472.      */
  473.     public function getVat()
  474.     {
  475.         return $this->vat;
  476.     }
  477.     /**
  478.      * @param float $vat
  479.      */
  480.     public function setVat($vat)
  481.     {
  482.         $this->vat = $vat;
  483.     }
  484.     /**
  485.      * @param array<StockMovement> $movements
  486.      */
  487.     public function getMovementItemsWithPurchasePrice(array $movements): array
  488.     {
  489.         $result = [];
  490.         /** @var StockMovementItem $stockMovementItemOUT */
  491.         foreach ($this->getMovementItems() as $stockMovementItemOUT) {
  492.             $productOUT = $stockMovementItemOUT->getProduct();
  493.             // try to find prices in previous stock movements.
  494.             foreach ($movements as $previousStockMovement) {
  495.                 /** @var StockMovementItem $stockMovementItemIN */
  496.                 foreach ($previousStockMovement->getMovementItems() as $stockMovementItemIN) {
  497.                     if ($stockMovementItemIN->getProduct()->getId() != $productOUT->getId()) {
  498.                         // another product
  499.                         continue;
  500.                     }
  501.                     if (array_key_exists($productOUT->getId(), $result)) {
  502.                         // we found prices already
  503.                         continue;
  504.                     }
  505.                     // collect prices from found stockItems
  506.                     $purchasePrices = array_map(
  507.                         fn(StockItem $stockItemIN) => $stockItemIN->getPurchasePrice(),
  508.                         $stockMovementItemIN->getItems()->toArray()
  509.                     );
  510.                     if ($purchasePrices === []) {
  511.                         continue;
  512.                     }
  513.                     $purchasePrice = array_sum($purchasePrices) / count($purchasePrices);
  514.                     $purchasePrice = round($purchasePrice, 2);
  515.                     $result[$productOUT->getId()] = [
  516.                         'stockMovementItem' => $stockMovementItemOUT,
  517.                         'product'           => $productOUT,
  518.                         'avgPrice'          => $purchasePrice,
  519.                         'from'              => 'previous StockItem',
  520.                         'items'             => $stockMovementItemIN->getItems()->toArray(),
  521.                     ];
  522.                 }
  523.             }
  524.             if (!array_key_exists($productOUT->getId(), $result)) {
  525.                 // price not found in prev. stockMovements
  526.                 $avgPrice = $productOUT->getAvgPrice();
  527.                 $result[$productOUT->getId()] = [
  528.                     'stockMovementItem' => $stockMovementItemOUT,
  529.                     'product'           => $productOUT,
  530.                     'avgPrice'          => $avgPrice,
  531.                     'from'              => 'product avgPrice',
  532.                 ];
  533.             }
  534.         }
  535.         return array_map(
  536.             fn(array $row) => MovementItemWithPrice::fromPreviousStockMovementItem(
  537.                 $row['stockMovementItem'],
  538.                 $row['avgPrice'],
  539.                 $this->getVat()
  540.             ),
  541.             $result
  542.         );
  543.     }
  544.     /**
  545.      * @return string
  546.      */
  547.     public function getExtraVatPrice()
  548.     {
  549.         return $this->getCompany()->createVatPrice($this->extraPrice, $this->getVat());
  550.     }
  551.     /**
  552.      * @return Company
  553.      */
  554.     public function getCompany()
  555.     {
  556.         return $this->getStock()->getCompany();
  557.     }
  558.     /**
  559.      * @return Stock
  560.      */
  561.     public function getStock()
  562.     {
  563.         return $this->stock;
  564.     }
  565.     /**
  566.      * @param Stock $stock
  567.      */
  568.     public function setStock($stock)
  569.     {
  570.         $this->stock = $stock;
  571.     }
  572.     /**
  573.      * @return float
  574.      */
  575.     public function getRoundedExtraVatPrice(): float
  576.     {
  577.         return $this->getRoundedTotalVatPrice() - $this->getRoundedSubTotalVatPrice();
  578.     }
  579.     /**
  580.      * @return float
  581.      */
  582.     public function getRoundedTotalVatPrice(): float
  583.     {
  584.         if ($this->getQuantity() == 0) {
  585.             return 0;
  586.         }
  587.         return $this->getQuantity() * $this->getCompany()->createVatPrice(
  588.                 $this->getRoundedTotalPrice() / $this->getQuantity(),
  589.                 $this->getVat()
  590.             );
  591.     }
  592.     /**
  593.      * @return int
  594.      */
  595.     public function getQuantity()
  596.     {
  597.         return $this->quantity;
  598.     }
  599.     /**
  600.      * @param int $quantity
  601.      */
  602.     public function setQuantity($quantity)
  603.     {
  604.         $this->quantity = $quantity;
  605.     }
  606.     /**
  607.      * @return float
  608.      */
  609.     public function getRoundedTotalPrice(): float
  610.     {
  611.         return (float)$this->getRoundedSubTotalPrice() + (float)$this->getExtraPrice();
  612.     }
  613.     /**
  614.      * @return float
  615.      */
  616.     public function getRoundedSubTotalPrice(): float
  617.     {
  618.         if ($this->getQuantity() == 0) {
  619.             return 0;
  620.         }
  621.         return round(($this->getTotalPrice() - $this->getExtraPrice()) / $this->getQuantity(), 2) * $this->getQuantity();
  622.     }
  623.     /**
  624.      * @return string
  625.      */
  626.     public function getTotalPrice()
  627.     {
  628.         return $this->totalPrice;
  629.     }
  630.     /**
  631.      * @param string $totalPrice
  632.      */
  633.     public function setTotalPrice($totalPrice)
  634.     {
  635.         $this->totalPrice = $totalPrice;
  636.     }
  637.     /**
  638.      * @return string
  639.      */
  640.     public function getExtraPrice()
  641.     {
  642.         return $this->extraPrice;
  643.     }
  644.     /**
  645.      * @param string $extraPrice
  646.      */
  647.     public function setExtraPrice($extraPrice)
  648.     {
  649.         $this->extraPrice = $extraPrice;
  650.     }
  651.     /**
  652.      * @return float
  653.      */
  654.     public function getRoundedSubTotalVatPrice(): float
  655.     {
  656.         return $this->getCompany()->createVatPrice($this->getRoundedSubTotalPrice(), $this->getVat());
  657.     }
  658.     /**
  659.      * @return string
  660.      */
  661.     public function getTotalVatPrice()
  662.     {
  663.         return $this->getCompany()->createVatPrice($this->totalPrice, $this->getVat());
  664.     }
  665.     /**
  666.      * @return string
  667.      */
  668.     public function getSubTotalVatPrice()
  669.     {
  670.         return $this->getCompany()->createVatPrice($this->getSubTotalPrice(), $this->getVat());
  671.     }
  672.     /**
  673.      * @return string
  674.      */
  675.     public function getSubTotalPrice()
  676.     {
  677.         return $this->getTotalPrice() - $this->getExtraPrice();
  678.     }
  679.     /**
  680.      * @return int
  681.      */
  682.     public function getNote()
  683.     {
  684.         return $this->note;
  685.     }
  686.     /**
  687.      * @param string $note
  688.      */
  689.     public function setNote($note)
  690.     {
  691.         $this->note = $note;
  692.     }
  693.     /**
  694.      * @return mixed
  695.      */
  696.     public function getCurrency()
  697.     {
  698.         return $this->getMerchant()->getDomain()->getCurrency();
  699.     }
  700.     /**
  701.      * @return Customer
  702.      */
  703.     public function getMerchant()
  704.     {
  705.         return $this->merchant;
  706.     }
  707.     /**
  708.      * @param Customer $merchant
  709.      */
  710.     public function setMerchant(Customer $merchant)
  711.     {
  712.         $this->merchant = $merchant;
  713.     }
  714.     /**
  715.      * @return ArrayCollection
  716.      */
  717.     public function getBillingoDocuments()
  718.     {
  719.         return $this->billingoDocuments;
  720.     }
  721.     /**
  722.      * @param ArrayCollection $billingoDocuments
  723.      */
  724.     public function setBillingoDocuments($billingoDocuments)
  725.     {
  726.         $this->billingoDocuments = $billingoDocuments;
  727.     }
  728.     /**
  729.      * @return ArrayCollection
  730.      */
  731.     public function getCommissions()
  732.     {
  733.         return $this->commissions;
  734.     }
  735.     /**
  736.      * @return StockMovement
  737.      */
  738.     public function getRelatedMovement()
  739.     {
  740.         return $this->relatedMovement;
  741.     }
  742.     /**
  743.      * @param StockMovement $relatedMovement
  744.      */
  745.     public function setRelatedMovement($relatedMovement)
  746.     {
  747.         $this->relatedMovement = $relatedMovement;
  748.     }
  749.     /**
  750.      * @return ArrayCollection
  751.      */
  752.     public function getProducts()
  753.     {
  754.         $products = new ArrayCollection();
  755.         foreach ($this->getMovementItems() as $mi) {
  756.             $prod = $mi->getProduct();
  757.             if (!$products->contains($prod)) {
  758.                 $products->add($prod);
  759.             }
  760.         }
  761.         return $products;
  762.     }
  763.     public function getMovementItemsQuantityCount()
  764.     {
  765.         $total = 0;
  766.         foreach ($this->getMovementItems() as $mi) {
  767.             $total += $mi->getQuantity();
  768.         }
  769.         return $total;
  770.     }
  771.     /**
  772.      * 1 = less, 2 = OK, 3 = more
  773.      *
  774.      * @return int|null
  775.      */
  776.     public function isAllStockItemsAdded(): ?int
  777.     {
  778.         $neededStockItems = $this->getNeededStockItemsCount();
  779.         $addedStockItemsCount = $this->getStockItems()->count();
  780.         $result = $neededStockItems - $addedStockItemsCount;
  781.         if ($result > 0) { // less items than needed
  782.             return 1;
  783.         } elseif ($result == 0) { // quantity ok
  784.             return 2;
  785.         } elseif ($result < 0) { // more items than needed
  786.             return 3;
  787.         }
  788.         return null;
  789.     }
  790.     /**
  791.      * @return int
  792.      */
  793.     public function getNeededStockItemsCount(): int
  794.     {
  795.         $quantity = 0;
  796.         foreach ($this->getMovementItems() as $mi) {
  797.             if ($mi->getProduct()?->isScannable() && $mi->getProduct()->getType()->isScannable()) {
  798.                 $quantity += $mi->getQuantity();
  799.             }
  800.         }
  801.         return $quantity;
  802.     }
  803.     /**
  804.      * @return int
  805.      */
  806.     public function getType()
  807.     {
  808.         return $this->type;
  809.     }
  810.     /**
  811.      * @param int $type
  812.      */
  813.     public function setType($type)
  814.     {
  815.         $this->type = $type;
  816.     }
  817.     /**
  818.      * @return ArrayCollection
  819.      */
  820.     public function getStockItems()
  821.     {
  822.         $stockItems = new ArrayCollection();
  823.         foreach ($this->getMovementItems() as $mi) {
  824.             foreach ($mi->getItems() as $si) {
  825.                 if (!$stockItems->contains($si)) {
  826.                     $stockItems->add($si);
  827.                 }
  828.             }
  829.         }
  830.         return $stockItems;
  831.     }
  832.     /**
  833.      * @return mixed
  834.      */
  835.     public function getTypeTitle()
  836.     {
  837.         return self::getTypeChoices()[$this->getType()];
  838.     }
  839.     /**
  840.      * @return mixed
  841.      */
  842.     public function getSubTypeTitle()
  843.     {
  844.         return self::getSubTypeChoices()[$this->getSubType()];
  845.     }
  846.     /**
  847.      * @return array
  848.      */
  849.     public static function getSubTypeChoices(): array
  850.     {
  851.         $subTypes = [];
  852.         foreach (self::getSubTypeChoicesWithHierarchy() as $subTypeGroupWithHierarchy) {
  853.             foreach ($subTypeGroupWithHierarchy as $key => $subType) {
  854.                 $subTypes[$key] = $subType;
  855.             }
  856.         }
  857.         return $subTypes;
  858.     }
  859.     /**
  860.      * @return int
  861.      */
  862.     public function getSubType()
  863.     {
  864.         return $this->subType;
  865.     }
  866.     /**
  867.      * @param int $subType
  868.      */
  869.     public function setSubType($subType)
  870.     {
  871.         $this->subType = $subType;
  872.     }
  873.     /**
  874.      * @return bool
  875.      */
  876.     public function isLockedForNonAdmins()
  877.     {
  878.         return $this->lockedForNonAdmins;
  879.     }
  880.     /**
  881.      * @param bool $lockedForNonAdmins
  882.      */
  883.     public function setLockedForNonAdmins($lockedForNonAdmins)
  884.     {
  885.         $this->lockedForNonAdmins = $lockedForNonAdmins;
  886.     }
  887.     /**
  888.      * @return bool
  889.      * @throws \Exception
  890.      */
  891.     public function isLocked()
  892.     {
  893.         return ($this->getDocuments()->count() > 0
  894.                 && !is_null($this->getDocuments()->first())
  895.                 && $this->getDocuments()->first()->isLocked())
  896.             || (
  897.                 in_array($this->getType(), [self::TYPE_IN, self::TYPE_OUT])
  898.                 && $this->getCreated() < (new \DateTime())->setTime(0, 0)
  899.                 && $this->getShipmentCode() != '3961'
  900.                 && !in_array($this->getId(), [498458,498459])
  901.                 && $this->getSubType() != self::SUB_TYPE_IN_SHIPMENT
  902.             );
  903.     }
  904.     /**
  905.      * @return ArrayCollection
  906.      */
  907.     public function getDocuments()
  908.     {
  909.         return $this->documents;
  910.     }
  911.     /**
  912.      * @return Order
  913.      */
  914.     public function getOrder()
  915.     {
  916.         return $this->order;
  917.     }
  918.     /**
  919.      * @param Order $order
  920.      */
  921.     public function setOrder($order)
  922.     {
  923.         $this->order = $order;
  924.     }
  925.     /**
  926.      * @return string
  927.      */
  928.     public function getExtraVatPricePerItem()
  929.     {
  930.         return $this->getCompany()->createVatPrice($this->getExtraPricePerItem(), $this->getVat());
  931.     }
  932.     /**
  933.      * @return int
  934.      */
  935.     public function getExtraPricePerItem()
  936.     {
  937.         if ($this->getExtraPrice() != 0 && $this->getQuantity() > 0) {
  938.             return number_format($this->getExtraPrice() / $this->getQuantity(), 2, '.', '');
  939.         }
  940.         return 0;
  941.     }
  942.     /**
  943.      * @return string|null
  944.      */
  945.     public function getShipmentCode()
  946.     {
  947.         return $this->shipmentCode;
  948.     }
  949.     /**
  950.      * @param string|null $shipmentCode
  951.      * @return void
  952.      */
  953.     public function setShipmentCode(?string $shipmentCode)
  954.     {
  955.         $this->shipmentCode = $shipmentCode;
  956.     }
  957.     /**
  958.      * @return ArrayCollection
  959.      */
  960.     public function getLogs()
  961.     {
  962.         return $this->logs;
  963.     }
  964.     /**
  965.      * @param ArrayCollection $logs
  966.      */
  967.     public function setLogs($logs)
  968.     {
  969.         $this->logs = $logs;
  970.     }
  971.     /**
  972.      * @param StockMovementLog $log
  973.      */
  974.     public function addLog($log)
  975.     {
  976.         if (!$this->logs->contains($log)) {
  977.             $this->logs->add($log);
  978.         }
  979.     }
  980.     /**
  981.      * @return Order|null
  982.      */
  983.     public function getOrderConfigChange(): ?Order
  984.     {
  985.         return $this->orderConfigChange;
  986.     }
  987.     /**
  988.      * @param Order|null $orderConfigChange
  989.      * @return void
  990.      */
  991.     public function setOrderConfigChange(?Order $orderConfigChange): void
  992.     {
  993.         $this->orderConfigChange = $orderConfigChange;
  994.     }
  995.     /**
  996.      * @return Shipment|null
  997.      */
  998.     public function getShipment(): ?Shipment
  999.     {
  1000.         return $this->shipment;
  1001.     }
  1002.     /**
  1003.      * @param Shipment|null $shipment
  1004.      * @return void
  1005.      */
  1006.     public function setShipment(?Shipment $shipment): void
  1007.     {
  1008.         $this->shipment = $shipment;
  1009.     }
  1010.     /**
  1011.      * @return bool
  1012.      */
  1013.     public function isShipmentReady(): bool
  1014.     {
  1015.         return $this->shipmentReady;
  1016.     }
  1017.     /**
  1018.      * @param bool $shipmentReady
  1019.      * @return void
  1020.      */
  1021.     public function setShipmentReady(bool $shipmentReady): void
  1022.     {
  1023.         $this->shipmentReady = $shipmentReady;
  1024.     }
  1025. }