src/Entity/Profile/Profile.php line 74

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-03-19
  5.  * Time: 15:36
  6.  */
  7. namespace App\Entity\Profile;
  8. use AngelGamez\TranslatableBundle\Entity\TranslatableValue;
  9. //use ApiPlatform\Core\Annotation\ApiProperty;
  10. use App\Entity\Account\Advertiser;
  11. use App\Entity\ApartmentsPricing;
  12. use App\Entity\ContainsDomainEvents;
  13. use App\Entity\Account\Customer;
  14. use App\Entity\DomainEventsRecorderTrait;
  15. use App\Entity\ExpressPricing;
  16. use App\Entity\IProvidesServices;
  17. use App\Entity\Location\City;
  18. use App\Entity\Location\MapCoordinate;
  19. use App\Entity\Location\Station;
  20. use App\Entity\Messengers;
  21. use App\Entity\PhoneCallRestrictions;
  22. use App\Entity\Profile\Comment\CommentByCustomer;
  23. use App\Entity\Profile\Confirmation\ModerationRequest;
  24. use App\Entity\Sales\Profile\AdBoardPlacement;
  25. use App\Entity\Sales\Profile\PlacementHiding;
  26. use App\Entity\Sales\Profile\TopPlacement;
  27. use App\Entity\ProvidedServiceTrait;
  28. use App\Entity\TakeOutPricing;
  29. use App\Repository\ProfileRepository;
  30. use Carbon\Carbon;
  31. use Carbon\CarbonImmutable;
  32. use Doctrine\Common\Collections\ArrayCollection;
  33. use Doctrine\Common\Collections\Collection;
  34. use Doctrine\ORM\Mapping as ORM;
  35. use Doctrine\ORM\Mapping\Index;
  36. //use ApiPlatform\Core\Annotation\ApiResource;
  37. //use ApiPlatform\Core\Annotation\ApiFilter;
  38. //use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
  39. //use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter;
  40. use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
  41. use Symfony\Component\Serializer\Annotation\Groups;
  42. use Gedmo\Mapping\Annotation as Gedmo;
  43. use App\Validator\Constraints\ValidPhoneForCountry as ValidPhoneForCountryAssert;
  44. use App\Validator\Constraints\PhoneNotBlack as PhoneNotBlackAssert;
  45. /**
  46.  * ApiResource(collectionOperations={"get"}, itemOperations={"get"}, normalizationContext={"groups"={"profile"}}, attributes={"pagination_client_enabled"=true, "pagination_client_items_per_page"=true})
  47.  * ApiFilter(SearchFilter::class, properties={"city": "exact", "providedServices": "exact"})
  48.  * ApiFilter(RangeFilter::class, properties={"personParameters.age", "personParameters.height", "personParameters.weight", "personParameters.breastSize", "apartmentsPricing.oneHourPrice"})
  49.  * @ValidPhoneForCountryAssert/ProtocolClass
  50.  * @PhoneNotBlackAssert/ProtocolClass
  51.  */
  52. #[Gedmo\SoftDeleteable(fieldName"deletedAt"timeAwaretrue)]
  53. #[ORM\Table(name'profiles')]
  54. #[Index(name'idx_deleted_at'columns: ['deleted_at'])]
  55. #[Index(name'idx_created_at'columns: ['created_at'])]
  56. #[Index(name'idx_gender'columns: ['person_gender'])]
  57. #[Index(name'idx_apartments_one_hour_price'columns: ['apartments_one_hour_price'])]
  58. #[Index(name'idx_is_dummy'columns: ['is_dummy'])]
  59. #[Index(name'idx_city_deleted'columns: ['city_id''deleted_at'])]
  60. #[Index(name'idx_city_deleted_moderation'columns: ['city_id''deleted_at''moderation_status'])]
  61. #[Index(name'idx_city_masseur_deleted'columns: ['city_id''is_masseur''deleted_at'])]
  62. #[Index(name'idx_city_masseur_deleted_moderation'columns: ['city_id''is_masseur''deleted_at''moderation_status'])]
  63. #[Index(name'idx_city_deleted_gender'columns: ['city_id''deleted_at''person_gender'])]
  64. #[Index(name'idx_city_deleted_moderation_gender'columns: ['city_id''deleted_at''moderation_status''person_gender'])]
  65. #[Index(name'idx_city_masseur_deleted_gender'columns: ['city_id''is_masseur''deleted_at''person_gender'])]
  66. #[Index(name'idx_city_masseur_deleted_moderation_gender'columns: ['city_id''is_masseur''deleted_at''moderation_status''person_gender'])]
  67. #[ORM\Entity(repositoryClassProfileRepository::class)]
  68. #[ORM\HasLifecycleCallbacks]
  69. class Profile implements ContainsDomainEventsIProvidesServices
  70. {
  71.     use SoftDeleteableEntity;
  72.     use DomainEventsRecorderTrait;
  73.     use ProvidedServiceTrait;
  74.     const MODERATION_STATUS_NOT_PASSED 0;
  75.     const MODERATION_STATUS_APPROVED 1;
  76.     const MODERATION_STATUS_WAITING 2;
  77.     const MODERATION_STATUS_REJECTED 3;
  78.     #[ORM\Id]
  79.     #[ORM\Column(name'id'type'integer')]
  80.     #[ORM\GeneratedValue(strategy'AUTO')]
  81.     #[Groups('profile')]
  82.     protected int $id;
  83.     #[ORM\JoinColumn(name'user_id'referencedColumnName'id'nullabletrue)]
  84.     #[ORM\ManyToOne(targetEntityAdvertiser::class, inversedBy'profiles')]
  85.     protected ?Advertiser $owner;
  86.     #[ORM\Column(name'is_dummy'type'boolean'options: ['default' => 0])]
  87.     protected bool $dummy false;
  88.     /** @var TopPlacement[] */
  89.     #[ORM\OneToMany(targetEntityTopPlacement::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  90.     protected Collection $topPlacements;
  91.     #[ORM\OneToOne(targetEntityAdBoardPlacement::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  92.     protected ?AdBoardPlacement $adBoardPlacement null;
  93.     #[ORM\OneToOne(targetEntityPlacementHiding::class, mappedBy'profile'cascade: ['all'])]
  94.     protected ?PlacementHiding $placementHiding null;
  95.     #[ORM\Column(name'uri_identity'type'string'length64)]
  96.     #[Groups('profile')]
  97.     protected string $uriIdentity;
  98.     #[ORM\Column(name'name'type'translatable')]
  99.     #[Groups('profile')]
  100.     protected TranslatableValue $name;
  101.     #[ORM\Column(name'description'type'translatable')]
  102.     #[Groups('profile')]
  103.     protected ?TranslatableValue $description null;
  104.     #[ORM\Embedded(class: PersonParameters::class, columnPrefix'person_')]
  105.     #[Groups('profile')]
  106.     protected PersonParameters $personParameters;
  107.     /** var ProfileService[] */
  108.     #[ORM\OneToMany(targetEntityProfileService::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  109.     #[ORM\Cache(usage'NONSTRICT_READ_WRITE'region'profiles')]
  110.     protected Collection $providedServices;
  111.     /** @var int[] */
  112.     #[ORM\Column(name'client_types'type'simple_array'nullabletrue)]
  113.     protected ?array $clientTypes;
  114.     #[ORM\Column(name'phone_number'type'string'length24)]
  115.     #[Groups('profile')]
  116.     protected string $phoneNumber;
  117.     #[ORM\Embedded(class: Messengers::class, columnPrefixfalse)]
  118.     #[Groups('profile')]
  119.     protected ?Messengers $messengers null;
  120.     #[ORM\Embedded(class: PhoneCallRestrictions::class, columnPrefixfalse)]
  121.     protected ?PhoneCallRestrictions $phoneCallRestrictions null;
  122.     #[ORM\Column(name'is_masseur'type'boolean')]
  123.     protected bool $masseur false;
  124.     #[ORM\Embedded(class: ClientRestrictions::class, columnPrefixfalse)]
  125.     protected ?ClientRestrictions $clientRestrictions null;
  126.     #[ORM\Embedded(class: ApartmentsPricing::class, columnPrefixfalse)]
  127.     #[Groups('profile')]
  128.     protected ?ApartmentsPricing $apartmentsPricing null;
  129.     #[ORM\Embedded(class: TakeOutPricing::class, columnPrefixfalse)]
  130.     #[Groups('profile')]
  131.     protected ?TakeOutPricing $takeOutPricing null;
  132.     #[ORM\Embedded(class: ExpressPricing::class, columnPrefixfalse)]
  133.     #[Groups('profile')]
  134.     protected ?ExpressPricing $expressPricing null;
  135.     #[ORM\Embedded(class: CarPricing::class, columnPrefixfalse)]
  136.     #[Groups('profile')]
  137.     protected ?CarPricing $carPricing null;
  138.     #[ORM\Column(name'extra_charge'type'integer'nullabletrue)]
  139.     #[Groups('profile')]
  140.     protected ?int $extraCharge;
  141.     #[ORM\Column(name'prepayment'type'boolean'nullabletrue)]
  142.     protected ?bool $prepayment null;
  143.     #[ORM\Column(name'prepayment_amount'type'integer'nullabletrue)]
  144.     protected ?int $prepaymentAmount null;
  145.     #[ORM\Column(name'prepayment_comment'type'string'length512nullabletrue)]
  146.     protected ?string $prepaymentComment null;
  147.     /** @var Photo[] */
  148.     #[ORM\OneToMany(targetEntityPhoto::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  149.     #[Groups('profile')]
  150.     protected Collection $photos;
  151.     /** @var Selfie[] */
  152.     #[ORM\OneToMany(targetEntitySelfie::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  153.     #[Groups('profile')]
  154.     protected Collection $selfies;
  155.     /** @var Video[] */
  156.     #[ORM\OneToMany(targetEntityVideo::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  157.     protected Collection $videos;
  158.     #[ORM\OneToMany(targetEntityFileProcessingTask::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  159.     protected Collection $processingFiles;
  160.     #[ORM\OneToOne(targetEntityAdminApprovalPhoto::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  161.     protected ?AdminApprovalPhoto $adminApprovalPhoto null;
  162.     #[ORM\OneToOne(targetEntityAvatar::class, mappedBy'profile'cascade: ['all'], orphanRemovaltrue)]
  163.     protected ?Avatar $avatar null;
  164.     /** @var CommentByCustomer[] */
  165.     #[ORM\OneToMany(targetEntityCommentByCustomer::class, mappedBy'profile')]
  166.     protected Collection $comments;
  167.     #[ORM\Column(name'is_approved'type'boolean')]
  168.     #[Groups('profile')]
  169.     protected bool $approved false;
  170.     #[ORM\Column(name'moderation_status'type'integer')]
  171.     #[Groups('profile')]
  172.     protected int $moderationStatus 0;
  173.     #[ORM\JoinColumn(name'city_id'referencedColumnName'id')]
  174.     #[ORM\ManyToOne(targetEntityCity::class)]
  175.     #[ORM\Cache(usage'NONSTRICT_READ_WRITE'region'profiles')]
  176.     protected City $city;
  177.     /** @var Station[] */
  178.     //, indexBy="id"
  179.     #[ORM\JoinTable(name'profile_stations')]
  180.     #[ORM\JoinColumn(name'profile_id'referencedColumnName'id')]
  181.     #[ORM\InverseJoinColumn(name'station_id'referencedColumnName'id')]
  182.     #[ORM\ManyToMany(targetEntityStation::class)]
  183.     #[Groups('profile')]
  184.     #[ORM\Cache(usage'NONSTRICT_READ_WRITE'region'profiles')]
  185.     protected Collection $stations;
  186.     #[ORM\Embedded(class: MapCoordinate::class, columnPrefixfalse)] // ApiProperty()
  187.     #[Groups('profile')]
  188.     protected ?MapCoordinate $mapCoordinate;
  189.     #[ORM\Column(name'created_at'type'datetimetz_immutable'nullabletrue)]
  190.     protected ?\DateTimeImmutable $createdAt;
  191.     #[Gedmo\Timestampable(on"change"field: ["name""description""personParameters""providedServices""clientTypes""phoneNumber""messengers""phoneCallrestrictions""masseur""clientRestrictions""apartmentsPricing""takeOutPricing""expressPricing""carPricing""extraCharge""prepayment""prepaymentAmount""prepaymentComment""photos""selfies""videos""avatar""stations""mapCoordinate"])]
  192.     #[ORM\Column(name'updated_at'type'datetimetz_immutable'nullabletrue)]
  193.     #[Groups('profile')]
  194.     protected ?\DateTimeImmutable $updatedAt;
  195.     #[ORM\Column(name'inactivated_at'type'datetimetz_immutable'nullabletrue)]
  196.     protected ?\DateTimeImmutable $inactivatedAt;
  197.     private bool $draft false;
  198.     #[ORM\Column(name'seo'type'json'nullabletrue)]
  199.     #[Groups('profile')]
  200.     private ?array $seo null;
  201.     #[ORM\ManyToOne(targetEntityStation::class)]
  202.     #[ORM\JoinColumn(name'primary_station_id'referencedColumnName'id'nullabletrueonDelete'SET NULL')]
  203.     #[Groups('profile')]
  204.     #[ORM\Cache(usage'READ_ONLY')]
  205.     private ?Station $primaryStation null;
  206.     #[ORM\Column(type'smallint'options: ['default' => 0])]
  207.     private int $deleteMode 0;
  208.     protected function __construct(?\DateTimeImmutable $createdAt)
  209.     {
  210.         $this->draft true;
  211.         $this->createdAt $createdAt;
  212.         $this->photos = new ArrayCollection();
  213.         $this->selfies = new ArrayCollection();
  214.         $this->videos = new ArrayCollection();
  215.         $this->processingFiles = new ArrayCollection();
  216.         $this->comments = new ArrayCollection();
  217.         $this->topPlacements = new ArrayCollection();
  218.         $this->providedServices = new ArrayCollection();
  219.         $this->stations = new ArrayCollection();
  220.         $this->inactivatedAt CarbonImmutable::now();
  221.     }
  222.     public static function draft(?\DateTimeImmutable $createdAt null, ?bool $dummy null): self
  223.     {
  224.         $profile = new static($createdAt);
  225.         if (null !== $dummy)
  226.             $profile->dummy $dummy;
  227.         return $profile;
  228.     }
  229.     public static function create(string $uriIdentity, ?\DateTimeImmutable $createdAt null): self
  230.     {
  231.         $profile = new static($createdAt);
  232.         $profile->defineUriIdentity($uriIdentity);
  233.         $profile->toggleMasseur(false);
  234.         return $profile;
  235.     }
  236.     public function defineUriIdentity(string $uriIdentity): void
  237.     {
  238.         if (!$this->isDraft()) {
  239.             throw new \DomainException('Profile is already created and can\'t change its URI.');
  240.         }
  241.         $this->uriIdentity $uriIdentity;
  242.         $this->draft false;
  243.     }
  244.     public function isDraft(): bool
  245.     {
  246.         return $this->draft;
  247.     }
  248.     public function toggleMasseur(bool $isMasseur): void
  249.     {
  250.         if ($this->masseur !== $isMasseur && (null !== $this->adBoardPlacement && false == $this->adBoardPlacement->getType()->isFree())) {
  251.             throw new \DomainException('Impossible to toggle profile type while it is displaying on adboard.');
  252.         }
  253.         $this->masseur $isMasseur;
  254.     }
  255.     public static function createMasseur(string $uriIdentity, ?\DateTimeImmutable $createdAt null): self
  256.     {
  257.         $profile = new static($createdAt);
  258.         $profile->defineUriIdentity($uriIdentity);
  259.         $profile->toggleMasseur(true);
  260.         return $profile;
  261.     }
  262.     public function isOwnedBy(Advertiser $account): bool
  263.     {
  264.         return $account->getId() === $this->owner->getId();
  265.     }
  266.     public function getId(): int
  267.     {
  268.         return $this->id;
  269.     }
  270.     public function setBio(TranslatableValue $nameTranslatableValue $description): void
  271.     {
  272.         $this->name $name;
  273.         $this->description $description;
  274.     }
  275.     public function setLocation(City $city$stations, ?MapCoordinate $mapCoordinate): void
  276.     {
  277.         if (!$this->isDraft() && !$this->city->equals($city)) {
  278.             throw new \DomainException('City change for a saved profile is forbidden.');
  279.         }
  280.         $this->city $city;
  281.         $this->changeStations($stations);
  282.         $this->normalizePrimaryStation();
  283.         $this->mapCoordinate $mapCoordinate;
  284.     }
  285.     protected function changeStations($stations)
  286.     {
  287.         if (null === $stations)
  288.             return;
  289.         if (false === is_array($stations) && false === is_iterable($stations))
  290.             throw new \InvalidArgumentException('Stations list should be either an array or an ArrayCollection');
  291.         $stationsArray is_iterable($stations) && !is_array($stations) ? iterator_to_array($stations) : $stations;
  292.         $stations = [];
  293.         foreach ($stationsArray as $station) {
  294.             $stations[$station->getId()] = $station;
  295.         }
  296.         $stationIds array_map(function (Station $station): int {
  297.             return $station->getId();
  298.         }, $stations);
  299.         $existingStationIds $this->stations->map(function (Station $station): int {
  300.             return $station->getId();
  301.         })->getValues();
  302.         $stationIdsToAdd array_diff($stationIds$existingStationIds);
  303.         $stationIdsToRemove array_diff($existingStationIds$stationIds);
  304.         foreach ($stationIdsToAdd as $stationId) {
  305.             $this->stations->add($stations[$stationId]);
  306.         }
  307.         foreach ($stationIdsToRemove as $stationId) {
  308.             $this->stations->remove($stationId);
  309.         }
  310.     }
  311.     public function normalizePrimaryStation(): void
  312.     {
  313.         if ($this->stations->isEmpty()) {
  314.             $this->primaryStation null;
  315.             return;
  316.         }
  317.         if ($this->primaryStation === null || !$this->stations->contains($this->primaryStation)) {
  318.             $this->primaryStation $this->stations->first();
  319.         }
  320.     }
  321.     public function setEnabledProvidedServices($services): void
  322.     {
  323.         if (null !== $services) {
  324.             if (is_array($services)) {
  325.                 $services = new ArrayCollection($services);
  326.             } elseif (!$services instanceof ArrayCollection) {
  327.                 if (is_iterable($services)) {
  328.                     $services = new ArrayCollection(iterator_to_array($services));
  329.                 } else {
  330.                     throw new \InvalidArgumentException('Services list should be either an array or an ArrayCollection');
  331.                 }
  332.             }
  333.             $this->providedServices $services;
  334.         }
  335.     }
  336.     public function setPhoneCallOptions(string $phoneNumber, ?PhoneCallRestrictions $restrictions, ?Messengers $messengers): void
  337.     {
  338.         $this->phoneNumber $phoneNumber;
  339.         $this->phoneCallRestrictions $restrictions;
  340.         $this->messengers $messengers;
  341.     }
  342.     public function setPricing(?ApartmentsPricing $apartmentsPricing, ?TakeOutPricing $takeOutPricing, ?int $extraCharge, ?ExpressPricing $expressPricing null, ?CarPricing $carPricing null): void
  343.     {
  344.         $this->apartmentsPricing $apartmentsPricing;
  345.         $this->takeOutPricing $takeOutPricing;
  346.         $this->extraCharge $extraCharge;
  347.         $this->expressPricing $expressPricing;
  348.         $this->carPricing $carPricing;
  349.     }
  350.     public function setPrepaymentOptions(?bool $prepayment, ?int $prepaymentAmount, ?string $prepaymentComment): void
  351.     {
  352.         $this->prepayment $prepayment;
  353.         if ($prepayment !== true) {
  354.             $this->prepaymentAmount null;
  355.             $this->prepaymentComment null;
  356.             return;
  357.         }
  358.         $this->prepaymentAmount $prepaymentAmount;
  359.         $this->prepaymentComment $prepaymentComment;
  360.     }
  361.     public function isApproved(): bool
  362.     {
  363.         return $this->approved;
  364.     }
  365.     public function approve(): void
  366.     {
  367.         $this->approved true;
  368.     }
  369.     public function unApprove(): void
  370.     {
  371.         $this->approved false;
  372.     }
  373.     public function getOwner(): ?Advertiser
  374.     {
  375.         return $this->owner;
  376.     }
  377.     public function setOwner(Advertiser $owner): void
  378.     {
  379.         $this->owner $owner;
  380.     }
  381.     public function hasOwner(): bool
  382.     {
  383.         return null !== $this->owner;
  384.     }
  385.     public function getTopPlacements(): Collection
  386.     {
  387.         return $this->topPlacements;
  388.     }
  389.     public function addTopPlacement(TopPlacement $topPlacement): void
  390.     {
  391.         $this->topPlacements->add($topPlacement);
  392.     }
  393.     public function getAdBoardPlacement(): ?AdBoardPlacement
  394.     {
  395.         return $this->adBoardPlacement;
  396.     }
  397.     public function setAdBoardPlacement(AdBoardPlacement $adBoardPlacement): void
  398.     {
  399.         $this->adBoardPlacement $adBoardPlacement;
  400.     }
  401.     /**
  402.      * Анкета оплачена и выводится в общих списках на сайте
  403.      * или в ТОПе, то есть "АКТИВНА"
  404.      */
  405.     public function isActive(): bool
  406.     {
  407.         return null !== $this->adBoardPlacement || $this->hasRunningTopPlacement();
  408.     }
  409.     public function hasRunningTopPlacement(): bool
  410.     {
  411.         $now = new \DateTimeImmutable('now');
  412.         foreach ($this->topPlacements as /** @var TopPlacement $topPlacement */ $topPlacement) {
  413.             if ($topPlacement->getPlacedAt() <= $now && $now <= $topPlacement->getExpiresAt())
  414.                 return true;
  415.         }
  416.         return false;
  417.     }
  418.     public function getUriIdentity(): string
  419.     {
  420.         return $this->uriIdentity;
  421.     }
  422.     public function getName(): TranslatableValue
  423.     {
  424.         return $this->name;
  425.     }
  426.     public function getDescription(): ?TranslatableValue
  427.     {
  428.         return $this->description;
  429.     }
  430.     public function getPersonParameters(): PersonParameters
  431.     {
  432.         return $this->personParameters;
  433.     }
  434.     public function setPersonParameters(PersonParameters $personParameters): void
  435.     {
  436.         $this->personParameters $personParameters;
  437.     }
  438.     public function getPhoneNumber(): string
  439.     {
  440.         return $this->phoneNumber;
  441.     }
  442.     //TODO return type
  443.     public function getPhoneCallRestrictions(): ?PhoneCallRestrictions
  444.     {
  445.         return $this->phoneCallRestrictions;
  446.     }
  447.     public function isMasseur(): bool
  448.     {
  449.         return $this->masseur;
  450.     }
  451.     //TODO return type
  452.     public function getClientRestrictions(): ?ClientRestrictions
  453.     {
  454.         return $this->clientRestrictions;
  455.     }
  456.     //TODO return type
  457.     public function setClientRestrictions(?ClientRestrictions $restrictions): void
  458.     {
  459.         $this->clientRestrictions $restrictions;
  460.     }
  461.     //TODO return type
  462.     public function getApartmentsPricing(): ?ApartmentsPricing
  463.     {
  464.         return $this->apartmentsPricing;
  465.     }
  466.     public function getTakeOutPricing(): ?TakeOutPricing
  467.     {
  468.         return $this->takeOutPricing;
  469.     }
  470.     public function getExtraCharge(): ?int
  471.     {
  472.         return $this->extraCharge;
  473.     }
  474.     public function isPrepayment(): ?bool
  475.     {
  476.         return $this->prepayment;
  477.     }
  478.     public function isWithoutPrepayment(): bool
  479.     {
  480.         return $this->prepayment === false;
  481.     }
  482.     public function getPrepaymentAmount(): ?int
  483.     {
  484.         return $this->prepaymentAmount;
  485.     }
  486.     public function getPrepaymentComment(): ?string
  487.     {
  488.         return $this->prepaymentComment;
  489.     }
  490.     public function addPhoto(string $pathbool $isMain): Photo
  491.     {
  492.         $photos $this->getPhotos();
  493.         $found $photos->filter(function (Photo $photo) use ($path): bool {
  494.             return $path === $photo->getPath();
  495.         });
  496.         if (!$found->isEmpty())
  497.             return $found->first();
  498.         if (true === $isMain) {
  499.             $photos->forAll(function ($indexPhoto $photo): true {
  500.                 $photo->unsetMain();
  501.                 return true;
  502.             });
  503.         }
  504.         $photo = new Photo($this$path$isMain);
  505.         $this->photos->add($photo);
  506.         return $photo;
  507.     }
  508.     /**
  509.      * @return Photo[]
  510.      */
  511.     public function getPhotos(): Collection
  512.     {
  513.         return $this->photos->filter(function ($mediaFile): bool {
  514.             return get_class($mediaFile) == Photo::class;
  515.         });
  516.     }
  517.     public function removePhoto(string $path): bool
  518.     {
  519.         foreach ($this->getPhotos() as $photo) {
  520.             if ($path === $photo->getPath()) {
  521.                 $this->photos->removeElement($photo);
  522.                 return true;
  523.             }
  524.         }
  525.         return false;
  526.     }
  527.     public function getMainPhotoOrFirstPhoto(): ?Photo
  528.     {
  529.         $photos $this->getPhotos();
  530.         if ($photos->isEmpty()) {
  531.             return null;
  532.         }
  533.         $mainPhoto $this->getMainPhoto();
  534.         if (null === $mainPhoto) {
  535.             $mainPhoto $photos->first();
  536.         }
  537.         return $mainPhoto;
  538.     }
  539.     public function getMainPhoto(): ?Photo
  540.     {
  541.         $photos $this->getPhotos();
  542.         if ($photos->isEmpty()) {
  543.             return null;
  544.         }
  545.         $mainPhoto null;
  546.         $photos->forAll(function ($indexPhoto $photo) use (&$mainPhoto): bool {
  547.             if ($photo->isMain()) {
  548.                 $mainPhoto $photo;
  549.                 return false// Stop the cycle
  550.             }
  551.             return true;
  552.         });
  553.         return $mainPhoto;
  554.     }
  555.     public function changeMainPhoto(string $path): void
  556.     {
  557.         $photos $this->getPhotos();
  558.         $found $photos->filter(function (Photo $photo) use ($path): bool {
  559.             return $path === $photo->getPath();
  560.         });
  561.         if ($found->isEmpty()) {
  562.             return;
  563.         }
  564.         $mainPhoto $found->first();
  565.         $photos->forAll(function ($indexPhoto $photo): true {
  566.             $photo->unsetMain();
  567.             return true;
  568.         });
  569.         $mainPhoto->setMain();
  570.     }
  571.     public function addSelfie(string $path): Selfie
  572.     {
  573.         $found $this->getSelfies()->filter(function (Selfie $selfie) use ($path): bool {
  574.             return $path === $selfie->getPath();
  575.         });
  576.         if (!$found->isEmpty())
  577.             return $found->first();
  578.         $selfie = new Selfie($this$path);
  579.         $this->selfies->add($selfie);
  580.         return $selfie;
  581.     }
  582.     /**
  583.      * @return Selfie[]
  584.      */
  585.     public function getSelfies(): Collection
  586.     {
  587.         return $this->selfies;
  588.     }
  589.     public function removeSelfie(string $path): bool
  590.     {
  591.         foreach ($this->getSelfies() as $selfie) {
  592.             if ($path === $selfie->getPath()) {
  593.                 $this->selfies->removeElement($selfie);
  594.                 return true;
  595.             }
  596.         }
  597.         return false;
  598.     }
  599.     public function getConfirmedVideos(): Collection
  600.     {
  601.         return $this->videos->filter(function ($mediaFile): bool {
  602.             if (!$mediaFile instanceof Video) {
  603.                 return false;
  604.             }
  605.             return $mediaFile->isConfirmed();
  606.         });
  607.     }
  608.     /**
  609.      * Храним только 1 видео для анкеты
  610.      */
  611.     public function addVideo(string $videoPath, ?string $posterPath null): Video
  612.     {
  613.         $found $this->getVideos()->filter(function (Video $video) use ($videoPath): bool {
  614.             return $videoPath === $video->getPath();
  615.         });
  616.         if (!$found->isEmpty())
  617.             return $found->first();
  618.         $video = new Video($this$videoPath);
  619.         if (null !== $posterPath) {
  620.             $video->setPreviewPath($posterPath);
  621.         }
  622.         //теперь разрешаем много видео
  623.         //$this->videos->clear();
  624.         $this->videos->add($video);
  625.         return $video;
  626.     }
  627.     /**
  628.      * @return Video[]
  629.      */
  630.     public function getVideos(): Collection
  631.     {
  632.         return $this->videos->filter(function ($mediaFile): bool {
  633.             return ($mediaFile instanceof Video);
  634.         });
  635.     }
  636.     public function removeVideo(string $path): bool
  637.     {
  638.         foreach ($this->getVideos() as $video) {
  639.             if ($path === $video->getPath()) {
  640.                 $this->videos->removeElement($video);
  641.                 $this->photos->removeElement($video);
  642.                 return true;
  643.             }
  644.         }
  645.         return false;
  646.     }
  647.     /**
  648.      * Добавляет таск на обработку оригинала видео в подходящий формат
  649.      *
  650.      * @param string $path Путь к файлу оригинала относительно фс очередей
  651.      */
  652.     public function addRawVideo(string $path): FileProcessingTask
  653.     {
  654.         $file = new FileProcessingTask($this$path);
  655.         $this->processingFiles->add($file);
  656.         return $file;
  657.     }
  658.     public function hasFilesInProcess(): bool
  659.     {
  660.         return $this->videosInProcess() > 0;
  661.     }
  662.     public function videosInProcess(): int
  663.     {
  664.         $inProcess $this->processingFiles->filter(function (FileProcessingTask $task): bool {
  665.             return !$task->isCompleted();
  666.         });
  667.         return $inProcess->count();
  668.     }
  669.     public function isMediaProcessed(): bool
  670.     {
  671.         foreach ($this->videos as $video)
  672.             if (null === $video->getPreviewPath())
  673.                 return false;
  674.         return true;
  675.     }
  676.     public function getAvatar(): ?Avatar
  677.     {
  678.         return $this->avatar;
  679.     }
  680.     public function setAvatar(string $path): void
  681.     {
  682.         $this->avatar = new Avatar($this$path);
  683.     }
  684.     public function removeAvatar(): bool
  685.     {
  686.         if (null == $this->avatar)
  687.             return false;
  688.         foreach ($this->photos as $photo) {
  689.             if ($this->avatar->getPath() === $photo->getPath()) {
  690.                 $this->photos->removeElement($photo);
  691.                 break;
  692.             }
  693.         }
  694.         $this->avatar null;
  695.         return true;
  696.     }
  697.     /**
  698.      * @return CommentByCustomer[]
  699.      */
  700.     public function getComments(): Collection
  701.     {
  702.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  703.             return null == $comment->getParent();
  704.         });
  705.     }
  706.     /**
  707.      * @return CommentByCustomer[]
  708.      */
  709.     public function getCommentsOrderedByNotReplied(): array
  710.     {
  711.         $comments $this->comments->filter(function (CommentByCustomer $comment): bool {
  712.             return null == $comment->getParent();
  713.         })->toArray();
  714.         usort($comments, function (CommentByCustomer $commentACommentByCustomer $commentB): int {
  715.             if ((null == $commentA->getLastCommentByAdvertiser() && null == $commentB->getLastCommentByAdvertiser())
  716.                 || (null != $commentA->getLastCommentByAdvertiser() && null != $commentB->getLastCommentByAdvertiser())) {
  717.                 if ($commentA->getCreatedAt() == $commentB->getCreatedAt())
  718.                     return $commentA->getId() > $commentB->getId() ? -1;
  719.                 else
  720.                     return $commentA->getCreatedAt() > $commentB->getCreatedAt() ? -1;
  721.             }
  722.             if (null == $commentA->getLastCommentByAdvertiser() && null != $commentB->getLastCommentByAdvertiser())
  723.                 return -1;
  724.             else
  725.                 return 1;
  726.         });
  727.         return $comments;
  728.     }
  729.     public function getCreatedAt(): ?\DateTimeImmutable
  730.     {
  731.         return $this->createdAt;
  732.     }
  733.     /**
  734.      * @return CommentByCustomer[]
  735.      */
  736.     public function getCommentsWithoutReply(): Collection
  737.     {
  738.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  739.             return null == $comment->getParent() && false == $comment->isCommentedByAdvertiser();
  740.         });
  741.     }
  742.     /**
  743.      * @return CommentByCustomer[]
  744.      */
  745.     public function getCommentsWithReply(): Collection
  746.     {
  747.         return $this->comments->filter(function (CommentByCustomer $comment): bool {
  748.             return null == $comment->getParent() && true == $comment->isCommentedByAdvertiser();
  749.         });
  750.     }
  751.     /**
  752.      * @return CommentByCustomer[]
  753.      */
  754.     public function getNewComments(): Collection
  755.     {
  756.         $weekAgo CarbonImmutable::now()->sub('7 days');
  757.         return $this->comments->filter(function (CommentByCustomer $comment) use ($weekAgo): bool {
  758.             return null == $comment->getParent()
  759.                 && (
  760.                     $comment->getCreatedAt() >= $weekAgo
  761.                     || null == $this->getCommentReply($comment)
  762.                 );
  763.         });
  764.     }
  765.     private function getCommentReply(CommentByCustomer $parent): ?CommentByCustomer
  766.     {
  767.         foreach ($this->comments as $comment)
  768.             if ($comment->getParent() == $parent)
  769.                 return $comment;
  770.         return null;
  771.     }
  772.     public function getOldComments(): Collection
  773.     {
  774.         $weekAgo CarbonImmutable::now()->sub('7 days');
  775.         return $this->comments->filter(function (CommentByCustomer $comment) use ($weekAgo): bool {
  776.             return null == $comment->getParent()
  777.                 && false == (
  778.                     $comment->getCreatedAt() >= $weekAgo
  779.                     || null == $this->getCommentReply($comment)
  780.                 );
  781.         });
  782.     }
  783.     public function getCommentFromUser(Customer $user): ?CommentByCustomer
  784.     {
  785.         foreach ($this->comments as $comment)
  786.             if (null == $comment->getParent() && null != $comment->getUser() && $user->getId() == $comment->getUser()->getId())
  787.                 return $comment;
  788.         return null;
  789.     }
  790.     //TODO return type
  791.     public function getCity(): City
  792.     {
  793.         return $this->city;
  794.     }
  795.     /**
  796.      * @return Station[]
  797.      */
  798.     public function getStations(): Collection
  799.     {
  800.         return $this->stations;
  801.     }
  802.     public function getMapCoordinate(): ?MapCoordinate
  803.     {
  804.         return $this->mapCoordinate;
  805.     }
  806.     public function getUpdatedAt(): ?\DateTimeImmutable
  807.     {
  808.         return $this->updatedAt;
  809.     }
  810.     public function setUpdatedAt(\DateTimeImmutable $updatedAt): void
  811.     {
  812.         $this->updatedAt $updatedAt;
  813.     }
  814.     public function getModerationStatus(): int
  815.     {
  816.         return $this->moderationStatus;
  817.     }
  818.     public function setModerationStatus(int $status): void
  819.     {
  820.         if (self::MODERATION_STATUS_APPROVED === $status) {
  821.             throw new \RuntimeException(sprintf('Use %s::passModeration() method instead', static::class));
  822.         }
  823.         $validStatuses = [self::MODERATION_STATUS_NOT_PASSEDself::MODERATION_STATUS_APPROVEDself::MODERATION_STATUS_WAITINGself::MODERATION_STATUS_REJECTED];
  824.         if (false === array_search($status$validStatuses))
  825.             throw new \LogicException('Trying to set an invalid moderation status');
  826.         $this->moderationStatus $status;
  827.     }
  828.     public function isModerationPassed(): bool
  829.     {
  830.         return $this->moderationStatus == self::MODERATION_STATUS_APPROVED;
  831.     }
  832.     public function isModerationWaiting(): bool
  833.     {
  834.         return $this->moderationStatus == self::MODERATION_STATUS_WAITING;
  835.     }
  836.     public function isModerationRejected(): bool
  837.     {
  838.         return $this->moderationStatus == self::MODERATION_STATUS_REJECTED;
  839.     }
  840.     public function passModeration(?ModerationRequest $moderationRequest null): void
  841.     {
  842.         $this->moderationStatus self::MODERATION_STATUS_APPROVED;
  843.         $func = static function ($kPhoto|Video $file) use ($moderationRequest): bool {
  844.             if (!$file->isConfirmed()) {
  845.                 $file->passModeration($moderationRequest);
  846.             }
  847.             return true;
  848.         };
  849.         $this->videos->forAll($func);
  850.     }
  851.     public function delete(): void
  852.     {
  853.         $this->deletePlacementHiding();
  854.         $this->deleteFromAdBoard();
  855.         $now = new \DateTimeImmutable('now');
  856.         $toDelete = [];
  857.         foreach ($this->topPlacements as $topPlacement) {
  858.             if ($topPlacement->getExpiresAt() > $now)
  859.                 $toDelete[] = $topPlacement;
  860.         }
  861.         foreach ($toDelete as $topPlacement)
  862.             $this->topPlacements->removeElement($topPlacement);
  863.         $this->setDeletedAt(Carbon::now());
  864.     }
  865.     public function deletePlacementHiding(): void
  866.     {
  867.         $this->placementHiding null;
  868.     }
  869.     public function deleteFromAdBoard(): void
  870.     {
  871.         $this->adBoardPlacement null;
  872.     }
  873.     //TODO return type
  874.     public function undoDelete(): void
  875.     {
  876.         $this->setDeletedAt(); // will pass null by default
  877.     }
  878.     //TODO return type
  879.     public function deleteFromTopPlacement(): void
  880.     {
  881.         //здесь нужна логика отмены конретного размещения
  882. //        $this->topPlacement = null;
  883.     }
  884.     public function getExpressPricing(): ?ExpressPricing
  885.     {
  886.         return $this->expressPricing;
  887.     }
  888.     public function getCarPricing(): ?CarPricing
  889.     {
  890.         return $this->carPricing;
  891.     }
  892.     //TODO return type
  893.     /**
  894.      * @return int[]
  895.      */
  896.     public function getClientTypes(): array
  897.     {
  898.         return $this->clientTypes ?? [];
  899.     }
  900.     /**
  901.      * @param int[] $clientTypes
  902.      */
  903.     public function setClientTypes(array $clientTypes): void
  904.     {
  905.         $this->clientTypes $clientTypes;
  906.     }
  907.     public function getMessengers(): ?Messengers
  908.     {
  909.         return $this->messengers;
  910.     }
  911.     public function getInactivatedAt(): ?\DateTimeImmutable
  912.     {
  913.         return $this->inactivatedAt;
  914.     }
  915.     public function setInactive(): void
  916.     {
  917.         $this->inactivatedAt CarbonImmutable::now();
  918.     }
  919.     public function undoInactive(): void
  920.     {
  921.         $this->inactivatedAt null;
  922.     }
  923.     public function isHidden(): bool
  924.     {
  925.         return null !== $this->getPlacementHiding();
  926.     }
  927.     public function getPlacementHiding(): ?PlacementHiding
  928.     {
  929.         return $this->placementHiding;
  930.     }
  931.     public function setPlacementHiding(PlacementHiding $placementHiding): void
  932.     {
  933.         $this->placementHiding $placementHiding;
  934.     }
  935.     public function hasSelfie(): bool
  936.     {
  937.         return $this->selfies->count() > 0;
  938.     }
  939.     public function hasVideo(): bool
  940.     {
  941.         return $this->videos->count() > 0;
  942.     }
  943.     public function isCommented(): bool
  944.     {
  945.         return $this->comments->count() > 0;
  946.     }
  947.     public function adminApprovalPhoto(): ?AdminApprovalPhoto
  948.     {
  949.         return $this->adminApprovalPhoto;
  950.     }
  951.     public function setAdminApprovalPhoto(?string $path): void
  952.     {
  953.         $this->adminApprovalPhoto $path ? new AdminApprovalPhoto($this$path) : null;
  954.     }
  955.     public function seo(): ?array
  956.     {
  957.         return $this->seo;
  958.     }
  959.     public function seoPhoneNumber(): ?string
  960.     {
  961.         return $this->seo['phone'] ?? null;
  962.     }
  963.     public function setSeoPhoneNumber(string $phoneNumber): void
  964.     {
  965.         if (null === $this->seo) {
  966.             $this->seo = [];
  967.         }
  968.         $this->seo['phone'] = $phoneNumber;
  969.     }
  970.     public function getPrimaryStation(): ?Station
  971.     {
  972.         $station $this->primaryStation ?? $this->getStations()->first();
  973.         if (false === $station) {
  974.             return null;
  975.         }
  976.         return $station;
  977.     }
  978.     public function setPrimaryStation(?Station $station): void
  979.     {
  980.         $this->primaryStation $station;
  981.         $this->normalizePrimaryStation();
  982.     }
  983.     public function getStationsSortedByPrimary(): array
  984.     {
  985.         $stations $this->stations->toArray();
  986.         if (!$this->primaryStation) {
  987.             return $stations;
  988.         }
  989.         usort($stations, function (Station $aStation $b) {
  990.             if ($a->getId() === $this->primaryStation->getId()) return -1;
  991.             if ($b->getId() === $this->primaryStation->getId()) return 1;
  992.             return 0;
  993.         });
  994.         return $stations;
  995.     }
  996.     public function getDeleteMode(): int
  997.     {
  998.         return $this->deleteMode;
  999.     }
  1000.     public function setDeleteMode(int $deleteMode): self
  1001.     {
  1002.         $this->deleteMode $deleteMode;
  1003.         return $this;
  1004.     }
  1005.     public function isHardDeleted(): bool
  1006.     {
  1007.         return $this->deleteMode === 2;
  1008.     }
  1009. }