src/Entity/Saloon/Saloon.php line 42

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-04-15
  5.  * Time: 19:50
  6.  */
  7. namespace App\Entity\Saloon;
  8. use AngelGamez\TranslatableBundle\Entity\TranslatableValue;
  9. use App\Entity\Account\Advertiser;
  10. use App\Entity\ApartmentsPricing;
  11. use App\Entity\Account\Customer;
  12. use App\Entity\ExpressPricing;
  13. use App\Entity\IProvidedService;
  14. use App\Entity\IProvidesServices;
  15. use App\Entity\Location\City;
  16. use App\Entity\Location\Station;
  17. use App\Entity\Location\MapCoordinate;
  18. use App\Entity\Messengers;
  19. use App\Entity\PhoneCallRestrictions;
  20. use App\Entity\Sales\Saloon\AdBoardPlacement;
  21. use App\Entity\Saloon\Comment\CommentByCustomer;
  22. use App\Entity\Service;
  23. use App\Entity\Sales\Saloon\PlacementHiding;
  24. use App\Entity\ProvidedServiceTrait;
  25. use App\Entity\TakeOutPricing;
  26. use App\Helper\TopCardTrait;
  27. use App\Repository\SaloonRepository;
  28. use Carbon\Carbon;
  29. use Carbon\CarbonImmutable;
  30. use Doctrine\Common\Collections\ArrayCollection;
  31. use Doctrine\Common\Collections\Collection;
  32. use Doctrine\ORM\Mapping as ORM;
  33. use Gedmo\Mapping\Annotation as Gedmo;
  34. use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
  35. use Symfony\Component\Serializer\Annotation\Groups;
  36. #[Gedmo\SoftDeleteable(fieldName"deletedAt"timeAwaretrue)]
  37. #[ORM\Table(name'saloons')]
  38. #[ORM\Entity(repositoryClassSaloonRepository::class)]
  39. class Saloon implements IProvidesServices
  40. {
  41.     use SoftDeleteableEntity;
  42.     use ProvidedServiceTrait;
  43.     use TopCardTrait;
  44.     #[ORM\Id]
  45.     #[ORM\Column(name'id'type'integer')]
  46.     #[ORM\GeneratedValue(strategy'AUTO')]
  47.     #[Groups('saloon''listing')]
  48.     protected int $id;
  49.     #[ORM\JoinColumn(name'user_id'referencedColumnName'id'nullabletrue)]
  50.     #[ORM\ManyToOne(targetEntityAdvertiser::class, inversedBy'saloons')]
  51.     protected ?Advertiser $owner;
  52.     #[ORM\OneToOne(targetEntityAdBoardPlacement::class, mappedBy'saloon'cascade: ['all'])]
  53.     protected ?AdBoardPlacement $adBoardPlacement;
  54.     #[ORM\OneToOne(targetEntityPlacementHiding::class, mappedBy'saloon'cascade: ['all'])]
  55.     protected ?PlacementHiding $placementHiding;
  56.     #[ORM\Column(name'uri_identity'type'string'length128)]
  57.     #[Groups('saloon''listing')]
  58.     protected string $uriIdentity;
  59.     #[ORM\Column(name'name'type'translatable')]
  60.     #[Groups('saloon''listing')]
  61.     protected TranslatableValue $name;
  62.     #[ORM\Column(name'description'type'translatable')]
  63.     #[Groups('saloon')]
  64.     protected TranslatableValue $description;
  65.     /** @var SaloonService[] */
  66.     #[ORM\OneToMany(targetEntitySaloonService::class, mappedBy'saloon'cascade: ['all'], orphanRemovaltrue)]
  67.     #[ORM\Cache(usage'NONSTRICT_READ_WRITE')]
  68.     #[Groups('saloon''listing')]
  69.     protected Collection $providedServices;
  70.     /** @var int[] */
  71.     #[ORM\Column(name'client_types'type'simple_array'nullabletrue)]
  72.     #[Groups('saloon')]
  73.     protected ?array $clientTypes;
  74.     /**
  75.      * Номера телефонов в свободной форме написания
  76.      */
  77.     #[ORM\Column(name'phone_number'type'string'length255)]
  78.     #[Groups('saloon''listing')]
  79.     protected string $phoneNumber;
  80.     #[ORM\Embedded(class: Messengers::class, columnPrefixfalse)]
  81.     #[Groups('saloon')]
  82.     protected ?Messengers $messengers;
  83.     #[ORM\Embedded(class: PhoneCallRestrictions::class, columnPrefixfalse)]
  84.     #[Groups('saloon')]
  85.     protected ?PhoneCallRestrictions $phoneCallRestrictions;
  86.     #[ORM\Embedded(class: WorkingHours::class, columnPrefix'working_hours_')]
  87.     #[Groups('saloon')]
  88.     protected ?WorkingHours $workingHours;
  89.     /**
  90.      * Расценки в свободной форме написания
  91.      */
  92.     #[ORM\Column(name'prices'type'text'nullabletrue)]
  93.     #[Groups('saloon')]
  94.     protected ?string $prices;
  95.     #[ORM\Embedded(class: ApartmentsPricing::class, columnPrefixfalse)]
  96.     #[Groups('saloon')]
  97.     protected ?ApartmentsPricing $apartmentsPricing;
  98.     #[ORM\Embedded(class: TakeOutPricing::class, columnPrefixfalse)]
  99.     #[Groups('saloon')]
  100.     protected ?TakeOutPricing $takeOutPricing;
  101.     #[ORM\Embedded(class: ExpressPricing::class, columnPrefixfalse)]
  102.     #[Groups('saloon')]
  103.     protected ?ExpressPricing $expressPricing;
  104.     #[ORM\Column(name'extra_charge'type'integer'nullabletrue)]
  105.     #[Groups('saloon')]
  106.     protected ?int $extraCharge;
  107.     #[ORM\OneToOne(targetEntityThumbnail::class, mappedBy'saloon'cascade: ['all'], orphanRemovaltrue)]
  108.     #[Groups('saloon''listing')]
  109.     protected ?Thumbnail $thumbnail null;
  110.     /** @var Photo[] */
  111.     #[ORM\OneToMany(targetEntityPhoto::class, mappedBy'saloon'cascade: ['all'], orphanRemovaltrue)]
  112.     #[Groups('saloon')]
  113.     protected Collection $photos;
  114.     /** @var Video[] */
  115.     #[ORM\OneToMany(targetEntityVideo::class, mappedBy'saloon'cascade: ['all'], orphanRemovaltrue)]
  116.     #[Groups('saloon')]
  117.     protected Collection $videos;
  118.     /** @var CommentByCustomer[] */
  119.     #[ORM\OneToMany(targetEntityCommentByCustomer::class, mappedBy'saloon')]
  120.     #[Groups('saloon')]
  121.     protected Collection $comments;
  122.     #[ORM\JoinColumn(name'city_id'referencedColumnName'id')]
  123.     #[ORM\ManyToOne(targetEntityCity::class)]
  124.     #[Groups('saloon''listing')]
  125.     protected City $city;
  126.     /** @var Station[] */
  127.     #[ORM\JoinTable(name'saloon_stations')]
  128.     #[ORM\JoinColumn(name'saloon_id'referencedColumnName'id')]
  129.     #[ORM\InverseJoinColumn(name'station_id'referencedColumnName'id')]
  130.     #[ORM\ManyToMany(targetEntityStation::class)]
  131.     #[Groups('saloon''listing')]
  132.     protected Collection $stations;
  133.     /**
  134.      * Районы в свободной форме написания
  135.      */
  136.     #[ORM\Column(name'districts'type'string'length255nullabletrue)]
  137.     #[Groups('saloon''listing')]
  138.     protected ?string $districts;
  139.     #[ORM\Column(name'address'type'translatable'nullabletrue)]
  140.     #[Groups('saloon''listing')]
  141.     protected ?TranslatableValue $address;
  142.     #[ORM\Embedded(class: MapCoordinate::class, columnPrefixfalse)]
  143.     #[Groups('saloon''listing')]
  144.     protected MapCoordinate $mapCoordinate;
  145.     #[ORM\Column(name'created_at'type'datetimetz_immutable'nullabletrue)]
  146.     #[Groups('saloon')]
  147.     protected ?\DateTimeImmutable $createdAt;
  148.     #[Gedmo\Timestampable(on"update")]
  149.     #[ORM\Column(name'updated_at'type'datetimetz_immutable'nullabletrue)]
  150.     #[Groups('saloon')]
  151.     protected ?\DateTimeImmutable $updatedAt;
  152.     #[ORM\Column(name'inactivated_at'type'datetimetz_immutable'nullabletrue)]
  153.     protected ?\DateTimeImmutable $inactivatedAt;
  154.     #[ORM\Column(name'email'type'string'length180)]
  155.     #[Groups('saloon')]
  156.     protected string $email;
  157.     private bool $draft false;
  158.     #[ORM\Column(name'seo'type'json'nullabletrue)]
  159.     #[Groups('saloon''listing')]
  160.     private ?array $seo null;
  161.     #[ORM\ManyToOne(targetEntityStation::class)]
  162.     #[ORM\JoinColumn(name'primary_station_id'referencedColumnName'id'nullabletrueonDelete'SET NULL')]
  163.     #[Groups('saloon''listing')]
  164.     private ?Station $primaryStation null;
  165.     #[ORM\Column(type'smallint'options: ['default' => 0])]
  166.     private int $deleteMode 0;
  167.     public function __construct(string $uriIdentity, ?\DateTimeImmutable $createdAt null)
  168.     {
  169.         $this->draft true;
  170.         $this->uriIdentity $uriIdentity;
  171.         $this->createdAt $createdAt ?? CarbonImmutable::now();
  172.         $this->photos = new ArrayCollection();
  173.         $this->videos = new ArrayCollection();
  174.         $this->providedServices = new ArrayCollection();
  175.     }
  176.     public static function draft(?\DateTimeImmutable $createdAt null): self
  177.     {
  178.         $saloon = new static($createdAt);
  179.         return $saloon;
  180.     }
  181.     public function defineUriIdentity(string $uriIdentity): void
  182.     {
  183.         if (!$this->isDraft()) {
  184.             throw new \DomainException('Saloon is already created and can\'t change its URI.');
  185.         }
  186.         $this->uriIdentity $uriIdentity;
  187.         $this->draft false;
  188.     }
  189.     public function isDraft(): bool
  190.     {
  191.         return $this->draft;
  192.     }
  193.     public function hasOwner(): bool
  194.     {
  195.         return null !== $this->owner;
  196.     }
  197.     public function setNameAndDescription(TranslatableValue $nameTranslatableValue $description): void
  198.     {
  199.         if (!empty($name)) {
  200.             $this->name $name;
  201.         }
  202.         if (!empty($description)) {
  203.             $this->description $description;
  204.         }
  205.     }
  206.     public function setLocation(City $city$stations$districts$address, ?MapCoordinate $mapCoordinate): void
  207.     {
  208.         $this->city $city;
  209.         if (null !== $stations) {
  210.             if (is_array($stations)) {
  211.                 $stations = new ArrayCollection($stations);
  212.             } elseif (!$stations instanceof ArrayCollection) {
  213.                 if (is_iterable($stations)) {
  214.                     $stations = new ArrayCollection(iterator_to_array($stations));
  215.                 } else {
  216.                     throw new \InvalidArgumentException('Stations list should be either an array or an ArrayCollection');
  217.                 }
  218.             }
  219.             $this->stations $stations;
  220.         }
  221.         if (!empty($districts)) {
  222.             $this->districts $districts;
  223.         }
  224.         if (!empty($address)) {
  225.             $this->address $address;
  226.         }
  227.         $this->mapCoordinate $mapCoordinate;
  228.         $this->normalizePrimaryStation();
  229.     }
  230.     private function normalizePrimaryStation(): void
  231.     {
  232.         if ($this->stations->isEmpty()) {
  233.             $this->primaryStation null;
  234.             return;
  235.         }
  236.         if ($this->primaryStation === null || !$this->stations->contains($this->primaryStation)) {
  237.             $this->primaryStation $this->stations->first();
  238.         }
  239.     }
  240.     public function setPhoneCallOptions(string $phoneNumber, ?PhoneCallRestrictions $restrictions, ?Messengers $messengers): void
  241.     {
  242.         $this->phoneNumber $phoneNumber;
  243.         $this->phoneCallRestrictions $restrictions;
  244.         $this->messengers $messengers;
  245.     }
  246.     public function setPricing(?ApartmentsPricing $apartmentsPricing, ?TakeOutPricing $takeOutPricing, ?int $extraCharge, ?ExpressPricing $expressPricing null): void
  247.     {
  248. //        $this->prices = $pricing;
  249.         $this->apartmentsPricing $apartmentsPricing;
  250.         $this->takeOutPricing $takeOutPricing;
  251.         $this->extraCharge $extraCharge;
  252.         $this->expressPricing $expressPricing;
  253.     }
  254.     public function isOwnedBy(Advertiser $account): bool
  255.     {
  256.         return $account->getId() === $this->owner->getId();
  257.     }
  258.     public function getId(): int
  259.     {
  260.         return $this->id;
  261.     }
  262.     public function getOwner(): ?Advertiser
  263.     {
  264.         return $this->owner;
  265.     }
  266.     public function setOwner(Advertiser $owner): void
  267.     {
  268.         $this->owner $owner;
  269.     }
  270.     public function getAdBoardPlacement(): ?AdBoardPlacement
  271.     {
  272.         return $this->adBoardPlacement;
  273.     }
  274.     /**
  275.      * Салон оплачен и выводится в общих списках на сайте
  276.      */
  277.     public function isActive(): bool
  278.     {
  279.         return null !== $this->adBoardPlacement;
  280.     }
  281.     public function getUriIdentity(): string
  282.     {
  283.         return $this->uriIdentity;
  284.     }
  285.     public function getName(): TranslatableValue
  286.     {
  287.         return $this->name;
  288.     }
  289.     public function getDescription(): TranslatableValue
  290.     {
  291.         return $this->description;
  292.     }
  293.     public function getPhoneNumber(): string
  294.     {
  295.         return $this->phoneNumber;
  296.     }
  297.     public function getWorkingHours(): ?WorkingHours
  298.     {
  299.         return $this->workingHours;
  300.     }
  301.     public function setWorkingHours(?WorkingHours $workingHours): void
  302.     {
  303. //        $this->phoneNumber = $phoneNumber;
  304.         $this->workingHours $workingHours;
  305.     }
  306.     //TODO return type
  307.     public function getEmail(): ?string
  308.     {
  309.         return $this->email;
  310.     }
  311.     public function setEmail(string $email): void
  312.     {
  313.         $this->email $email;
  314.     }
  315.     public function getPrices(): ?string
  316.     {
  317.         return $this->prices;
  318.     }
  319.     //TODO return type
  320.     public function getApartmentsPricing(): ?ApartmentsPricing
  321.     {
  322.         return $this->apartmentsPricing;
  323.     }
  324.     //TODO return type
  325.     public function getTakeOutPricing(): ?TakeOutPricing
  326.     {
  327.         return $this->takeOutPricing;
  328.     }
  329.     public function getExtraCharge(): ?int
  330.     {
  331.         return $this->extraCharge;
  332.     }
  333.     public function getThumbnail(): ?Thumbnail
  334.     {
  335.         return $this->thumbnail;
  336.     }
  337.     public function setThumbnail(string $path): void
  338.     {
  339.         $this->thumbnail = new Thumbnail($this$path);
  340.     }
  341.     public function getCity(): City
  342.     {
  343.         return $this->city;
  344.     }
  345.     /**
  346.      * @return Station[]
  347.      */
  348.     public function getStations(): Collection
  349.     {
  350.         return $this->stations;
  351.     }
  352.     public function getDistricts(): ?string
  353.     {
  354.         return $this->districts;
  355.     }
  356.     public function getAddress(): TranslatableValue
  357.     {
  358.         return $this->address;
  359.     }
  360.     public function getMapCoordinate(): ?MapCoordinate
  361.     {
  362.         return $this->mapCoordinate;
  363.     }
  364.     public function getUpdatedAt(): ?\DateTimeImmutable
  365.     {
  366.         return $this->updatedAt;
  367.     }
  368.     //TODO return type
  369.     public function setUpdatedAt(\DateTimeImmutable $updatedAt): void
  370.     {
  371.         $this->updatedAt $updatedAt;
  372.     }
  373.     public function addPhoto(string $path): Photo
  374.     {
  375.         $photos $this->getPhotos();
  376.         $found $photos->filter(function (Photo $photo) use ($path): bool {
  377.             return $path === $photo->getPath();
  378.         });
  379.         if (!$found->isEmpty()) {
  380.             return $found->first();
  381.         }
  382.         $photo = new Photo($this$path);
  383.         $this->photos->add($photo);
  384.         return $photo;
  385.     }
  386.     /**
  387.      * @return Photo[]
  388.      */
  389.     public function getPhotos(): Collection
  390.     {
  391.         return $this->photos;
  392.     }
  393.     public function removePhoto(string $path): void
  394.     {
  395.         foreach ($this->getPhotos() as $photo) {
  396.             if ($path === $photo->getPath()) {
  397.                 $this->photos->removeElement($photo);
  398.             }
  399.         }
  400.     }
  401.     public function addVideo(string $path): Video
  402.     {
  403.         $found $this->getVideos()->filter(function (Video $video) use ($path): bool {
  404.             return $path === $video->getPath();
  405.         });
  406.         if (!$found->isEmpty())
  407.             return $found->first();
  408.         $video = new Video($this$path);
  409.         //теперь разрешаем много видео
  410.         //$this->videos->clear();
  411.         $this->videos->add($video);
  412.         return $video;
  413.     }
  414.     /**
  415.      * @return Photo[]
  416.      */
  417.     public function getVideos(): Collection
  418.     {
  419.         return $this->videos;
  420.     }
  421.     public function removeVideo(string $path): void
  422.     {
  423.         foreach ($this->getVideos() as $video) {
  424.             if ($path === $video->getPath()) {
  425.                 $this->videos->removeElement($video);
  426.             }
  427.         }
  428.     }
  429.     public function isMediaProcessed(): bool
  430.     {
  431.         foreach ($this->videos as $video)
  432.             if (null === $video->getPreviewPath())
  433.                 return false;
  434.         return true;
  435.     }
  436.     public function delete(): void
  437.     {
  438.         $this->deletePlacementHiding();
  439.         $this->deleteFromAdBoard();
  440.         $this->setDeletedAt(Carbon::now());
  441.     }
  442.     public function deletePlacementHiding(): void
  443.     {
  444.         $this->placementHiding null;
  445.     }
  446.     public function deleteFromAdBoard(): void
  447.     {
  448.         $this->adBoardPlacement null;
  449.     }
  450.     //TODO return type
  451.     public function undoDelete(): void
  452.     {
  453.         $this->setDeletedAt(); // will pass null by default
  454.     }
  455.     //TODO return type
  456.     public function getPhoneCallRestrictions(): ?PhoneCallRestrictions
  457.     {
  458.         return $this->phoneCallRestrictions;
  459.     }
  460.     public function getExpressPricing(): ?ExpressPricing
  461.     {
  462.         return $this->expressPricing;
  463.     }
  464.     public function getClientTypes(): ?array
  465.     {
  466.         return $this->clientTypes;
  467.     }
  468.     //TODO return type
  469.     /**
  470.      * @param int[] $clientTypes
  471.      */
  472.     public function setClientTypes(?array $clientTypes): void
  473.     {
  474.         $this->clientTypes $clientTypes;
  475.     }
  476.     public function getMessengers(): ?Messengers
  477.     {
  478.         return $this->messengers;
  479.     }
  480.     public function getInactivatedAt(): ?\DateTimeImmutable
  481.     {
  482.         return $this->inactivatedAt;
  483.     }
  484.     public function setInactive(): void
  485.     {
  486.         $this->inactivatedAt CarbonImmutable::now();
  487.     }
  488.     public function undoInactive(): void
  489.     {
  490.         $this->inactivatedAt null;
  491.     }
  492.     public function isHidden(): bool
  493.     {
  494.         return null !== $this->getPlacementHiding();
  495.     }
  496.     public function getPlacementHiding(): ?PlacementHiding
  497.     {
  498.         return $this->placementHiding;
  499.     }
  500.     /**
  501.      * @return CommentByCustomer[]
  502.      */
  503.     public function getComments(): Collection
  504.     {
  505.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  506.             return null == $comment->getParent();
  507.         });
  508.     }
  509.     /**
  510.      * @return CommentByCustomer[]
  511.      */
  512.     public function getCommentsOrderedByNotReplied(): array
  513.     {
  514.         $comments $this->comments->filter(function (CommentByCustomer $comment): bool {
  515.             return null == $comment->getParent();
  516.         })->toArray();
  517.         usort($comments, function (CommentByCustomer $commentACommentByCustomer $commentB): int {
  518.             if ((null == $commentA->getLastCommentByAdvertiser() && null == $commentB->getLastCommentByAdvertiser())
  519.                 || (null != $commentA->getLastCommentByAdvertiser() && null != $commentB->getLastCommentByAdvertiser())) {
  520.                 if ($commentA->getCreatedAt() == $commentB->getCreatedAt())
  521.                     return $commentA->getId() > $commentB->getId() ? -1;
  522.                 else
  523.                     return $commentA->getCreatedAt() > $commentB->getCreatedAt() ? -1;
  524.             }
  525.             if (null == $commentA->getLastCommentByAdvertiser() && null != $commentB->getLastCommentByAdvertiser())
  526.                 return -1;
  527.             else
  528.                 return 1;
  529.         });
  530.         return $comments;
  531.     }
  532.     public function getCreatedAt(): ?\DateTimeImmutable
  533.     {
  534.         return $this->createdAt;
  535.     }
  536.     /**
  537.      * @return CommentByCustomer[]
  538.      */
  539.     public function getCommentsWithoutReply(): Collection
  540.     {
  541.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  542.             return null == $comment->getParent() && false == $comment->isCommentedByAdvertiser();
  543.         });
  544.     }
  545.     /**
  546.      * @return CommentByCustomer[]
  547.      */
  548.     public function getCommentsWithReply(): Collection
  549.     {
  550.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  551.             return null == $comment->getParent() && true == $comment->isCommentedByAdvertiser();
  552.         });
  553.     }
  554.     /**
  555.      * @return CommentByCustomer[]
  556.      */
  557.     public function getNewComments(): Collection
  558.     {
  559.         $weekAgo CarbonImmutable::now()->sub('7 days');
  560.         return $this->comments->filter(function (CommentByCustomer $comment) use ($weekAgo): bool {
  561.             return null == $comment->getParent()
  562.                 && (
  563.                     $comment->getCreatedAt() >= $weekAgo
  564.                     || null == $this->getCommentReply($comment)
  565.                 );
  566.         });
  567.     }
  568.     private function getCommentReply(CommentByCustomer $parent): ?CommentByCustomer
  569.     {
  570.         foreach ($this->comments as $comment)
  571.             if ($comment->getParent() == $parent)
  572.                 return $comment;
  573.         return null;
  574.     }
  575.     public function getOldComments(): Collection
  576.     {
  577.         $weekAgo CarbonImmutable::now()->sub('7 days');
  578.         return $this->comments->filter(function (CommentByCustomer $comment) use ($weekAgo): bool {
  579.             return null == $comment->getParent()
  580.                 && false == (
  581.                     $comment->getCreatedAt() >= $weekAgo
  582.                     || null == $this->getCommentReply($comment)
  583.                 );
  584.         });
  585.     }
  586.     public function getCommentFromUser(Customer $user): ?CommentByCustomer
  587.     {
  588.         foreach ($this->comments as $comment)
  589.             if (null == $comment->getParent() && null != $comment->getUser() && $user->getId() == $comment->getUser()->getId())
  590.                 return $comment;
  591.         return null;
  592.     }
  593.     public function seo(): ?array
  594.     {
  595.         return $this->seo;
  596.     }
  597.     public function seoPhoneNumber(): ?string
  598.     {
  599.         return $this->seo['phone'] ?? null;
  600.     }
  601.     public function setSeoPhoneNumber(string $phoneNumber): void
  602.     {
  603.         if (null === $this->seo) {
  604.             $this->seo = [];
  605.         }
  606.         $this->seo['phone'] = $phoneNumber;
  607.     }
  608.     public function getPrimaryStation(): ?Station
  609.     {
  610.         $station $this->primaryStation ?? $this->getStations()->first();
  611.         if (false === $station) {
  612.             return null;
  613.         }
  614.         return $station;
  615.     }
  616.     public function setPrimaryStation(?Station $station): void
  617.     {
  618.         $this->primaryStation $station;
  619.         $this->normalizePrimaryStation();
  620.     }
  621.     public function getStationsSortedByPrimary(): array
  622.     {
  623.         $stations $this->stations->toArray();
  624.         if (!$this->primaryStation) {
  625.             return $stations;
  626.         }
  627.         usort($stations, function (Station $aStation $b) {
  628.             if ($a->getId() === $this->primaryStation->getId()) return -1;
  629.             if ($b->getId() === $this->primaryStation->getId()) return 1;
  630.             return 0;
  631.         });
  632.         return $stations;
  633.     }
  634.     public function getDeleteMode(): int
  635.     {
  636.         return $this->deleteMode;
  637.     }
  638.     public function setDeleteMode(int $deleteMode): self
  639.     {
  640.         $this->deleteMode $deleteMode;
  641.         return $this;
  642.     }
  643.     public function isHardDeleted(): bool
  644.     {
  645.         return $this->deleteMode === 2;
  646.     }
  647. }