src/Entity/Profile/Profile.php line 77

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