partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

Part.php (25965B)


      1 <?php
      2 
      3 namespace PartKeepr\PartBundle\Entity;
      4 
      5 use Doctrine\Common\Collections\ArrayCollection;
      6 use Doctrine\ORM\Mapping as ORM;
      7 use PartKeepr\CoreBundle\Entity\BaseEntity;
      8 use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService;
      9 use PartKeepr\FootprintBundle\Entity\Footprint;
     10 use PartKeepr\PartBundle\Exceptions\CategoryNotAssignedException;
     11 use PartKeepr\PartBundle\Exceptions\MinStockLevelOutOfRangeException;
     12 use PartKeepr\PartBundle\Exceptions\StorageLocationNotAssignedException;
     13 use PartKeepr\ProjectBundle\Entity\Project;
     14 use PartKeepr\ProjectBundle\Entity\ProjectPart;
     15 use PartKeepr\StockBundle\Entity\StockEntry;
     16 use PartKeepr\StorageLocationBundle\Entity\StorageLocation;
     17 use PartKeepr\UploadedFileBundle\Annotation\UploadedFileCollection;
     18 use Symfony\Component\Serializer\Annotation\Groups;
     19 use Symfony\Component\Validator\Constraints as Assert;
     20 
     21 /**
     22  * Represents a part in the database. The heart of our project. Handle with care!
     23  *
     24  * @ORM\Entity
     25  * @ORM\HasLifecycleCallbacks
     26  * @TargetService(uri="/api/parts")
     27  */
     28 class Part extends BaseEntity
     29 {
     30     /**
     31      * The category of the part.
     32      *
     33      * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\PartCategory")
     34      * @Assert\NotNull()
     35      * @Groups({"default"})
     36      *
     37      * @var PartCategory
     38      */
     39     private $category;
     40 
     41     /**
     42      * The part's name.
     43      *
     44      * @ORM\Column
     45      * @Groups({"default"})
     46      * @Assert\NotBlank()
     47      *
     48      * @var string
     49      */
     50     private $name;
     51 
     52     /**
     53      * The part's short description.
     54      *
     55      * @ORM\Column(type="string",nullable=true)
     56      * @Groups({"default"})
     57      *
     58      * @var string
     59      */
     60     private $description;
     61 
     62     /**
     63      * The footprint of this part.
     64      *
     65      * @ORM\ManyToOne(targetEntity="PartKeepr\FootprintBundle\Entity\Footprint")
     66      * @Groups({"default"})
     67      *
     68      * @var Footprint
     69      */
     70     private $footprint;
     71 
     72     /**
     73      * The unit in which the part's "amount" is calculated. This is necessary to count parts
     74      * in "pieces", "meters" or "grams".
     75      *
     76      * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\PartMeasurementUnit", inversedBy="parts")
     77      * @Groups({"default"})
     78      *
     79      * @var PartMeasurementUnit
     80      */
     81     private $partUnit;
     82 
     83     /**
     84      * Defines the storage location of this part.
     85      *
     86      * @ORM\ManyToOne(targetEntity="PartKeepr\StorageLocationBundle\Entity\StorageLocation")
     87      * @Groups({"default"})
     88      *
     89      * @var StorageLocation
     90      */
     91     private $storageLocation;
     92 
     93     /**
     94      * Holds the manufacturers which can manufacture this part.
     95      *
     96      * @ORM\OneToMany(targetEntity="PartKeepr\PartBundle\Entity\PartManufacturer",mappedBy="part",cascade={"persist", "remove"},
     97      *                                                                                                                orphanRemoval=true)
     98      * @Groups({"default"})
     99      *
    100      * @var ArrayCollection
    101      */
    102     private $manufacturers;
    103 
    104     /**
    105      * Holds the distributors from where we can buy the part.
    106      *
    107      * @ORM\OneToMany(targetEntity="PartKeepr\PartBundle\Entity\PartDistributor",mappedBy="part",cascade={"persist", "remove"},
    108      *                                                                                                               orphanRemoval=true)
    109      * @Groups({"default"})
    110      *
    111      * @var ArrayCollection
    112      */
    113     private $distributors;
    114 
    115     /**
    116      * Holds the part attachments.
    117      *
    118      * @ORM\OneToMany(targetEntity="PartKeepr\PartBundle\Entity\PartAttachment",
    119      *                mappedBy="part",cascade={"persist", "remove"}, orphanRemoval=true)
    120      * @Groups({"default"})
    121      * @UploadedFileCollection()
    122      *
    123      * @var PartAttachment
    124      */
    125     private $attachments;
    126 
    127     /**
    128      * The comment for this part.
    129      *
    130      * @ORM\Column(type="text")
    131      * @Groups({"default"})
    132      */
    133     private $comment = '';
    134 
    135     /**
    136      * The stock level. Note that this is a cached value, because it makes our summary queries easier.
    137      *
    138      * @todo It would be nice if we could get rid of that.
    139      * @ORM\Column(type="integer")
    140      * @Groups({"readonly"})
    141      *
    142      * @var int
    143      */
    144     private $stockLevel = 0;
    145 
    146     /**
    147      * The minimum stock level for this part. If we run out of this part (e.g. stockLevel < minStockLevel),
    148      * we can see that in the system and re-order parts.
    149      *
    150      * @Groups({"default"})
    151      * @ORM\Column(type="integer")
    152      *
    153      * @var int
    154      */
    155     private $minStockLevel = 0;
    156 
    157     /**
    158      * The average price for the part. Note that this is a cached value.
    159      *
    160      * @ORM\Column(type="decimal",precision=13,scale=4,nullable=false)
    161      * @Groups({"readonly"})
    162      *
    163      * @var float
    164      */
    165     private $averagePrice = 0;
    166 
    167     /**
    168      * The stock level history.
    169      *
    170      * @ORM\OneToMany(targetEntity="PartKeepr\StockBundle\Entity\StockEntry",mappedBy="part",cascade={"persist", "remove"})
    171      * @Groups({"stock"})
    172      *
    173      * @var ArrayCollection
    174      */
    175     private $stockLevels;
    176 
    177     /**
    178      * The parameters for this part.
    179      *
    180      * @ORM\OneToMany(targetEntity="PartKeepr\PartBundle\Entity\PartParameter",
    181      *                mappedBy="part",cascade={"persist", "remove"}, orphanRemoval=true)
    182      * @Groups({"default"})
    183      *
    184      * @var ArrayCollection
    185      */
    186     private $parameters;
    187 
    188     /**
    189      * The meta part parameter criterias for this part.
    190      *
    191      * @ORM\OneToMany(targetEntity="PartKeepr\PartBundle\Entity\MetaPartParameterCriteria",
    192      *                mappedBy="part",cascade={"persist", "remove"}, orphanRemoval=true)
    193      * @Groups({"default"})
    194      *
    195      * @var ArrayCollection
    196      */
    197     private $metaPartParameterCriterias;
    198 
    199     /**
    200      * The part status for this part.
    201      *
    202      * @ORM\Column(type="string",nullable=true)
    203      * @Groups({"default"})
    204      *
    205      * @var string
    206      */
    207     private $status;
    208 
    209     /**
    210      * Defines if the part needs review.
    211      *
    212      * @ORM\Column(type="boolean")
    213      * @Groups({"default"})
    214      *
    215      * @var bool
    216      */
    217     private $needsReview;
    218 
    219     /**
    220      * Defines the condition of the part.
    221      *
    222      * @ORM\Column(type="string",nullable=true)
    223      * @Groups({"default"})
    224      *
    225      * @var string
    226      */
    227     private $partCondition;
    228 
    229     /**
    230      * Defines the production remarks for a part.
    231      *
    232      * @ORM\Column(type="string",nullable=true)
    233      * @Groups({"default"})
    234      *
    235      * @var string
    236      */
    237     private $productionRemarks;
    238 
    239     /**
    240      * The create date+time for this part.
    241      *
    242      * @ORM\Column(type="datetime",nullable=true)
    243      * @Groups({"readonly"})
    244      *
    245      * @var \DateTime
    246      */
    247     private $createDate;
    248 
    249     /**
    250      * @ORM\OneToMany(targetEntity="PartKeepr\ProjectBundle\Entity\ProjectPart", mappedBy="part")
    251      *
    252      * @var ProjectPart[]
    253      **/
    254     private $projectParts;
    255 
    256     /**
    257      * The internal part number.
    258      *
    259      * @ORM\Column(type="string",nullable=true)
    260      * @Groups({"default"})
    261      *
    262      * @var string
    263      */
    264     private $internalPartNumber;
    265 
    266     /**
    267      * @ORM\Column(type="boolean",nullable=false)
    268      * @Groups({"readonly"})
    269      *
    270      * @var bool
    271      */
    272     private $removals = false;
    273 
    274     /**
    275      * @ORM\Column(type="boolean",nullable=false)
    276      * @Groups({"readonly"})
    277      *
    278      * @var bool
    279      */
    280     private $lowStock = false;
    281 
    282     /**
    283      * Defines if the part is a meta-part.
    284      *
    285      * @ORM\Column(type="boolean", options={ "default":false})
    286      * @Groups({"default"})
    287      *
    288      * @var bool
    289      */
    290     private $metaPart = false;
    291 
    292     /**
    293      * An array of all matching meta parts.
    294      *
    295      * @Groups({"default"})
    296      *
    297      * @var array
    298      */
    299     private $metaPartMatches;
    300 
    301     public function __construct()
    302     {
    303         $this->distributors = new ArrayCollection();
    304         $this->manufacturers = new ArrayCollection();
    305         $this->parameters = new ArrayCollection();
    306         $this->attachments = new ArrayCollection();
    307         $this->stockLevels = new ArrayCollection();
    308         $this->projectParts = new ArrayCollection();
    309         $this->metaPartParameterCriterias = new ArrayCollection();
    310         $this->setCreateDate(new \DateTime());
    311         $this->setNeedsReview(false);
    312         $this->setMetaPart(false);
    313     }
    314 
    315     /**
    316      * Sets the create date for this part.
    317      *
    318      * @param \DateTime $dateTime The create date+time
    319      */
    320     private function setCreateDate(\DateTime $dateTime)
    321     {
    322         $this->createDate = $dateTime;
    323     }
    324 
    325     /**
    326      * @return string
    327      */
    328     public function getProductionRemarks()
    329     {
    330         return $this->productionRemarks;
    331     }
    332 
    333     /**
    334      * @param string $productionRemarks
    335      */
    336     public function setProductionRemarks($productionRemarks)
    337     {
    338         $this->productionRemarks = $productionRemarks;
    339     }
    340 
    341     /**
    342      * @return array
    343      */
    344     public function getMetaPartMatches()
    345     {
    346         return $this->metaPartMatches;
    347     }
    348 
    349     /**
    350      * @param array $metaPartMatches
    351      */
    352     public function setMetaPartMatches($metaPartMatches)
    353     {
    354         $this->metaPartMatches = $metaPartMatches;
    355     }
    356 
    357     /**
    358      * @return bool
    359      */
    360     public function isLowStock()
    361     {
    362         return $this->lowStock;
    363     }
    364 
    365     /**
    366      * @param bool $lowStock
    367      */
    368     public function setLowStock($lowStock)
    369     {
    370         $this->lowStock = $lowStock;
    371     }
    372 
    373     /**
    374      * @return mixed
    375      */
    376     public function hasRemovals()
    377     {
    378         return $this->removals;
    379     }
    380 
    381     /**
    382      * Returns the name of this part.
    383      *
    384      * @return string The part name
    385      */
    386     public function getName()
    387     {
    388         return $this->name;
    389     }
    390 
    391     /**
    392      * Sets the name for this part.
    393      *
    394      * @param string $name The part's name
    395      */
    396     public function setName($name)
    397     {
    398         $this->name = $name;
    399     }
    400 
    401     /**
    402      * Returns the internal part number for this part.
    403      *
    404      * @return string the internal part number
    405      */
    406     public function getInternalPartNumber()
    407     {
    408         return $this->internalPartNumber;
    409     }
    410 
    411     /**
    412      * Sets the internal part number for this part.
    413      *
    414      * @param string $partNumber
    415      */
    416     public function setInternalPartNumber($partNumber)
    417     {
    418         $this->internalPartNumber = $partNumber;
    419     }
    420 
    421     /**
    422      * Returns the short description of this part.
    423      *
    424      * @return string The part description
    425      */
    426     public function getDescription()
    427     {
    428         return $this->description;
    429     }
    430 
    431     /**
    432      * Sets the description for this part.
    433      *
    434      * @param string $description The part's short description
    435      */
    436     public function setDescription($description)
    437     {
    438         $this->description = $description;
    439     }
    440 
    441     /**
    442      * Returns the part unit.
    443      *
    444      * @param none
    445      *
    446      * @return PartMeasurementUnit The part unit object
    447      */
    448     public function getPartUnit()
    449     {
    450         return $this->partUnit;
    451     }
    452 
    453     /**
    454      * Sets the part unit.
    455      *
    456      * @param PartMeasurementUnit $partUnit The part unit object to set
    457      */
    458     public function setPartUnit(PartMeasurementUnit $partUnit = null)
    459     {
    460         $this->partUnit = $partUnit;
    461     }
    462 
    463     /**
    464      * Returns the review flag.
    465      *
    466      * @return bool True if the part needs review, false otherwise
    467      */
    468     public function getNeedsReview()
    469     {
    470         return $this->needsReview;
    471     }
    472 
    473     /**
    474      * Sets the review flag.
    475      *
    476      * @param bool $bReview True if the part needs review, false otherwise
    477      */
    478     public function setNeedsReview($bReview)
    479     {
    480         $this->needsReview = $bReview;
    481     }
    482 
    483     /**
    484      * Returns the condition of this part.
    485      *
    486      * @return string The part condition
    487      */
    488     public function getPartCondition()
    489     {
    490         return $this->partCondition;
    491     }
    492 
    493     /**
    494      * Sets the condition for this part.
    495      *
    496      * @param string $partCondition The part's condition
    497      */
    498     public function setPartCondition($partCondition)
    499     {
    500         $this->partCondition = $partCondition;
    501     }
    502 
    503     /**
    504      * Returns the category path.
    505      *
    506      * @Groups({"default"})
    507      *
    508      * @return string
    509      */
    510     public function getCategoryPath()
    511     {
    512         if ($this->category !== null) {
    513             return $this->category->getCategoryPath();
    514         } else {
    515             return '';
    516         }
    517     }
    518 
    519     /**
    520      * Retrieves the footprint.
    521      */
    522     public function getFootprint()
    523     {
    524         return $this->footprint;
    525     }
    526 
    527     /**
    528      * Sets the footprint for this part.
    529      *
    530      * @param \PartKeepr\FootprintBundle\Entity\Footprint $footprint The footprint to set
    531      */
    532     public function setFootprint(Footprint $footprint = null)
    533     {
    534         $this->footprint = $footprint;
    535     }
    536 
    537     /**
    538      * Returns the comment for this part.
    539      *
    540      * @return string The comment
    541      */
    542     public function getComment()
    543     {
    544         return $this->comment;
    545     }
    546 
    547     /**
    548      * Sets the comment for this part.
    549      *
    550      * @param string $comment The comment for this part
    551      */
    552     public function setComment($comment)
    553     {
    554         $this->comment = $comment;
    555     }
    556 
    557     /**
    558      * Returns the distributors array.
    559      *
    560      * @return ArrayCollection the distributors
    561      */
    562     public function getDistributors()
    563     {
    564         return $this->distributors->getValues();
    565     }
    566 
    567     /**
    568      * Returns the part attachments array.
    569      *
    570      * @return ArrayCollection the part attachments
    571      */
    572     public function getAttachments()
    573     {
    574         return $this->attachments->getValues();
    575     }
    576 
    577     /**
    578      * Returns the manufacturers array.
    579      *
    580      * @return ArrayCollection the manufacturers
    581      */
    582     public function getManufacturers()
    583     {
    584         return $this->manufacturers->getValues();
    585     }
    586 
    587     /**
    588      * Returns the parameters assigned to this part.
    589      *
    590      * @return ArrayCollection An array of PartParameter objects
    591      */
    592     public function getParameters()
    593     {
    594         return $this->parameters->getValues();
    595     }
    596 
    597     /**
    598      * Returns the meta part parameter criterias assigned to this part.
    599      *
    600      * @return MetaPartParameterCriteria[] An array of MetaPartParameterCriteria objects
    601      */
    602     public function getMetaPartParameterCriterias()
    603     {
    604         return $this->metaPartParameterCriterias->getValues();
    605     }
    606 
    607     /**
    608      * Returns the create date.
    609      *
    610      * @return \DateTime The create date+time
    611      */
    612     public function getCreateDate()
    613     {
    614         return $this->createDate;
    615     }
    616 
    617     /**
    618      * Returns the status for this part.
    619      *
    620      * @return string The status
    621      */
    622     public function getStatus()
    623     {
    624         return $this->status;
    625     }
    626 
    627     /**
    628      * Sets the status for this part. A status is any string describing the status,
    629      * e.g. "new", "used", "broken" etc.
    630      *
    631      * @param string $status The status
    632      */
    633     public function setStatus($status)
    634     {
    635         $this->status = $status;
    636     }
    637 
    638     /**
    639      * Checks if the requirements for database persisting are given.
    640      *
    641      * @throws CategoryNotAssignedException        Thrown if no category is set
    642      * @throws StorageLocationNotAssignedException Thrown if no storage location is set
    643      *
    644      * @ORM\PrePersist
    645      */
    646     public function onPrePersist()
    647     {
    648         $this->executeSaveListener();
    649     }
    650 
    651     public function executeSaveListener()
    652     {
    653         $this->checkCategoryConsistency();
    654         $this->checkStorageLocationConsistency();
    655     }
    656 
    657     /**
    658      * Checks if the part category is set.
    659      *
    660      * @throws CategoryNotAssignedException
    661      */
    662     private function checkCategoryConsistency()
    663     {
    664         if ($this->getCategory() === null) {
    665             throw new CategoryNotAssignedException();
    666         }
    667     }
    668 
    669     /**
    670      * Returns the assigned category.
    671      *
    672      * @return PartCategory
    673      */
    674     public function getCategory()
    675     {
    676         return $this->category;
    677     }
    678 
    679     /**
    680      * Sets the category for this part.
    681      *
    682      * @param PartCategory $category The category
    683      */
    684     public function setCategory($category)
    685     {
    686         $this->category = $category;
    687     }
    688 
    689     /**
    690      * Checks if the part storage location is set.
    691      *
    692      * @throws \PartKeepr\PartBundle\Exceptions\StorageLocationNotAssignedException
    693      */
    694     private function checkStorageLocationConsistency()
    695     {
    696         if ($this->getStorageLocation() === null && !$this->isMetaPart()) {
    697             throw new StorageLocationNotAssignedException();
    698         }
    699     }
    700 
    701     /**
    702      * Returns the storage location for this part.
    703      *
    704      * @return \PartKeepr\StorageLocationBundle\Entity\StorageLocation $storageLocation The storage location
    705      */
    706     public function getStorageLocation()
    707     {
    708         return $this->storageLocation;
    709     }
    710 
    711     /**
    712      * Sets the storage location for this part.
    713      *
    714      * @param \PartKeepr\StorageLocationBundle\Entity\StorageLocation $storageLocation The storage location
    715      */
    716     public function setStorageLocation(StorageLocation $storageLocation = null)
    717     {
    718         $this->storageLocation = $storageLocation;
    719     }
    720 
    721     /**
    722      * @return bool
    723      */
    724     public function isMetaPart()
    725     {
    726         return $this->metaPart;
    727     }
    728 
    729     /**
    730      * @param bool $metaPart
    731      */
    732     public function setMetaPart($metaPart)
    733     {
    734         $this->metaPart = $metaPart;
    735     }
    736 
    737     /**
    738      * @param mixed $removals
    739      */
    740     public function setRemovals($removals = false)
    741     {
    742         $this->removals = $removals;
    743     }
    744 
    745     /**
    746      * Returns the acrage price.
    747      *
    748      * @return float
    749      */
    750     public function getAveragePrice()
    751     {
    752         return $this->averagePrice;
    753     }
    754 
    755     /**
    756      * Sets the average price for this part.
    757      *
    758      * @param float $price The price to set
    759      */
    760     public function setAveragePrice($price)
    761     {
    762         $this->averagePrice = $price;
    763     }
    764 
    765     /**
    766      * Checks if the requirements for database persisting are given.
    767      *
    768      * For a list of exceptions, see
    769      *
    770      * @see Part::onPrePersist()
    771      *
    772      * @ORM\PreUpdate
    773      */
    774     public function onPreUpdate()
    775     {
    776         $this->executeSaveListener();
    777     }
    778 
    779     /**
    780      * Adds a new stock entry to this part.
    781      *
    782      * @param StockEntry $stockEntry
    783      */
    784     public function addStockLevel(StockEntry $stockEntry)
    785     {
    786         $stockEntry->setPart($this);
    787         $this->stockLevels->add($stockEntry);
    788     }
    789 
    790     /**
    791      * Removes a stock entry from this part.
    792      *
    793      * @param StockEntry $stockEntry
    794      */
    795     public function removeStockLevel($stockEntry)
    796     {
    797         $stockEntry->setPart(null);
    798         $this->stockLevels->removeElement($stockEntry);
    799     }
    800 
    801     /**
    802      * Adds a Part Parameter.
    803      *
    804      * @param PartParameter $partParameter A parameter to add
    805      */
    806     public function addParameter($partParameter)
    807     {
    808         if ($partParameter instanceof PartParameter) {
    809             $partParameter->setPart($this);
    810         }
    811         $this->parameters->add($partParameter);
    812     }
    813 
    814     /**
    815      * Removes a Part Parameter.
    816      *
    817      * @param PartParameter $partParameter An parameter to remove
    818      */
    819     public function removeParameter($partParameter)
    820     {
    821         $partParameter->setPart(null);
    822         $this->parameters->removeElement($partParameter);
    823     }
    824 
    825     /**
    826      * Adds a Meta Part Parameter Criteria.
    827      *
    828      * @param MetaPartParameterCriteria $metaPartParameterCriteria A meta part parameter criteria to
    829      */
    830     public function addMetaPartParameterCriteria($metaPartParameterCriteria)
    831     {
    832         if ($metaPartParameterCriteria instanceof MetaPartParameterCriteria) {
    833             $metaPartParameterCriteria->setPart($this);
    834         }
    835         $this->metaPartParameterCriterias->add($metaPartParameterCriteria);
    836     }
    837 
    838     /**
    839      * Removes a Part Parameter.
    840      *
    841      * @param MetaPartParameterCriteria $metaPartParameterCriteria A meta part parameter criteria to remove
    842      */
    843     public function removeMetaPartParameterCriteria($metaPartParameterCriteria)
    844     {
    845         $metaPartParameterCriteria->setPart(null);
    846         $this->metaPartParameterCriterias->removeElement($metaPartParameterCriteria);
    847     }
    848 
    849     /**
    850      * Adds a Part Attachment.
    851      *
    852      * @param PartAttachment $partAttachment An attachment to add
    853      */
    854     public function addAttachment($partAttachment)
    855     {
    856         if ($partAttachment instanceof PartAttachment) {
    857             $partAttachment->setPart($this);
    858         }
    859         $this->attachments->add($partAttachment);
    860     }
    861 
    862     /**
    863      * Removes a Part Attachment.
    864      *
    865      * @param PartAttachment $partAttachment An attachment to remove
    866      */
    867     public function removeAttachment($partAttachment)
    868     {
    869         if ($partAttachment instanceof PartAttachment) {
    870             $partAttachment->setPart(null);
    871         }
    872 
    873         $this->attachments->removeElement($partAttachment);
    874     }
    875 
    876     /**
    877      * Adds a Part Manufacturer.
    878      *
    879      * @param PartManufacturer $partManufacturer A part manufacturer to add
    880      */
    881     public function addManufacturer(PartManufacturer $partManufacturer)
    882     {
    883         $partManufacturer->setPart($this);
    884         $this->manufacturers->add($partManufacturer);
    885     }
    886 
    887     /**
    888      * Removes a part manufacturer.
    889      *
    890      * @param PartManufacturer $partManufacturer A part manufacturer to remove
    891      */
    892     public function removeManufacturer(PartManufacturer $partManufacturer)
    893     {
    894         $partManufacturer->setPart(null);
    895         $this->manufacturers->removeElement($partManufacturer);
    896     }
    897 
    898     /**
    899      * Adds a Part Distributor.
    900      *
    901      * @param PartDistributor $partDistributor A part distributor to add
    902      */
    903     public function addDistributor(PartDistributor $partDistributor)
    904     {
    905         $partDistributor->setPart($this);
    906         $this->distributors->add($partDistributor);
    907     }
    908 
    909     /**
    910      * Removes a part distributor.
    911      *
    912      * @param PartDistributor $partDistributor A part distributor to remove
    913      */
    914     public function removeDistributor(PartDistributor $partDistributor)
    915     {
    916         $partDistributor->setPart(null);
    917         $this->distributors->removeElement($partDistributor);
    918     }
    919 
    920     /**
    921      * Returns the project parts.
    922      *
    923      * @return ArrayCollection
    924      */
    925     public function getProjectParts()
    926     {
    927         return $this->projectParts->getValues();
    928     }
    929 
    930     /**
    931      * Returns the project names this part is used in.
    932      *
    933      * @Groups({"default"})
    934      *
    935      * @return array
    936      */
    937     public function getProjectNames()
    938     {
    939         $projectNames = [];
    940         foreach ($this->projectParts as $projectPart) {
    941             if ($projectPart->getProject() instanceof Project) {
    942                 $projectNames[] = $projectPart->getProject()->getName();
    943             }
    944         }
    945 
    946         return array_unique($projectNames);
    947     }
    948 
    949     public function recomputeStockLevels()
    950     {
    951         $currentStock = 0;
    952         $avgPrice = 0;
    953 
    954         $totalPartStockPrice = 0;
    955         $lastPosEntryQuant = 0;
    956         $lastPosEntryPrice = 0;
    957         $negativeStock = 0;
    958 
    959         foreach ($this->getStockLevels() as $stockLevel) {
    960             $currentStock += $stockLevel->getStockLevel();
    961 
    962             if ($currentStock <= 0) {
    963                 $avgPrice = 0;
    964                 $totalPartStockPrice = 0;
    965                 $negativeStock = $currentStock;
    966             } else {
    967                 if ($stockLevel->getStockLevel() > 0) {
    968                     $lastPosEntryQuant = $stockLevel->getStockLevel();
    969                     $lastPosEntryPrice = $stockLevel->getPrice();
    970                     $totalPartStockPrice += $lastPosEntryPrice * ($lastPosEntryQuant + $negativeStock);
    971                     $avgPrice = $totalPartStockPrice / $currentStock;
    972                 } else {
    973                     if ($currentStock < $lastPosEntryQuant) {
    974                         $totalPartStockPrice = $currentStock * $lastPosEntryPrice;
    975                         $avgPrice = $totalPartStockPrice / $currentStock;
    976                     } else {
    977                         $totalPartStockPrice += $stockLevel->getStockLevel() * $avgPrice;
    978                         $avgPrice = $totalPartStockPrice / $currentStock;
    979                     }
    980                     $negativeStock = 0;
    981                 }
    982             }
    983         }
    984 
    985         $this->setStockLevel($currentStock);
    986         $this->setAveragePrice($avgPrice);
    987 
    988         if ($currentStock < $this->getMinStockLevel()) {
    989             $this->setLowStock(true);
    990         } else {
    991             $this->setLowStock(false);
    992         }
    993     }
    994 
    995     /**
    996      * Returns all stock entries.
    997      *
    998      * @return ArrayCollection
    999      */
   1000     public function getStockLevels()
   1001     {
   1002         return $this->stockLevels->getValues();
   1003     }
   1004 
   1005     /**
   1006      * Returns the minimum stock level.
   1007      *
   1008      * @return int
   1009      */
   1010     public function getMinStockLevel()
   1011     {
   1012         return $this->minStockLevel;
   1013     }
   1014 
   1015     /**
   1016      * Set the minimum stock level for this part.
   1017      *
   1018      * Only positive values are allowed.
   1019      *
   1020      * @param int $minStockLevel A minimum stock level, only values >= 0 are allowed.
   1021      *
   1022      * @throws MinStockLevelOutOfRangeException If the passed stock level is not in range (>=0)
   1023      */
   1024     public function setMinStockLevel($minStockLevel)
   1025     {
   1026         $minStockLevel = intval($minStockLevel);
   1027 
   1028         if ($minStockLevel < 0) {
   1029             throw new MinStockLevelOutOfRangeException();
   1030         }
   1031 
   1032         $this->minStockLevel = $minStockLevel;
   1033 
   1034         if ($this->getStockLevel() < $this->getMinStockLevel()) {
   1035             $this->setLowStock(true);
   1036         } else {
   1037             $this->setLowStock(false);
   1038         }
   1039     }
   1040 
   1041     /**
   1042      * Returns the stock level.
   1043      *
   1044      * @return int The stock level
   1045      */
   1046     public function getStockLevel()
   1047     {
   1048         return $this->stockLevel;
   1049     }
   1050 
   1051     /**
   1052      * Sets the stock level.
   1053      *
   1054      * @param $stockLevel int The stock level to set
   1055      */
   1056     public function setStockLevel($stockLevel)
   1057     {
   1058         $this->stockLevel = $stockLevel;
   1059     }
   1060 }