src/Entity/OnlineShop/Product.php line 46

Open in your IDE?
  1. <?php
  2. namespace App\Entity\OnlineShop;
  3. use App\Annotation\SiteAware;
  4. use App\Entity\AbstractBase;
  5. use App\Entity\Interfaces\DocumentInterface;
  6. use App\Entity\Interfaces\ImageInterface;
  7. use App\Entity\Interfaces\PublishedInterface;
  8. use App\Entity\Interfaces\SiteInterface;
  9. use App\Entity\MiniAbstractBase;
  10. use App\Entity\Traits\DescriptionTrait;
  11. use App\Entity\Traits\HasDocumentTrait;
  12. use App\Entity\Traits\HasImageTrait;
  13. use App\Entity\Traits\NullableNameTrait;
  14. use App\Entity\Traits\OnlyAdminTrait;
  15. use App\Entity\Traits\PublishedTrait;
  16. use App\Entity\Traits\SiteTrait;
  17. use App\Entity\User;
  18. use App\Enum\UserRolesEnum;
  19. use App\Persistence\Model\UserGroupAccessType;
  20. use App\Repository\OnlineShop\ProductRepository;
  21. use Doctrine\Common\Collections\ArrayCollection;
  22. use Doctrine\Common\Collections\Collection;
  23. use Doctrine\ORM\Mapping as ORM;
  24. use Gedmo\Mapping\Annotation as Gedmo;
  25. use Money\Currency;
  26. use Money\Money;
  27. use Symfony\Component\HttpFoundation\File\File;
  28. use Symfony\Component\Security\Core\User\UserInterface;
  29. use Symfony\Component\Validator\Constraints as Assert;
  30. use Tbbc\MoneyBundle\Formatter\MoneyFormatter;
  31. use Vich\UploaderBundle\Mapping\Annotation as Vich;
  32. /**
  33.  * @ORM\Table(name="vulco_product", indexes={@ORM\Index(name="product_site_idx", columns={"site"})}, uniqueConstraints={@ORM\UniqueConstraint(name="familiy_and_supplier_reference_unique_idx", columns={"family_id", "supplier_reference", "removed_at"})})})
  34.  *
  35.  * @ORM\Entity(repositoryClass=ProductRepository::class)
  36.  *
  37.  * @Gedmo\SoftDeleteable(fieldName="removedAt", timeAware=false)
  38.  *
  39.  * @SiteAware(siteFieldName="site")
  40.  *
  41.  * @Vich\Uploadable
  42.  */
  43. class Product extends AbstractBase implements SiteInterfaceDocumentInterfacePublishedInterfaceImageInterface
  44. {
  45.     use DescriptionTrait;
  46.     use HasDocumentTrait;
  47.     use HasImageTrait;
  48.     use NullableNameTrait;
  49.     use OnlyAdminTrait;
  50.     use PublishedTrait;
  51.     use SiteTrait;
  52.     public const RATE_PRICE 1;
  53.     public const COST_PRICE 2;
  54.     /**
  55.      * @ORM\Column(type="string", length=200, nullable=false)
  56.      */
  57.     private string $reference;
  58.     /**
  59.      * @ORM\Column(type="string", length=40, nullable=false)
  60.      */
  61.     private ?string $name null;
  62.     /**
  63.      * @ORM\Column(type="text", length=10000, nullable=true)
  64.      */
  65.     private ?string $description null;
  66.     /**
  67.      * @ORM\Column(type="integer")
  68.      */
  69.     private int $ratePriceAmount;
  70.     /**
  71.      * @ORM\Column(type="string", length=64, nullable=false)
  72.      */
  73.     private string $ratePriceCurrency;
  74.     /**
  75.      * @ORM\Column(type="money")
  76.      */
  77.     private Money $costPrice;
  78.     /**
  79.      * @ORM\Column(type="money", nullable=true)
  80.      * At the moment we only use this field for orders export of garages with GRIPS software
  81.      */
  82.     private ?Money $pvpPrice null;
  83.     /**
  84.      * @ORM\Column(type="money", nullable=true)
  85.      * Sale price (precio de oferta). At the moment we only use it for the Meta catalog feed. (sale_price field)
  86.      */
  87.     private ?Money $salePrice null;
  88.     /**
  89.      * @ORM\Column(type="string", length=255, nullable=true)
  90.      * To assimilate families with GRIPS software
  91.      */
  92.     private ?string $positionGroup null;
  93.     /**
  94.      * @ORM\Column(type="money")
  95.      */
  96.     private Money $sigaus;
  97.     /**
  98.      * @ORM\Column(type="money")
  99.      */
  100.     private Money $greenPoint;
  101.     /**
  102.      * @ORM\Column(type="money")
  103.      */
  104.     private Money $ecoTax;
  105.     /**
  106.      * @ORM\Column(type="decimal", precision=10, scale=2)
  107.      */
  108.     private float $weight;
  109.     /**
  110.      * @ORM\Column(type="integer")
  111.      */
  112.     private int $transportPoints;
  113.     /**
  114.      * @ORM\Column(type="text", length=10000, nullable=true)
  115.      */
  116.     private ?string $promotion null;
  117.     /**
  118.      * @ORM\Column(type="text", length=10000, nullable=true)
  119.      */
  120.     private ?string $imageUrl null;
  121.     /**
  122.      * @ORM\Column(type="string", length=255, nullable=true)
  123.      */
  124.     private ?string $supplierReference null;
  125.     /**
  126.      * @ORM\Column(type="decimal", precision=10, scale=2)
  127.      */
  128.     private float $volume;
  129.     /**
  130.      * @ORM\Column(type="integer", nullable=true)
  131.      */
  132.     private ?int $units null;
  133.     /**
  134.      * @Vich\UploadableField(mapping="product_image", fileNameProperty="imageName")
  135.      */
  136.     private ?File $image null;
  137.     /**
  138.      * @ORM\Column(type="string", length=255, nullable=true)
  139.      */
  140.     private ?string $imageName null;
  141.     /**
  142.      * @Vich\UploadableField(mapping="product_document", fileNameProperty="documentName")
  143.      */
  144.     private ?File $document null;
  145.     /**
  146.      * @ORM\Column(type="string", length=255, nullable=true)
  147.      *
  148.      * @Vich\UploadableField(mapping="document", fileNameProperty="imageName")
  149.      */
  150.     private ?string $documentName null;
  151.     /**
  152.      * @ORM\Column(type="string", length=255, nullable=true)
  153.      */
  154.     private ?string $documentRealName null;
  155.     /**
  156.      * @ORM\Column(type="boolean")
  157.      */
  158.     private bool $noLinealDiscountEnabled;
  159.     /**
  160.      * @ORM\Column(type="boolean")
  161.      */
  162.     private bool $onlyAdminVisible;
  163.     /**
  164.      * @ORM\Column(type="boolean", nullable=false, options={"default": 0})
  165.      */
  166.     private bool $featured false;
  167.     /**
  168.      * @ORM\Column(type="boolean", nullable=false, options={"default": 0})
  169.      * Use it to include the product in the Meta catalog feed
  170.      */
  171.     private bool $metaCatalog false;
  172.     /**
  173.      * @ORM\ManyToOne(targetEntity="App\Entity\OnlineShop\Family", fetch="EAGER")
  174.      *
  175.      * @ORM\JoinColumn(name="family_id", referencedColumnName="id")
  176.      */
  177.     private Family $family;
  178.     /**
  179.      * @ORM\ManyToMany(targetEntity="App\Entity\OnlineShop\ScalePrice", cascade={"persist"}, orphanRemoval=true)
  180.      *
  181.      * @ORM\JoinTable(name="vulco_shop_product_scale_price",
  182.      *     joinColumns={@ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE")},
  183.      *     inverseJoinColumns={@ORM\JoinColumn(name="scale_price_id", referencedColumnName="id", onDelete="CASCADE", unique=true)}
  184.      * )
  185.      *
  186.      * @ORM\OrderBy({"quantity": "ASC"})
  187.      */
  188.     private ?Collection $scalePrices;
  189.     /**
  190.      * @ORM\ManyToMany(targetEntity="App\Entity\OnlineShop\VolumeDiscount", cascade={"persist"}, orphanRemoval=true)
  191.      *
  192.      * @ORM\JoinTable(name="vulco_shop_product_volume_discount",
  193.      *     joinColumns={@ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE")},
  194.      *     inverseJoinColumns={@ORM\JoinColumn(name="volume_discount_id", referencedColumnName="id", onDelete="CASCADE", unique=true)}
  195.      * )
  196.      *
  197.      * @ORM\OrderBy({"volume": "ASC"})
  198.      */
  199.     private ?Collection $volumeDiscounts;
  200.     /**
  201.      * @ORM\OneToMany(targetEntity="App\Entity\OnlineShop\ProductSpecialPrice", mappedBy="product", cascade={"persist", "remove"}, orphanRemoval=true)
  202.      *
  203.      * @Assert\Valid()
  204.      */
  205.     private ?Collection $productSpecialPrices;
  206.     public function __construct()
  207.     {
  208.         $this->scalePrices = new ArrayCollection();
  209.         $this->volumeDiscounts = new ArrayCollection();
  210.         $this->productSpecialPrices = new ArrayCollection();
  211.         $this->noLinealDiscountEnabled false;
  212.         $this->onlyAdminVisible false;
  213.     }
  214.     public function getReference(): string
  215.     {
  216.         return $this->reference;
  217.     }
  218.     public function setReference(string $reference): self
  219.     {
  220.         $this->reference $reference;
  221.         return $this;
  222.     }
  223.     public function getRatePrice(): ?Money
  224.     {
  225.         if (!$this->ratePriceCurrency) {
  226.             return null;
  227.         }
  228.         if (!$this->ratePriceAmount) {
  229.             return new Money(0, new Currency($this->ratePriceCurrency));
  230.         }
  231.         return new Money($this->ratePriceAmount, new Currency($this->ratePriceCurrency));
  232.     }
  233.     public function getRatePriceWithDiscount($quantity): Money
  234.     {
  235.         return $this->getPriceWithDiscount($this->getRatePrice(), $quantityself::RATE_PRICE);
  236.     }
  237.     public function getRatePriceWithDiscountAndTax($quantity): Money
  238.     {
  239.         $ratePrice $this->getRatePriceWithDiscount($quantity);
  240.         return $ratePrice->add($this->sigaus)->add($this->greenPoint)->add($this->ecoTax);
  241.     }
  242.     public function setRatePrice(Money $ratePrice): self
  243.     {
  244.         $this->ratePriceAmount $ratePrice->getAmount();
  245.         $this->ratePriceCurrency $ratePrice->getCurrency()->getCode();
  246.         return $this;
  247.     }
  248.     public function getRatePriceAmount(): int
  249.     {
  250.         return $this->ratePriceAmount;
  251.     }
  252.     public function setRatePriceAmount(int $ratePriceAmount): self
  253.     {
  254.         $this->ratePriceAmount $ratePriceAmount;
  255.         return $this;
  256.     }
  257.     public function getRatePriceCurrency(): string
  258.     {
  259.         return $this->ratePriceCurrency;
  260.     }
  261.     public function setRatePriceCurrency(string $ratePriceCurrency): self
  262.     {
  263.         $this->ratePriceCurrency $ratePriceCurrency;
  264.         return $this;
  265.     }
  266.     public function getSpecialPriceActive(): ?ProductSpecialPrice
  267.     {
  268.         if ($this->hasSpecialPriceActive()) {
  269.             /** @var ProductSpecialPrice $productSpecialPrice */
  270.             foreach ($this->getProductSpecialPrices() as $productSpecialPrice) {
  271.                 if ($productSpecialPrice->isActive()) {
  272.                     return $productSpecialPrice;
  273.                 }
  274.             }
  275.         }
  276.         return null;
  277.     }
  278.     public function hasSpecialPriceActive(): bool
  279.     {
  280.         if ($this->getProductSpecialPrices() && !$this->getProductSpecialPrices()->isEmpty()) {
  281.             /** @var ProductSpecialPrice $productSpecialPrice */
  282.             foreach ($this->getProductSpecialPrices() as $productSpecialPrice) {
  283.                 if ($productSpecialPrice->isActive()) {
  284.                     return true;
  285.                 }
  286.             }
  287.         }
  288.         return false;
  289.     }
  290.     public function getCostPrice(): Money
  291.     {
  292.         return $this->costPrice;
  293.     }
  294.     public function getCostPriceWithDiscount($quantity): Money
  295.     {
  296.         return $this->getPriceWithDiscount($this->getCostPrice(), $quantityself::COST_PRICE);
  297.     }
  298.     public function setCostPrice(Money $costPrice): self
  299.     {
  300.         $this->costPrice $costPrice;
  301.         return $this;
  302.     }
  303.     public function getPvpPrice(): ?Money
  304.     {
  305.         return $this->pvpPrice;
  306.     }
  307.     public function setPvpPrice(?Money $pvpPrice): self
  308.     {
  309.         $this->pvpPrice $pvpPrice;
  310.         return $this;
  311.     }
  312.     /**
  313.      * Sale price (precio de oferta). At the moment we only use it for the Meta catalog feed. (sale_price field)
  314.      * @return Money|null
  315.      */
  316.     public function getSalePrice(): ?Money
  317.     {
  318.         return $this->salePrice;
  319.     }
  320.     public function getSalePriceAsString(): ?string
  321.     {
  322.         if (!empty($this->salePrice)) {
  323.             // @see \Tbbc\MoneyBundle\Formatter\MoneyFormatter::asFloat
  324.             $amount = (float) $this->salePrice->getAmount();
  325.             $amount $amount pow(102);
  326.             return MiniAbstractBase::getFloatAsString($amount);
  327.         }
  328.         return null;
  329.     }
  330.     public function setSalePrice(?Money $salePrice): self
  331.     {
  332.         $this->salePrice $salePrice;
  333.         return $this;
  334.     }
  335.     public function getPositionGroup(): ?string
  336.     {
  337.         return $this->positionGroup;
  338.     }
  339.     public function setPositionGroup(?string $positionGroup): self
  340.     {
  341.         $this->positionGroup $positionGroup;
  342.         return $this;
  343.     }
  344.     public function getPriceWithDiscount(Money $price$quantity$type): Money
  345.     {
  346.         if ($this->getScalePrices() && !$this->getScalePrices()->isEmpty()) {
  347.             return $this->calculatePriceByScalePrice($price$quantity$type);
  348.         }
  349.         if ($this->getVolumeDiscounts() && !$this->getVolumeDiscounts()->isEmpty()) {
  350.             return $this->calculatePriceByVolumeDiscount($price$quantity$type);
  351.         }
  352.         return $price;
  353.     }
  354.     public function getSigaus(): Money
  355.     {
  356.         return $this->sigaus;
  357.     }
  358.     public function setSigaus(Money $sigaus): self
  359.     {
  360.         $this->sigaus $sigaus;
  361.         return $this;
  362.     }
  363.     public function getGreenPoint(): Money
  364.     {
  365.         return $this->greenPoint;
  366.     }
  367.     public function setGreenPoint(Money $greenPoint): self
  368.     {
  369.         $this->greenPoint $greenPoint;
  370.         return $this;
  371.     }
  372.     public function getEcoTax(): Money
  373.     {
  374.         return $this->ecoTax;
  375.     }
  376.     public function setEcoTax(Money $ecoTax): self
  377.     {
  378.         $this->ecoTax $ecoTax;
  379.         return $this;
  380.     }
  381.     public function getWeight(): float
  382.     {
  383.         return $this->weight;
  384.     }
  385.     public function setWeight(float $weight): self
  386.     {
  387.         $this->weight $weight;
  388.         return $this;
  389.     }
  390.     public function getTransportPoints(): int
  391.     {
  392.         return $this->transportPoints;
  393.     }
  394.     public function setTransportPoints(int $transportPoints): self
  395.     {
  396.         $this->transportPoints $transportPoints;
  397.         return $this;
  398.     }
  399.     public function getPromotion(): ?string
  400.     {
  401.         return $this->promotion;
  402.     }
  403.     public function setPromotion(?string $promotion): self
  404.     {
  405.         $this->promotion $promotion;
  406.         return $this;
  407.     }
  408.     public function getImageUrl(): ?string
  409.     {
  410.         return $this->imageUrl;
  411.     }
  412.     public function setImageUrl(?string $imageUrl): self
  413.     {
  414.         $this->imageUrl $imageUrl;
  415.         return $this;
  416.     }
  417.     public function getSupplierReference(): ?string
  418.     {
  419.         return $this->supplierReference;
  420.     }
  421.     public function setSupplierReference(?string $supplierReference): self
  422.     {
  423.         $this->supplierReference $supplierReference;
  424.         return $this;
  425.     }
  426.     public function getVolume(): float
  427.     {
  428.         return $this->volume;
  429.     }
  430.     public function setVolume(float $volume): self
  431.     {
  432.         $this->volume $volume;
  433.         return $this;
  434.     }
  435.     public function getUnits(): ?int
  436.     {
  437.         return $this->units;
  438.     }
  439.     public function setUnits(?int $units): self
  440.     {
  441.         $this->units $units;
  442.         return $this;
  443.     }
  444.     public function getImageName(): ?string
  445.     {
  446.         return $this->imageName;
  447.     }
  448.     public function setImageName(?string $imageName): self
  449.     {
  450.         $this->imageName $imageName;
  451.         return $this;
  452.     }
  453.     public function getDocumentName(): ?string
  454.     {
  455.         return $this->documentName;
  456.     }
  457.     public function setDocumentName(?string $documentName): self
  458.     {
  459.         $this->documentName $documentName;
  460.         return $this;
  461.     }
  462.     public function getDocumentRealName(): ?string
  463.     {
  464.         return $this->documentRealName;
  465.     }
  466.     public function setDocumentRealName(?string $documentRealName): self
  467.     {
  468.         $this->documentRealName $documentRealName;
  469.         return $this;
  470.     }
  471.     public function isNoLinealDiscountEnabled(): bool
  472.     {
  473.         return $this->noLinealDiscountEnabled;
  474.     }
  475.     public function setNoLinealDiscountEnabled(bool $noLinealDiscountEnabled): self
  476.     {
  477.         $this->noLinealDiscountEnabled $noLinealDiscountEnabled;
  478.         return $this;
  479.     }
  480.     public function isFeatured(): bool
  481.     {
  482.         return $this->featured;
  483.     }
  484.     public function setFeatured(bool $featured): self
  485.     {
  486.         $this->featured $featured;
  487.         return $this;
  488.     }
  489.     public function isMetaCatalog(): bool
  490.     {
  491.         return $this->metaCatalog;
  492.     }
  493.     public function setMetaCatalog(bool $metaCatalog): self
  494.     {
  495.         $this->metaCatalog $metaCatalog;
  496.         return $this;
  497.     }
  498.     public function getFamily(): Family
  499.     {
  500.         return $this->family;
  501.     }
  502.     public function setFamily(Family $family): self
  503.     {
  504.         $this->family $family;
  505.         return $this;
  506.     }
  507.     public function getScalePrices(): ?Collection
  508.     {
  509.         return $this->scalePrices;
  510.     }
  511.     public function setScalePrices(?Collection $scalePrices): self
  512.     {
  513.         $this->scalePrices $scalePrices;
  514.         return $this;
  515.     }
  516.     public function addScalePrice(ScalePrice $scalePrice): self
  517.     {
  518.         if (!$this->scalePrices->contains($scalePrice)) {
  519.             $this->scalePrices->add($scalePrice);
  520.         }
  521.         return $this;
  522.     }
  523.     public function removeScalePrice(ScalePrice $scalePrice): self
  524.     {
  525.         if ($this->scalePrices->contains($scalePrice)) {
  526.             $this->scalePrices->removeElement($scalePrice);
  527.         }
  528.         return $this;
  529.     }
  530.     public function deleteScalePrice(ScalePrice $scalePrice): self
  531.     {
  532.         return $this->removeScalePrice($scalePrice);
  533.     }
  534.     public function getVolumeDiscounts(): ?Collection
  535.     {
  536.         return $this->volumeDiscounts;
  537.     }
  538.     public function setVolumeDiscounts(?Collection $volumeDiscounts): self
  539.     {
  540.         $this->volumeDiscounts $volumeDiscounts;
  541.         return $this;
  542.     }
  543.     public function addVolumeDiscount(VolumeDiscount $volumeDiscount): self
  544.     {
  545.         if (!$this->volumeDiscounts->contains($volumeDiscount)) {
  546.             $this->volumeDiscounts->add($volumeDiscount);
  547.         }
  548.         return $this;
  549.     }
  550.     public function removeVolumeDiscount(VolumeDiscount $volumeDiscount): self
  551.     {
  552.         if ($this->volumeDiscounts->contains($volumeDiscount)) {
  553.             $this->volumeDiscounts->removeElement($volumeDiscount);
  554.         }
  555.         return $this;
  556.     }
  557.     public function deleteVolumeDiscount(VolumeDiscount $volumeDiscount): self
  558.     {
  559.         return $this->removeVolumeDiscount($volumeDiscount);
  560.     }
  561.     public function getProductSpecialPrices(): ?Collection
  562.     {
  563.         return $this->productSpecialPrices;
  564.     }
  565.     public function setProductSpecialPrices(?Collection $productSpecialPrices): self
  566.     {
  567.         $this->productSpecialPrices $productSpecialPrices;
  568.         return $this;
  569.     }
  570.     public function addProductSpecialPrice(ProductSpecialPrice $productSpecialPrice): self
  571.     {
  572.         if (!$this->productSpecialPrices->contains($productSpecialPrice)) {
  573.             $productSpecialPrice->setProduct($this);
  574.             $this->productSpecialPrices->add($productSpecialPrice);
  575.         }
  576.         return $this;
  577.     }
  578.     public function removeProductSpecialPrice(ProductSpecialPrice $productSpecialPrice): self
  579.     {
  580.         if ($this->productSpecialPrices->contains($productSpecialPrice)) {
  581.             $this->productSpecialPrices->removeElement($productSpecialPrice);
  582.         }
  583.         return $this;
  584.     }
  585.     public function deleteProductSpecialPrice(ProductSpecialPrice $productSpecialPrice): self
  586.     {
  587.         return $this->removeProductSpecialPrice($productSpecialPrice);
  588.     }
  589.     public function calculatePriceByVolumeDiscount(Money $price$quantity$type): Money
  590.     {
  591.         $volumeDiscounts $this->getVolumeDiscounts();
  592.         $highVolumeDiscount null;
  593.         /** @var VolumeDiscount $volumeDiscount */
  594.         foreach ($volumeDiscounts as $volumeDiscount) {
  595.             $quantityThreshold $volumeDiscount->getVolume();
  596.             if ($quantity >= $quantityThreshold) {
  597.                 $highVolumeDiscount $volumeDiscount;
  598.             }
  599.         }
  600.         if ($highVolumeDiscount instanceof VolumeDiscount) {
  601.             $discount self::COST_PRICE === $type $highVolumeDiscount->getCostDiscount() : $highVolumeDiscount->getRateDiscount();
  602.             return $price->multiply($discount 100);
  603.         }
  604.         return $price;
  605.     }
  606.     public function calculatePriceByScalePrice(Money $price$quantity$type): Money
  607.     {
  608.         // because if isNoLinealDiscountEnabled this lineals discounts are for no lineal discount and are calculated as a disccount in cartToOrderTransformer
  609.         if (!$this->isNoLinealDiscountEnabled()) {
  610.             $scalePrices $this->getScalePrices();
  611.             foreach ($scalePrices as $scalePrice) {
  612.                 $quantityThreshold $scalePrice->getQuantity();
  613.                 if ($quantity >= $quantityThreshold) {
  614.                     $price self::COST_PRICE === $type $scalePrice->getCostPrice() : $scalePrice->getRatePrice();
  615.                 }
  616.             }
  617.         }
  618.         return $price;
  619.     }
  620.     public function userHasAccess(UserInterface $user): bool
  621.     {
  622.         /** @var User $user */
  623.         $family $this->getFamily();
  624.         // coordinator must be the first condition to evaluate because coordinators have the associated role as well
  625.         if ($user->hasRole(UserRolesEnum::ROLE_COORDINATOR_LONG)) {
  626.             switch ($family->getUserAccessType()) {
  627.                 case UserGroupAccessType::ENABLE:
  628.                     return $user->belongsToUsers($family->getUsers());
  629.                 case UserGroupAccessType::DISABLE:
  630.                     return !$user->belongsToUsers($family->getUsers());
  631.                 case UserGroupAccessType::IGNORE:
  632.                 default:
  633.                     return true;
  634.             }
  635.         } elseif ($user->hasRole(UserRolesEnum::ROLE_ASSOCIATED_LONG)) {
  636.             switch ($family->getUserGroupAccessType()) {
  637.                 case UserGroupAccessType::ENABLE:
  638.                     return $user->belongsToUserGroup($family->getUserGroups());
  639.                 case UserGroupAccessType::DISABLE:
  640.                     return !$user->belongsToUserGroup($family->getUserGroups());
  641.                 case UserGroupAccessType::IGNORE:
  642.                 default:
  643.                     return true;
  644.             }
  645.         } else {
  646.             return true;
  647.         }
  648.     }
  649.     public static function userHasAccessClosure(UserInterface $user): \Closure
  650.     {
  651.         return static function (Product $product) use ($user) {
  652.             $family $product->getFamily();
  653.             // coordinator must be the first condition to evaluate because coordinators have the associated role as well
  654.             if ($user->hasRole(UserRolesEnum::ROLE_COORDINATOR_LONG)) {
  655.                 switch ($family->getUserAccessType()) {
  656.                     case UserGroupAccessType::ENABLE:
  657.                         return $user->belongsToUsers($family->getUsers());
  658.                     case UserGroupAccessType::DISABLE:
  659.                         return !$user->belongsToUsers($family->getUsers());
  660.                     case UserGroupAccessType::IGNORE:
  661.                     default:
  662.                         return true;
  663.                 }
  664.             } elseif ($user->hasRole(UserRolesEnum::ROLE_ASSOCIATED_LONG)) {
  665.                 switch ($family->getUserGroupAccessType()) {
  666.                     case UserGroupAccessType::ENABLE:
  667.                         return $user->belongsToUserGroup($family->getUserGroups());
  668.                     case UserGroupAccessType::DISABLE:
  669.                         return !$user->belongsToUserGroup($family->getUserGroups());
  670.                     case UserGroupAccessType::IGNORE:
  671.                     default:
  672.                         return true;
  673.                 }
  674.             } else {
  675.                 return true;
  676.             }
  677.         };
  678.     }
  679.     public static function getProductsUserHasAccess(UserInterface $useriterable $products): iterable
  680.     {
  681.         $productsUserHasAccess = [];
  682.         if (!empty($products)) {
  683.             // get only products that current user has access
  684.             /** @var Product $product */
  685.             foreach ($products as $product) {
  686.                 if ($product->userHasAccess($user)) {
  687.                     $productsUserHasAccess[] = $product;
  688.                 }
  689.             }
  690.         }
  691.         return $productsUserHasAccess;
  692.     }
  693.     public function getRappel(): Money
  694.     {
  695.         $rappelPercentage $this->getFamily()->getSupplier()->getRappelPercentage();
  696.         if ($this->hasSpecialPriceActive()) {
  697.             return $this->getSpecialPriceActive()->getPrice()->multiply($rappelPercentage 100);
  698.         }
  699.         return $this->getRatePrice()->multiply($rappelPercentage 100);
  700.     }
  701. }