From 320f2f30afbfef69f73ff4346ddf2a9e488062da Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 8 Nov 2025 18:56:59 +0100 Subject: [PATCH] =?UTF-8?q?Immobilien=20&=20User=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/packages/api_platform.yaml | 2 + migrations/Version20251108172941.php | 31 +++ migrations/Version20251108175151.php | 42 ++++ src/Controller/ImmobilieController.php | 37 +++ src/Entity/Immobilie.php | 325 +++++++++++++++++++++++++ src/Entity/User.php | 30 +++ src/Enum/ImmobilienTyp.php | 23 ++ src/Repository/ImmobilieRepository.php | 137 +++++++++++ templates/immobilie/index.html.twig | 153 ++++++++++++ templates/immobilie/show.html.twig | 211 ++++++++++++++++ templates/immobilie/suche.html.twig | 103 ++++++++ 11 files changed, 1094 insertions(+) create mode 100644 migrations/Version20251108172941.php create mode 100644 migrations/Version20251108175151.php create mode 100644 src/Controller/ImmobilieController.php create mode 100644 src/Entity/Immobilie.php create mode 100644 src/Enum/ImmobilienTyp.php create mode 100644 src/Repository/ImmobilieRepository.php create mode 100644 templates/immobilie/index.html.twig create mode 100644 templates/immobilie/show.html.twig create mode 100644 templates/immobilie/suche.html.twig diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 02f295a..fb487fa 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -1,6 +1,8 @@ api_platform: title: Hello API Platform version: 1.0.0 + mapping: + paths: ['%kernel.project_dir%/src/Entity'] defaults: stateless: true cache_headers: diff --git a/migrations/Version20251108172941.php b/migrations/Version20251108172941.php new file mode 100644 index 0000000..e31d82a --- /dev/null +++ b/migrations/Version20251108172941.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE immobilien (id INT AUTO_INCREMENT NOT NULL, adresse VARCHAR(255) NOT NULL, preis NUMERIC(10, 2) NOT NULL, flaeche NUMERIC(8, 2) NOT NULL, garage TINYINT(1) NOT NULL, zimmer INT NOT NULL, baujahr INT DEFAULT NULL, typ VARCHAR(255) NOT NULL, beschreibung LONGTEXT DEFAULT NULL, verfuegbar TINYINT(1) NOT NULL, balkon_flaeche INT DEFAULT NULL, keller_flaeche INT DEFAULT NULL, etage INT DEFAULT NULL, heizungstyp VARCHAR(100) DEFAULT NULL, nebenkosten NUMERIC(6, 2) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE immobilien'); + } +} diff --git a/migrations/Version20251108175151.php b/migrations/Version20251108175151.php new file mode 100644 index 0000000..c73fa09 --- /dev/null +++ b/migrations/Version20251108175151.php @@ -0,0 +1,42 @@ +addSql('ALTER TABLE immobilien ADD verwalter_id INT DEFAULT NULL'); + + // Assign all existing immobilien to the first user + $this->addSql('UPDATE immobilien SET verwalter_id = (SELECT id FROM users ORDER BY id LIMIT 1)'); + + // Now make it NOT NULL and add constraints + $this->addSql('ALTER TABLE immobilien MODIFY verwalter_id INT NOT NULL'); + $this->addSql('ALTER TABLE immobilien ADD CONSTRAINT FK_2C789D3E5F66D3 FOREIGN KEY (verwalter_id) REFERENCES users (id)'); + $this->addSql('CREATE INDEX IDX_2C789D3E5F66D3 ON immobilien (verwalter_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE immobilien DROP FOREIGN KEY FK_2C789D3E5F66D3'); + $this->addSql('DROP INDEX IDX_2C789D3E5F66D3 ON immobilien'); + $this->addSql('ALTER TABLE immobilien DROP verwalter_id'); + } +} diff --git a/src/Controller/ImmobilieController.php b/src/Controller/ImmobilieController.php new file mode 100644 index 0000000..0cb8587 --- /dev/null +++ b/src/Controller/ImmobilieController.php @@ -0,0 +1,37 @@ +findVerfuegbare(); + + return $this->render('immobilie/index.html.twig', [ + 'immobilien' => $immobilien, + ]); + } + + #[Route('/{id}', name: 'app_immobilie_show', requirements: ['id' => '\d+'])] + public function show(Immobilie $immobilie): Response + { + return $this->render('immobilie/show.html.twig', [ + 'immobilie' => $immobilie, + ]); + } + + #[Route('/suche', name: 'app_immobilie_suche')] + public function suche(ImmobilieRepository $repository): Response + { + return $this->render('immobilie/suche.html.twig'); + } +} diff --git a/src/Entity/Immobilie.php b/src/Entity/Immobilie.php new file mode 100644 index 0000000..2d9a6ab --- /dev/null +++ b/src/Entity/Immobilie.php @@ -0,0 +1,325 @@ +createdAt = new \DateTime(); + $this->updatedAt = new \DateTime(); + $this->typ = ImmobilienTyp::WOHNUNG; + } + + #[ORM\PreUpdate] + public function setUpdatedAtValue(): void + { + $this->updatedAt = new \DateTime(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getAdresse(): string + { + return $this->adresse; + } + + public function setAdresse(string $adresse): self + { + $this->adresse = $adresse; + return $this; + } + + public function getPreis(): float + { + return $this->preis; + } + + public function setPreis(float $preis): self + { + $this->preis = $preis; + return $this; + } + + public function getFlaeche(): float + { + return $this->flaeche; + } + + public function setFlaeche(float $flaeche): self + { + $this->flaeche = $flaeche; + return $this; + } + + public function getGarage(): bool + { + return $this->garage; + } + + public function isGarage(): bool + { + return $this->garage; + } + + public function setGarage(bool $garage): self + { + $this->garage = $garage; + return $this; + } + + public function getZimmer(): int + { + return $this->zimmer; + } + + public function setZimmer(int $zimmer): self + { + $this->zimmer = $zimmer; + return $this; + } + + public function getBaujahr(): ?int + { + return $this->baujahr; + } + + public function setBaujahr(?int $baujahr): self + { + $this->baujahr = $baujahr; + return $this; + } + + public function getTyp(): ImmobilienTyp + { + return $this->typ; + } + + public function setTyp(ImmobilienTyp $typ): self + { + $this->typ = $typ; + return $this; + } + + public function getBeschreibung(): ?string + { + return $this->beschreibung; + } + + public function setBeschreibung(?string $beschreibung): self + { + $this->beschreibung = $beschreibung; + return $this; + } + + public function isVerfuegbar(): bool + { + return $this->verfuegbar; + } + + public function setVerfuegbar(bool $verfuegbar): self + { + $this->verfuegbar = $verfuegbar; + return $this; + } + + public function getBalkonFlaeche(): ?int + { + return $this->balkonFlaeche; + } + + public function setBalkonFlaeche(?int $balkonFlaeche): self + { + $this->balkonFlaeche = $balkonFlaeche; + return $this; + } + + public function getKellerFlaeche(): ?int + { + return $this->kellerFlaeche; + } + + public function setKellerFlaeche(?int $kellerFlaeche): self + { + $this->kellerFlaeche = $kellerFlaeche; + return $this; + } + + public function getEtage(): ?int + { + return $this->etage; + } + + public function setEtage(?int $etage): self + { + $this->etage = $etage; + return $this; + } + + public function getHeizungstyp(): ?string + { + return $this->heizungstyp; + } + + public function setHeizungstyp(?string $heizungstyp): self + { + $this->heizungstyp = $heizungstyp; + return $this; + } + + public function getNebenkosten(): ?float + { + return $this->nebenkosten; + } + + public function setNebenkosten(?float $nebenkosten): self + { + $this->nebenkosten = $nebenkosten; + return $this; + } + + public function getCreatedAt(): \DateTimeInterface + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeInterface $createdAt): self + { + $this->createdAt = $createdAt; + return $this; + } + + public function getUpdatedAt(): \DateTimeInterface + { + return $this->updatedAt; + } + + public function setUpdatedAt(\DateTimeInterface $updatedAt): self + { + $this->updatedAt = $updatedAt; + return $this; + } + + public function getVerwalter(): User + { + return $this->verwalter; + } + + public function setVerwalter(User $verwalter): self + { + $this->verwalter = $verwalter; + return $this; + } + + /** + * Berechnet den Preis pro Quadratmeter + */ + public function getPreisProQm(): float + { + if ($this->flaeche > 0) { + return round($this->preis / $this->flaeche, 2); + } + return 0; + } + + /** + * Berechnet die Gesamtfläche inkl. Balkon und Keller + */ + public function getGesamtflaeche(): float + { + $gesamt = $this->flaeche; + if ($this->balkonFlaeche) { + $gesamt += $this->balkonFlaeche; + } + if ($this->kellerFlaeche) { + $gesamt += $this->kellerFlaeche; + } + return $gesamt; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 06e4fe0..aa9123d 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -10,6 +10,8 @@ use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\Delete; use App\Enum\UserRole; use App\Repository\UserRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; @@ -47,10 +49,14 @@ class User #[ORM\Column(type: 'datetime')] private \DateTimeInterface $createdAt; + #[ORM\OneToMany(targetEntity: Immobilie::class, mappedBy: 'verwalter', orphanRemoval: true)] + private Collection $immobilien; + public function __construct() { $this->createdAt = new \DateTime(); $this->role = UserRole::USER; + $this->immobilien = new ArrayCollection(); } public function getId(): ?int @@ -101,4 +107,28 @@ class User $this->createdAt = $createdAt; return $this; } + + /** + * @return Collection + */ + public function getImmobilien(): Collection + { + return $this->immobilien; + } + + public function addImmobilie(Immobilie $immobilie): self + { + if (!$this->immobilien->contains($immobilie)) { + $this->immobilien->add($immobilie); + $immobilie->setVerwalter($this); + } + + return $this; + } + + public function removeImmobilie(Immobilie $immobilie): self + { + $this->immobilien->removeElement($immobilie); + return $this; + } } diff --git a/src/Enum/ImmobilienTyp.php b/src/Enum/ImmobilienTyp.php new file mode 100644 index 0000000..f7bc683 --- /dev/null +++ b/src/Enum/ImmobilienTyp.php @@ -0,0 +1,23 @@ + 'Wohnung', + self::HAUS => 'Haus', + self::GRUNDSTUECK => 'Grundstück', + self::GEWERBE => 'Gewerbe', + self::BUERO => 'Büro', + }; + } +} diff --git a/src/Repository/ImmobilieRepository.php b/src/Repository/ImmobilieRepository.php new file mode 100644 index 0000000..ab8d6eb --- /dev/null +++ b/src/Repository/ImmobilieRepository.php @@ -0,0 +1,137 @@ + + */ +class ImmobilieRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Immobilie::class); + } + + /** + * Find available properties + */ + public function findVerfuegbare(): array + { + return $this->createQueryBuilder('i') + ->andWhere('i.verfuegbar = :verfuegbar') + ->setParameter('verfuegbar', true) + ->orderBy('i.createdAt', 'DESC') + ->getQuery() + ->getResult(); + } + + /** + * Find properties by type + */ + public function findByTyp(ImmobilienTyp $typ): array + { + return $this->createQueryBuilder('i') + ->andWhere('i.typ = :typ') + ->setParameter('typ', $typ) + ->orderBy('i.preis', 'ASC') + ->getQuery() + ->getResult(); + } + + /** + * Find properties within price range + */ + public function findByPreisRange(float $minPreis, float $maxPreis): array + { + return $this->createQueryBuilder('i') + ->andWhere('i.preis BETWEEN :minPreis AND :maxPreis') + ->andWhere('i.verfuegbar = :verfuegbar') + ->setParameter('minPreis', $minPreis) + ->setParameter('maxPreis', $maxPreis) + ->setParameter('verfuegbar', true) + ->orderBy('i.preis', 'ASC') + ->getQuery() + ->getResult(); + } + + /** + * Find properties within area range + */ + public function findByFlaecheRange(float $minFlaeche, float $maxFlaeche): array + { + return $this->createQueryBuilder('i') + ->andWhere('i.flaeche BETWEEN :minFlaeche AND :maxFlaeche') + ->andWhere('i.verfuegbar = :verfuegbar') + ->setParameter('minFlaeche', $minFlaeche) + ->setParameter('maxFlaeche', $maxFlaeche) + ->setParameter('verfuegbar', true) + ->orderBy('i.flaeche', 'ASC') + ->getQuery() + ->getResult(); + } + + /** + * Find properties with garage + */ + public function findMitGarage(): array + { + return $this->createQueryBuilder('i') + ->andWhere('i.garage = :garage') + ->andWhere('i.verfuegbar = :verfuegbar') + ->setParameter('garage', true) + ->setParameter('verfuegbar', true) + ->orderBy('i.preis', 'ASC') + ->getQuery() + ->getResult(); + } + + /** + * Find properties by minimum number of rooms + */ + public function findByMinZimmer(int $minZimmer): array + { + return $this->createQueryBuilder('i') + ->andWhere('i.zimmer >= :minZimmer') + ->andWhere('i.verfuegbar = :verfuegbar') + ->setParameter('minZimmer', $minZimmer) + ->setParameter('verfuegbar', true) + ->orderBy('i.zimmer', 'ASC') + ->getQuery() + ->getResult(); + } + + /** + * Get average price per sqm by type + */ + public function getAveragePreisProQmByTyp(ImmobilienTyp $typ): float + { + $result = $this->createQueryBuilder('i') + ->select('AVG(i.preis / i.flaeche) as avgPreisProQm') + ->andWhere('i.typ = :typ') + ->andWhere('i.verfuegbar = :verfuegbar') + ->setParameter('typ', $typ) + ->setParameter('verfuegbar', true) + ->getQuery() + ->getSingleScalarResult(); + + return round((float) $result, 2); + } + + /** + * Search properties by address + */ + public function searchByAdresse(string $search): array + { + return $this->createQueryBuilder('i') + ->andWhere('i.adresse LIKE :search') + ->setParameter('search', '%' . $search . '%') + ->orderBy('i.createdAt', 'DESC') + ->getQuery() + ->getResult(); + } +} diff --git a/templates/immobilie/index.html.twig b/templates/immobilie/index.html.twig new file mode 100644 index 0000000..04292c5 --- /dev/null +++ b/templates/immobilie/index.html.twig @@ -0,0 +1,153 @@ +{% extends 'base.html.twig' %} + +{% block title %}Immobilien - {{ parent() }}{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} + + + {% if immobilien|length > 0 %} +
+ {% for immobilie in immobilien %} +
+
+
{{ immobilie.typ.label }}
+ {{ immobilie.adresse }} +
+
+
{{ immobilie.preis|number_format(2, ',', '.') }} €
+ +
    +
  • 📐 Fläche: {{ immobilie.flaeche }} m²
  • +
  • 🛏️ Zimmer: {{ immobilie.zimmer }}
  • +
  • 💰 Preis/m²: {{ immobilie.preisProQm|number_format(2, ',', '.') }} €
  • + {% if immobilie.baujahr %} +
  • 📅 Baujahr: {{ immobilie.baujahr }}
  • + {% endif %} + {% if immobilie.garage %} +
  • 🚗 Mit Garage
  • + {% endif %} +
+ + + Details ansehen + +
+
+ {% endfor %} +
+ {% else %} +
+

Keine Immobilien verfügbar

+

Aktuell sind keine Immobilien in unserem System verfügbar.

+
+ {% endif %} +{% endblock %} diff --git a/templates/immobilie/show.html.twig b/templates/immobilie/show.html.twig new file mode 100644 index 0000000..829c004 --- /dev/null +++ b/templates/immobilie/show.html.twig @@ -0,0 +1,211 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ immobilie.adresse }} - {{ parent() }}{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} + ← Zurück zur Übersicht + +
+
+ {{ immobilie.typ.label }} +

