src/Entity/Property.php line 361

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use ApiPlatform\Core\Annotation\ApiFilter;
  4. use ApiPlatform\Core\Annotation\ApiResource;
  5. use App\Fiken\Api\FikenEnv;
  6. use DateTimeImmutable;
  7. use Doctrine\Common\Collections\ArrayCollection;
  8. use Doctrine\Common\Collections\Collection;
  9. use Doctrine\Common\Collections\Criteria;
  10. use Doctrine\Common\Collections\Expr\Expression;
  11. use Doctrine\Common\Collections\Selectable;
  12. use Doctrine\ORM\Mapping as ORM;
  13. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
  14. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  15. use Symfony\Component\Serializer\Annotation\Groups;
  16. use Symfony\Component\Serializer\Annotation\SerializedName;
  17. /**
  18.  * @ApiResource(
  19.  *  attributes={"security"="is_granted('ROLE_USER')"},
  20.  *  itemOperations = {
  21.  *      "get" = {
  22.  *          "normalization_context" = {"groups" = {
  23.  *              "property:read", "property:item:get"
  24.  *          }}
  25.  *      },
  26.  *      "put" = {
  27.  *          "denormalization_context" = {"groups" = {
  28.  *              "property:item:put"
  29.  *          }}
  30.  *      }
  31.  *  },
  32.  *  collectionOperations = {
  33.  *      "get" = {
  34.  *          "normalization_context" = {"groups" = {
  35.  *              "property:read", "property:item:get"
  36.  *          }}
  37.  *      }
  38.  *  }
  39.  * )
  40.  * @ApiFilter(OrderFilter::class, properties={"section": "desc"})
  41.  * @ORM\Entity(repositoryClass="App\Repository\PropertyRepository")
  42.  */
  43. class Property
  44. {
  45.     /**
  46.      * @ORM\Id()
  47.      * @ORM\GeneratedValue()
  48.      * @ORM\Column(type="integer")
  49.      * @Groups({
  50.      *  "property:read", "property:item:get",
  51.      *  "booking:read", "booking:item:get"
  52.      * })
  53.      */
  54.     private $id;
  55.     /**
  56.      * @ORM\Column(type="string", length=255)
  57.      * @Groups({
  58.      *  "property:read", "property:item:get",
  59.      *  "booking:read", "booking:item:get"
  60.      * })
  61.      */
  62.     private $section;
  63.     /**
  64.      * @ORM\Column(type="json")
  65.      * @Groups({
  66.      *  "property:read", "property:item:get",
  67.      *  "booking:read", "booking:item:get"
  68.      * })
  69.      */
  70.     private $address = [];
  71.     /**
  72.      * @ORM\Column(type="text")
  73.      * @Groups({"property:read", "property:item:get"})
  74.      * @Groups({
  75.      *  "property:read", "property:item:get",
  76.      *  "booking:read", "booking:item:get"
  77.      * })
  78.      */
  79.     private $entryDescription;
  80.     /**
  81.      * @ORM\Column(type="time")
  82.      * @Groups({
  83.      *  "property:read", "property:item:get",
  84.      *  "booking:read", "booking:item:get"
  85.      * })
  86.      */
  87.     private $checkinTime;
  88.     /**
  89.      * @ORM\Column(type="time")
  90.      * @Groups({
  91.      *  "property:read", "property:item:get",
  92.      *  "booking:read", "booking:item:get"
  93.      * })
  94.      */
  95.     private $checkoutTime;
  96.     /**
  97.      * @ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="property", cascade={"persist"})
  98.      */
  99.     private $bookings;
  100.     /**
  101.      * @ORM\Column(type="json")
  102.      * @Groups({
  103.      *  "property:read", "property:item:get",
  104.      * })
  105.      */
  106.     private $cal = [];
  107.     /**
  108.      * @ORM\Column(type="json", nullable=true)
  109.      */
  110.     private $notes = [];
  111.     /**
  112.      * @ORM\Column(type="boolean")
  113.      */
  114.     private $disabled false;
  115.     /**
  116.      * @ORM\ManyToOne(targetEntity=User::class)
  117.      */
  118.     private $responsible;
  119.     /**
  120.      * @ORM\Column(type="dateinterval", nullable=true)
  121.      */
  122.     private $cleaning_duration;
  123.     /**
  124.      * @ORM\Column(type="integer", nullable=true)
  125.      */
  126.     private $guests;
  127.     /**
  128.      * @ORM\Column(type="integer")
  129.      */
  130.     private $double_bedrooms;
  131.     /**
  132.      * @ORM\Column(type="integer")
  133.      */
  134.     private $single_rooms;
  135.     /**
  136.      * @ORM\Column(type="dateinterval")
  137.      */
  138.     private $driving_duration;
  139.     /**
  140.      * @ORM\Column(type="datetime_immutable")
  141.      */
  142.     private $created_at;
  143.     /**
  144.      * @ORM\OneToMany(targetEntity=Invoice::class, mappedBy="property", orphanRemoval=true)
  145.      */
  146.     private $invoices;
  147.     /**
  148.      * @ORM\Column(type="float")
  149.      */
  150.     private $base_fee_cleaning;
  151.     /**
  152.      * @ORM\Column(type="boolean")
  153.      */
  154.     private $vat_registered;
  155.     /**
  156.      * @ORM\Column(type="smallint")
  157.      */
  158.     private $host_share;
  159.     /**
  160.      * @ORM\ManyToOne(targetEntity=PropertyOwner::class, inversedBy="properties")
  161.      */
  162.     private $owner;
  163.     /**
  164.      * @ORM\Column(type="bigint", nullable=true)
  165.      */
  166.     private $projectId;
  167.     /**
  168.      * @ORM\Column(type="integer")
  169.      */
  170.     private $importAttempt;
  171.     /**
  172.      * @ORM\ManyToMany(targetEntity=MqttDoorLock::class, inversedBy="properties")
  173.      */
  174.     private $doorLocks;
  175.     /**
  176.      * @ORM\OneToMany(targetEntity=User::class, mappedBy="propertyUser")
  177.      */
  178.     private $users;
  179.     /**
  180.      * @ORM\OneToMany(targetEntity=BookingProvider::class, mappedBy="property", cascade={"persist"})
  181.      */
  182.     private $bookingProviders;
  183.     /**
  184.      * @ORM\OneToOne(targetEntity=PropertyAddress::class, mappedBy="property", cascade={"persist", "remove"})
  185.      */
  186.     private $propertyAddress;
  187.     public function __construct() {
  188.         $this->bookings = new ArrayCollection();
  189.         $this->invoices = new ArrayCollection();
  190.         $this->doorLocks = new ArrayCollection();
  191.         $this->users = new ArrayCollection();
  192.         $this->bookingProviders = new ArrayCollection();
  193.     }
  194.     public function getId(): ?int {
  195.         return $this->id;
  196.     }
  197.     public function getAddress(): ?array {
  198.         return $this->address;
  199.     }
  200.     public function getStreetAddr(): ?string {
  201.         $addr $this->getAddress() ? $this->getAddress()['address'] : "";
  202.         $addr explode(','$addr);
  203.         return trim($addr[0], ',');
  204.     }
  205.     public function getPostalCode(): ?string {
  206.         $addr $this->getAddress() ? $this->getAddress()['address'] : "";
  207.         $addr explode(','$addr);
  208.         if (sizeof($addr) == 1)
  209.             return '9000';
  210.         return trim(explode(' 'trim($addr[1]))[0], ',');
  211.     }
  212.     public function getPostalCity(): ?string {
  213.         $addr $this->getAddress() ? $this->getAddress()['address'] : "";
  214.         $addr explode(','$addr);
  215.         //dump($this, $addr);
  216.         if (sizeof($addr) == 1)
  217.             return 'Tromsø';
  218.         return trim(explode(" "$addr[sizeof($addr)-1])[1]);
  219.     }
  220.     public function setAddress(array $address): self {
  221.         $this->address $address;
  222.         return $this;
  223.     }
  224.     public function getEntryDescription(): ?string {
  225.         return $this->entryDescription;
  226.     }
  227.     public function setEntryDescription(string $entryDescription): self {
  228.         $this->entryDescription $entryDescription;
  229.         return $this;
  230.     }
  231.     
  232.     private static function diffTimeOfDay(\DateTimeInterface $now): ?\DateInterval {
  233.         return static::setTimeToMidnight($now)?->diff($now);
  234.     }
  235.     private static function setTimeToDiff(?\DateTimeInterface $dateTime, ?\DateInterval $diff): ?\DateTimeImmutable {
  236.         return $diff && $dateTime ? static::setTimeToMidnight($dateTime)?->add($diff) : $diff;
  237.     }
  238.     private static function setTimeToMidnight(?\DateTimeInterface $dateTime): ?\DateTimeImmutable {
  239.         return $dateTime \DateTimeImmutable::createFromInterface($dateTime)->setTime(...Booking::START_OF_DAY) : null;
  240.     }
  241.     public function getCheckinTime(): ?\DateTimeInterface {
  242.         return $this->checkinTime;
  243.     }
  244.     public function getTodaysCheckinTime(?\DateTimeInterface $dateTime null): ?\DateTimeImmutable {
  245.         return static::setTimeToDiff($dateTime ??= $this->checkinTime, static::diffTimeOfDay($this->checkinTime));
  246.     }
  247.     public function setCheckinTime(\DateTimeImmutable $checkinTime): self {
  248.         $this->checkinTime $checkinTime;
  249.         return $this;
  250.     }
  251.     public function getCheckoutTime(): ?\DateTimeInterface {
  252.         return $this->checkoutTime;
  253.     }
  254.     public function getTodaysCheckoutTime(?\DateTimeInterface $dateTime null): ?\DateTimeImmutable {
  255.         return static::setTimeToDiff($dateTime ??= $this->checkoutTime, static::diffTimeOfDay($this->checkoutTime));
  256.     }
  257.     public function setCheckoutTime(\DateTimeImmutable $checkoutTime): self {
  258.         $this->checkoutTime $checkoutTime;
  259.         return $this;
  260.     }
  261.     /**
  262.      * @return Collection|Booking[]
  263.      */
  264.     public function getBookings(null|string|Expression ...$criterias): Collection {
  265.         return $this->selectBookings()->matching(self::getBookingsCriteria(...$criterias));
  266.     }
  267.     public function getBookingsCriteria(null|string|Expression ...$criterias): Criteria {
  268.         $criteria Criteria::create();
  269.         $expressions array_filter($criterias, fn(null|string|Expression $x): bool => $x instanceof Expression);
  270.         $dates array_filter($criterias, fn(null|string|Expression $x): bool => is_string($x));
  271.         if (($dn sizeof($dates)) > 2)
  272.             throw new \InvalidArgumentException(sprintf("To many string types for 'getBookings', max: 2, got: %d"$dn));
  273.         $types = ['checkin''checkout'];
  274.         $dates sizeof($dates array_map(fn($t): Expression => sizeof($res array_map(
  275.             fn ($d$id): Expression => Criteria::expr()->{($id 2) ? 'lte' 'gte'}($t
  276.                 (new \DateTimeImmutable($d))?->setTime(
  277.                     ...($id Booking::END_OF_DAY Booking::START_OF_DAY)
  278.                 )),
  279.                 $datesarray_keys($dates)
  280.             )) > Criteria::expr()->andX(...$res) : $res[0] ?? null,
  281.             array_splice($typessizeof($types) - sizeof($dates), sizeof($dates)), 
  282.         )) > Criteria::expr()->orX(...$dates) : $dates[0] ?? null;
  283.         $where array_filter([$dates, ...$expressions], fn($a) => $a != null);
  284.         if ($where)
  285.             $criteria->andWhere(Criteria::expr()->andX(...$where));
  286.         return $criteria->orderBy(['checkin' => 'ASC']);
  287.     }
  288.     public function getConflictingBookings(string $fromFormat null): Collection {
  289.         return $this->getBookings($fromFormat
  290.             Criteria::expr()->neq('blocking'1),
  291.             Criteria::expr()->isNull('cancelledAt')
  292.         )->filter(
  293.             fn(Booking $b): bool => !$b->getConflictingBookings()->isEmpty()
  294.         );
  295.     }
  296.     public function getUncleanBookings(?string $dateFormat nullExpression ...$criterias): Collection {
  297.         return $this->getBookings(
  298.             Criteria::expr()->neq('blocking'1), Booking::cleanCriteria(false),
  299.             Criteria::expr()->lte('checkout', (new \DateTimeImmutable($dateFormat))->setTime(...Booking::END_OF_DAY)), ...$criterias
  300.         );
  301.     }
  302.     public function isClean(?string $dateFormat null): bool {
  303.         return $this->getUncleanBookings($dateFormat)->isEmpty();
  304.     }
  305.     public function selectBookings(): Selectable {
  306.         return $this->bookings;
  307.     }
  308.     public function addBooking(Booking $booking): self {
  309.         if (!$this->bookings->contains($booking)) {
  310.             $this->bookings[] = $booking;
  311.             $booking->setProperty($this);
  312.         }
  313.         return $this;
  314.     }
  315.     public function removeBooking(Booking $booking): self {
  316.         if ($this->bookings->contains($booking)) {
  317.             $this->bookings->removeElement($booking);
  318.             // set the owning side to null (unless already changed)
  319.             if ($booking->getProperty() === $this) {
  320.                 $booking->setProperty(null);
  321.             }
  322.         }
  323.         return $this;
  324.     }
  325.     public function getBookingProviderUrls(): ?array {
  326.         return $this->getBookingProviders()->map(fn(BookingProvider $bp): string => $bp->getCalendarUrl())->toArray();
  327.     }
  328.     public function getCal(): ?array {
  329.         return $this->getBookingProviderUrls();
  330.     }
  331.     public function setCal(array $cal): self {
  332.         $this->cal $cal;
  333.         return $this;
  334.     }
  335.     public function getSection(): ?string {
  336.         return $this->section// . ($this->disabled ? " (!! Contract ended, SKIP !!) " : "");
  337.     }
  338.     public function isDisabled(): bool {
  339.         return $this->disabled === true;
  340.     }
  341.     public function setSection(string $Section): self {
  342.         $this->section $Section;
  343.         return $this;
  344.     }
  345.     /**
  346.      * @Groups({
  347.      *  "booking:read", "booking:item:get", "booking:item:put",
  348.      *  "property:read", "property:item:get"
  349.      * })
  350.      */
  351.     public function getNotes(): ?array {
  352.         return $this->notes;
  353.     }
  354.     /**
  355.      * @Groups({"booking:item:put"})
  356.      */
  357.     public function setNote(string $text): self {
  358.         $match =  [];
  359.         preg_match('/^((?:[^\/]*\/)*[\d]+):(.+)$/'$text$match);
  360.         if (count($match) == 3) {
  361.             $this->notes[$match[1]] = $match[2];
  362.             try {
  363.                 $this->getBookings(
  364.                     Criteria::expr()->eq('id'intval(substr($match[1], strrpos($match[1], '/')+1)))
  365.                 )->first()?->setEmployeeNote($match[2]);
  366.             } catch(\Exception $e) {
  367.                 // It didn't work obviusly
  368.             }
  369.         }
  370.         return $this;
  371.     }
  372.     /**
  373.      * @Groups({"booking:item:put"})
  374.      */
  375.     public function setNotes(array $array): self {
  376.         $this->notes sizeof($array) == null $array;
  377.         return $this;
  378.     }
  379.     public function getResponsible(): ?User {
  380.         return $this->responsible;
  381.     }
  382.     public function getStartCleaningBy(): ?\DateTimeImmutable {
  383.         return (
  384.             fn(\DateTimeImmutable $d): \DateTimeImmutable => $d->sub($this->getCleaningDuration())
  385.         )($this->getTodaysCheckinTime());
  386.     }
  387.     public function getCleaningDuration(): ?\DateInterval {
  388.         return $this->cleaning_duration ?? (new \DateInterval('PT0H'));
  389.     }
  390.     public function setCleaningDuration(?\DateInterval $cleaning_duration): self {
  391.         $this->cleaning_duration $cleaning_duration;
  392.         return $this;
  393.     }
  394.     /**
  395.      * @SerializedName("cleaningDuration")
  396.      * @Groups({
  397.      *  "property:read", "property:item:get",
  398.      *  "booking:read", "booking:item:get"
  399.      * })
  400.      */
  401.     public function getCleaningDurationMilliseconds(): ?int {
  402.         if (!$this->getCleaningDuration())
  403.             return null;
  404.         return self::durationToMilliseconds($this->getCleaningDuration());
  405.     }
  406.     public static function durationToMilliseconds(?\DateInterval $di): int {
  407.         if ($di == null)
  408.             return 0;
  409.         $ms 1000;
  410.         return $di->* (60 60 24 $ms) +
  411.             $di->* (60 60 $ms) +
  412.             $di->* (60 $ms) +
  413.             $di->$ms;
  414.     }
  415.     /**
  416.      * @Groups({
  417.      *  "booking:read", "booking:item:get"
  418.      * })
  419.      * */
  420.     public function getNumBedrooms(): ?int {
  421.         return $this->getDoubleBedrooms() + $this->getSingleRooms();
  422.     }
  423.     /**
  424.      * @SerializedName("maxGuests")
  425.      * @Groups({"booking:read", "booking:item:get"})
  426.      */
  427.     public function getGuests(): int {
  428.         return $this->guests === null $this->getDoubleBedrooms() * $this->getSingleRooms() : $this->guests;
  429.     }
  430.     // public function setGuests(int $guests): self
  431.     // {
  432.     //     $this->guests = $guests;
  433.     //     return $this;
  434.     // }
  435.     public function getDoubleBedrooms(): int {
  436.         return $this->double_bedrooms;
  437.     }
  438.     public function setDoubleBedrooms(int $double_bedrooms): self {
  439.         $this->double_bedrooms $double_bedrooms;
  440.         return $this;
  441.     }
  442.     public function getExtraBeds(): int {
  443.         return $this->getGuests() - ($this->getDoubleBedrooms() * $this->getSingleRooms());
  444.     }
  445.     public function getSingleRooms(): ?int {
  446.         return $this->single_rooms;
  447.     }
  448.     public function setSingleRooms(int $single_rooms): self {
  449.         $this->single_rooms $single_rooms;
  450.         return $this;
  451.     }
  452.     /**
  453.      * @SerializedName("drivingDuration")
  454.      * @Groups({
  455.      *  "property:read", "property:item:get",
  456.      *  "booking:read", "booking:item:get"
  457.      * })
  458.      */
  459.     public function getDrivingDurationMilliseconds(): ?int {
  460.         return self::durationToMilliseconds($this->getDrivingDuration());
  461.     }
  462.     public function getDrivingDuration(): ?\DateInterval {
  463.         return $this->driving_duration;
  464.     }
  465.     public function setDrivingDuration(\DateInterval $driving_duration): self {
  466.         $this->driving_duration $driving_duration;
  467.         return $this;
  468.     }
  469.     public function getCreatedAt(): ?\DateTimeImmutable {
  470.         return $this->created_at;
  471.     }
  472.     public function setCreatedAt(\DateTimeImmutable $created_at): self {
  473.         $this->created_at $created_at;
  474.         return $this;
  475.     }
  476.     /**
  477.      * @Groups({
  478.      *  "property:read", "property:item:get"
  479.      * })
  480.      **/
  481.     public function hasInvoicableBookings(): bool {
  482.         return false;
  483.     }
  484.     /**
  485.      * @return Collection|Invoice[]
  486.      */
  487.     public function getInvoices(): Collection {
  488.         return $this->invoices;
  489.     }
  490.     public function addInvoice(Invoice $invoice): self {
  491.         if (!$this->invoices->contains($invoice)) {
  492.             $this->invoices[] = $invoice;
  493.             $invoice->setProperty($this);
  494.         }
  495.         return $this;
  496.     }
  497.     public function removeInvoice(Invoice $invoice): self {
  498.         if ($this->invoices->removeElement($invoice)) {
  499.             // set the owning side to null (unless already changed)
  500.             if ($invoice->getProperty() === $this) {
  501.                 $invoice->setProperty(null);
  502.             }
  503.         }
  504.         return $this;
  505.     }
  506.     /**
  507.      * @Groups({
  508.      *  "booking:read"
  509.      * })
  510.      **/
  511.     public function getBaseFeeCleaning(): ?float {
  512.         return $this->base_fee_cleaning;
  513.     }
  514.     public function setBaseFeeCleaning(float $base_fee_cleaning): self {
  515.         $this->base_fee_cleaning $base_fee_cleaning;
  516.         return $this;
  517.     }
  518.     /**
  519.      * @Groups({
  520.      *  "booking:read"
  521.      * })
  522.      **/
  523.     public function getVatRegistered(): ?bool {
  524.         return $this->vat_registered;
  525.     }
  526.     public function setVatRegistered(bool $vat_registered): self {
  527.         $this->vat_registered $vat_registered;
  528.         return $this;
  529.     }
  530.     /**
  531.      * @Groups({
  532.      *  "booking:read"
  533.      * })
  534.      */
  535.     public function getHostShare(): ?int {
  536.         return $this->host_share;
  537.     }
  538.     public function setHostShare(int $host_share): self {
  539.         $this->host_share $host_share;
  540.         return $this;
  541.     }
  542.     public function getOwner(): ?PropertyOwner {
  543.         return $this->owner;
  544.     }
  545.     public function setOwner(?PropertyOwner $owner): self {
  546.         $this->owner $owner;
  547.         return $this;
  548.     }
  549.     public function getProjectId(FikenEnv $fikenEnv FikenEnv::DEBUG): ?int {
  550.         return $fikenEnv == FikenEnv::PROD $this->projectId $this->getOwner()->getTestProjectId();
  551.     }
  552.     public function setProjectId(int $projectId): self {
  553.         $this->projectId $projectId;
  554.         return $this;
  555.     }
  556.     /**
  557.      * @Groups({
  558.      *  "booking:read"
  559.      * })
  560.      */
  561.     public function isInvoiceReady(): ?bool {
  562.         return $this->owner != null && $this->projectId != null;
  563.     }
  564.     public function getImportAttempt(): ?int {
  565.         return $this->importAttempt;
  566.     }
  567.     public function setImportAttempt(int $importAttempt): self {
  568.         $this->importAttempt $importAttempt;
  569.         return $this;
  570.     }
  571.     /**
  572.      * @return Collection<int, MqttDoorLock>
  573.      * @SerializedName("doorLocks")
  574.      * @Groups({
  575.      *  "property:read", "property:item:get",
  576.      *  "booking:read", "booking:item:get"
  577.      * })
  578.      */
  579.     public function getDoorLocks(): Collection {
  580.         return $this->doorLocks;
  581.     }
  582.     public function addDoorLock(MqttDoorLock $doorLock): self {
  583.         if (!$this->doorLocks->contains($doorLock)) {
  584.             $this->doorLocks[] = $doorLock;
  585.         }
  586.         return $this;
  587.     }
  588.     public function removeDoorLock(MqttDoorLock $doorLock): self {
  589.         $this->doorLocks->removeElement($doorLock);
  590.         return $this;
  591.     }
  592.     /**
  593.      * @return Collection<int, User>
  594.      */
  595.     public function getUsers(): Collection {
  596.         return $this->users;
  597.     }
  598.     public function addUser(User $user): self {
  599.         if (!$this->users->contains($user)) {
  600.             $this->users[] = $user;
  601.             $user->setPropertyUser($this);
  602.         }
  603.         return $this;
  604.     }
  605.     public function removeUser(User $user): self {
  606.         if ($this->users->removeElement($user)) {
  607.             // set the owning side to null (unless already changed)
  608.             if ($user->getPropertyUser() === $this) {
  609.                 $user->setPropertyUser(null);
  610.             }
  611.         }
  612.         return $this;
  613.     }
  614.     /**
  615.      * @return Collection<int, BookingProvider>
  616.      */
  617.     public function getBookingProviders(): Collection {
  618.         return $this->bookingProviders;
  619.     }
  620.     public function addBookingProvider(BookingProvider $bookingProvider): self {
  621.         if (!$this->bookingProviders->contains($bookingProvider)) {
  622.             $this->bookingProviders[] = $bookingProvider;
  623.             $bookingProvider->setProperty($this);
  624.         }
  625.         return $this;
  626.     }
  627.     public function removeBookingProvider(BookingProvider $bookingProvider): self {
  628.         if ($this->bookingProviders->removeElement($bookingProvider)) {
  629.             // set the owning side to null (unless already changed)
  630.             if ($bookingProvider->getProperty() === $this) {
  631.                 $bookingProvider->setProperty(null);
  632.             }
  633.         }
  634.         return $this;
  635.     }
  636.     public function getPropertyAddress(): ?PropertyAddress {
  637.         return $this->propertyAddress;
  638.     }
  639.     public function setPropertyAddress(PropertyAddress $propertyAddress): self {
  640.         $this->propertyAddress $propertyAddress;
  641.         return $this;
  642.     }
  643. }