{{ immobilie.adresse }}

+ + {{ immobilie.verfuegbar ? 'Verfügbar' : 'Nicht verfügbar' }} + +
+ +
+
{{ immobilie.preis|number_format(2, ',', '.') }} €
+
{{ immobilie.preisProQm|number_format(2, ',', '.') }} € pro m²
+
+ +

Hauptmerkmale

+
+
+
Wohnfläche
+
{{ immobilie.flaeche }} m²
+
+
+
Zimmer
+
{{ immobilie.zimmer }}
+
+ {% if immobilie.baujahr %} +
+
Baujahr
+
{{ immobilie.baujahr }}
+
+ {% endif %} + {% if immobilie.etage is not null %} +
+
Etage
+
{{ immobilie.etage }}
+
+ {% endif %} +
+ +

Ausstattung & Extras

+
    +
  • + 🚗 + Garage: {{ immobilie.garage ? 'Ja' : 'Nein' }} +
  • + {% if immobilie.balkonFlaeche %} +
  • + 🌿 + Balkon: {{ immobilie.balkonFlaeche }} m² +
  • + {% endif %} + {% if immobilie.kellerFlaeche %} +
  • + 📦 + Keller: {{ immobilie.kellerFlaeche }} m² +
  • + {% endif %} + {% if immobilie.heizungstyp %} +
  • + 🔥 + Heizung: {{ immobilie.heizungstyp }} +
  • + {% endif %} + {% if immobilie.nebenkosten %} +
  • + 💶 + Nebenkosten: {{ immobilie.nebenkosten|number_format(2, ',', '.') }} € / Monat +
  • + {% endif %} +
  • + 📊 + Gesamtfläche: {{ immobilie.gesamtflaeche }} m² +
  • +
+ + {% if immobilie.beschreibung %} +

Beschreibung

+
+ {{ immobilie.beschreibung|nl2br }} +
+ {% endif %} + +
+ Erstellt am: {{ immobilie.createdAt|date('d.m.Y H:i') }} Uhr
+ Zuletzt aktualisiert: {{ immobilie.updatedAt|date('d.m.Y H:i') }} Uhr +
+
+{% endblock %} diff --git a/templates/immobilie/suche.html.twig b/templates/immobilie/suche.html.twig new file mode 100644 index 0000000..95e5b2d --- /dev/null +++ b/templates/immobilie/suche.html.twig @@ -0,0 +1,103 @@ +{% extends 'base.html.twig' %} + +{% block title %}Immobiliensuche - {{ parent() }}{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} + ← Zurück zur Übersicht + + +{% endblock %}