10-04-2026

This commit is contained in:
Kevin Adametz 2026-04-10 17:18:17 +02:00
parent 4d6b4930b2
commit 4bb89aad8c
836 changed files with 52961 additions and 5950 deletions

View file

@ -1,9 +1,9 @@
# Entwicklungsplan: B2In / Local for Local Marktplatz-Ökosystem
# Entwicklungsplan: B2in / Local for Local Marktplatz-Ökosystem
**Erstellt:** 12.02.2026
**Letzte Aktualisierung:** 12.02.2026
**Basis:** konzeption.md (Version 1.1)
**Status:** Phase 1 ✅, Phase 2 ✅, Phase 2.5 Produkt-Bearbeitung ✅, Phase 2.6 Refactoring & UX ✅, Phase 2.7 Admin-Produktverwaltung & Freigabe ✅, Phase 3 Kern ✅
**Status:** Phase 1 ✅, Phase 2 ✅, Phase 2.5 Produkt-Bearbeitung ✅, Phase 2.6 Refactoring & UX ✅, Phase 2.7 Admin-Produktverwaltung & Freigabe ✅, Phase 3 Kern ✅, Phase 2.8 Partner-Präsentation ✅
**Docker** Projekt läuft in Docker, root /var/www/html nutze php artisan ... nicht vendor/bin/sail artisan ...
---
@ -581,6 +581,31 @@ Alle Preistypen erlaubt; Standard-Varianten mit Festpreis möglich.
- [ ] Notification an Händler/Hersteller bei Freigabe/Ablehnung ausstehend
- [ ] Admin kann `product_type` bei Bedarf nachträglich ändern ausstehend
#### 2.4a Nachbesserungen Phase 2 ✅ Abgeschlossen (27.02.2026)
**Fix: `curate products` Permission fehlte in der Datenbank**
- Die Permission war im `RoleSeeder` definiert, aber nie in die Produktions-DB eingespielt
- `Attempt to read property "id" on null` beim Öffnen von `/admin/products`
- Lösung: Migration `2026_02_27_154145_add_curate_products_permission.php`
- `Permission::firstOrCreate(['name' => 'curate products', 'guard_name' => 'web'])`
- Admin-Rolle bekommt Permission via `Role::where('name', 'Admin')->first()` (kein `findByName` = würde Exception werfen wenn Rolle fehlt)
- Tests (`ProductCurationTest`, `ProductPolicyTest`, `PartnerPolicyTest`): `Permission::create()``Permission::firstOrCreate()` (verhindert Fehler wenn Migration die Permission bereits angelegt hat)
**Fix: Admin kann Produkte anlegen (Partner-Selector)**
- Admin hat keine eigene `partner_id``$user->partner` war `null` → Crash beim Speichern
- Lösung in `form-teaser.blade.php` und `form-standard.blade.php`:
- Neues Property `$selectedPartnerId` + `resolvePartner()` Methode
- Für Admins: Partner-Auswahl-Karte erscheint vor dem Formular (nur bei Neuanlage)
- `updatedSelectedPartnerId()` aktualisiert Produktnummer automatisch nach Partnerwahl
- Validierung: `selectedPartnerId` Pflichtfeld für Admins bei Neuanlage
- Alle bestehenden 158 Produkt-Tests weiterhin grün ✅
**Fix: Portal Textarea Dark-Mode Kontrast**
- Korrektur-Textfeld in der Kuration zeigte weißen Text auf weißem Hintergrund
- Ursache: `@apply dark:text-zinc-500` innerhalb von `::placeholder` Pseudo-Elementen kompiliert in Tailwind v4 zu ungültigem `:is():where(.dark,.dark *)` Selektor
- Lösung in `resources/css/portal.css`: Explizites CSS mit `:where(.dark,.dark *)` als Vorfahren-Selektor statt `@apply dark:`
- Portal-Build (`npm run build:portal`) aktualisiert
#### 2.5 Tests Phase 2 ✅ Kern abgeschlossen
- [x] Feature-Tests: `PartnerPolicyTest` (10 Tests) viewAny, view, update, curateProducts
- [x] Feature-Tests: `ProductPolicyTest` (14 Tests) alle Policy-Methoden
@ -596,6 +621,59 @@ Alle Preistypen erlaubt; Standard-Varianten mit Festpreis möglich.
---
### Phase 2.8: Partner-Präsentation & Selbst-Service-Profil ✅ ABGESCHLOSSEN (27.02.2026)
**Geschätzter Aufwand:** 2-3 Tage | **Tatsächlich:** 1 Sitzung
#### 2.8.1 Händler-Profil (Retailer) Präsentation
- [x] `my-data.blade.php` erweitert um:
- [x] **Story-Text** Freitext max. 2000 Zeichen mit Live-Zeichenzähler, Feld: `story_text`
- [x] **Öffnungszeiten** 7-Tage-Eingabe (MoSo), `open`/`close`-Felder + "Geschlossen"-Checkbox, Feld: `opening_hours` (JSON)
- [x] **Spezialisierungen** kommagetrennte Eingabe → gespeichert als JSON-Array, Feld: `specialties`
- [x] **Gründungsjahr** Zahlfeld mit Validierung (1800heute), Feld: `founded_year`
- [x] **Team-Fotos** Mehrfach-Upload via `WithFileUploads`, gespeichert als `Media.type = 'team_photo'`
- [x] **Showroom-Galerie** Mehrfach-Upload, `Media.type = 'showroom'`
- [x] Foto-Löschen per Klick (mit `wire:confirm`)
- [x] Formular in 4 separate `flux:card`-Abschnitte gegliedert: Stammdaten, Präsentation & Story, Öffnungszeiten, Fotos
#### 2.8.2 Hersteller-Profil (Manufacturer) Firma & Marke
- [x] `my-data.blade.php` erweitert um:
- [x] **Story-Text** Unternehmensgeschichte, Feld: `story_text`
- [x] **Gründungsjahr** Feld: `founded_year`
- [x] **Spezialisierungen** Feld: `specialties`
- [x] **Marken-Bilder** Mehrfach-Upload, `Media.type = 'brand_image'`
- [x] Abschnitte: Stammdaten, Über das Unternehmen, Marke (mit Markenname, Beschreibung, Bilder)
#### 2.8.3 Öffentliches Partnerprofil (`livewire/partner/profile.blade.php`)
- [x] Story-Text anzeigen wenn vorhanden (bereits vorhanden)
- [x] Öffnungszeiten anzeigen (bereits vorhanden)
- [x] Spezialisierungen + Gründungsjahr anzeigen (bereits vorhanden)
- [x] **Showroom-Galerie** 3-spaltige Bild-Galerie unterhalb der Story
- [x] **Marken-Bilder** (Hersteller) 3-spaltige Galerie unter der Story
- [x] **Team-Fotos** 2-spaltige Galerie in der rechten Sidebar
#### 2.8.4 Datenbank
- [x] Alle Felder vorhanden: `story_text`, `opening_hours`, `specialties`, `founded_year` (Migration `2026_02_12_000003_add_profile_fields_to_partners_table`)
- [x] Media-System: Custom `Media` Model mit `type`-Feld (kein Spatie) `Partner::media()` morph-Relation bereits vorhanden
#### 2.8.5 Tests Phase 2.8
- [x] `PartnerSelfServiceProfileTest` (15 Tests) alle bestanden ✅
- Händler kann Story, Öffnungszeiten, Spezialisierungen, Gründungsjahr speichern
- Händler kann Team-Fotos und Showroom-Fotos hochladen
- Händler kann Foto löschen
- Hersteller kann Story, Markendaten, Gründungsjahr speichern
- Hersteller kann Marken-Bilder hochladen
- Öffentliches Profil zeigt Story und Spezialisierungen an
- Validierung: Story-Text max. 2000 Zeichen, Gründungsjahr-Grenzen
**Erstellte/geänderte Dateien (2):**
| Datei | Änderungen |
|-------|-----------|
| `resources/views/livewire/partner/my-data.blade.php` | Komplett überarbeitet: 4 Karten-Abschnitte, neue Profil-Felder, Foto-Upload mit `WithFileUploads` |
| `resources/views/livewire/partner/profile.blade.php` | Showroom-Galerie, Marken-Bilder, Team-Fotos hinzugefügt; `media` eager-loaded |
---
### Phase 3: Kunden-Frontend & Local Feed ✅ KERN ABGESCHLOSSEN (12.02.2026)
**Geschätzter Aufwand:** 4-5 Tage | **Tatsächlich:** 1 Tag (12.02.2026)
**Priorität:** HOCH Kunden-Facing Funktionalität
@ -721,7 +799,7 @@ Enum: App\Enums\TransactionStatus
- [ ] `PendingMerchant` Beleg hochgeladen, Händler muss bestätigen
- [ ] `Confirmed` Händler hat bestätigt → Rechnung an Händler generieren
- [ ] `Invoiced` Rechnung erstellt → Zahlung ausstehend
- [ ] `Paid` Händler hat Provision an B2In überwiesen
- [ ] `Paid` Händler hat Provision an B2in überwiesen
- [ ] `Distributed` Provisionen an Makler/Kunde ausgeschüttet
- [ ] `Rejected` Händler hat abgelehnt
- [ ] `Disputed` Streitfall
@ -778,7 +856,7 @@ Migration: create_ledger_entries_table
#### 6.2 Provisions-Berechnung
- [ ] Service: `App\Services\CommissionService`
- `calculateSplit(Transaction $transaction): CommissionSplit`
- Berechnet: Makler-Anteil, Kunden-Cashback, B2In-Marge
- Berechnet: Makler-Anteil, Kunden-Cashback, B2in-Marge
- Nutzt `partner.provision_rate_percentage` und `partner.provision_fixed_amount`
- [ ] Event Listener für `TransactionPaid`:
- Erstellt Ledger-Einträge
@ -856,7 +934,7 @@ Migration: create_ledger_entries_table
> **Entscheidung:** Individuell pro Partner. Admin-Settings-Seite mit Feldern für:
> - Makler-Provision (%)
> - Kunden-Cashback (%)
> - B2In-Marge (Rest)
> - B2in-Marge (Rest)
> Felder werden pro Partner im Admin-Backend konfiguriert.
**Frage 8: Ticket-Gültigkeit** ✅

View file

@ -2,9 +2,9 @@ Konzept-Update: "Local for Local" Marktplatz
1. Strategische Neuausrichtung: Die Domains
Die Trennung wird schärfer.
B2In (Backend/B2B): Bleibt das "Maschinenraum"-Portal für Händler, Hersteller und Makler. Hier wird verwaltet.
B2in (Backend/B2B): Bleibt das "Maschinenraum"-Portal für Händler, Hersteller und Makler. Hier wird verwaltet.
Local for Local (Customer Frontend): Das wird das Gesicht zum Kunden. Der Kunde fühlt sich nicht auf einer abstrakten "B2In"-Seite, sondern in seinem regionalen Hub (z.B. "Local for Local OWL").
Local for Local (Customer Frontend): Das wird das Gesicht zum Kunden. Der Kunde fühlt sich nicht auf einer abstrakten "B2in"-Seite, sondern in seinem regionalen Hub (z.B. "Local for Local OWL").
To-Do: Wir benötigen ggf. die Domain localforlocal.de (o.ä.) und routen diese auf die Endkunden-Ansicht.
@ -83,11 +83,11 @@ Anstatt eines klassischen "Warenkorbs" bauen wir für Typ A Produkte einen "Tick
Das ist technisch einfacher als ein Checkout mit Payment-Provider! Wir generieren ein PDF/QR-Code und senden eine Mail.
Konzeptpapier: B2In / Local for Local Marktplatz-Ökosystem
Konzeptpapier: B2in / Local for Local Marktplatz-Ökosystem
Status: Final | Version: 1.1 (Update: Marken-Hierarchie)
1. Executive Summary
Das B2In-Ökosystem ist ein hybrider Marktplatz, der den Immobilienkauf ("Moment of Need") mit der Einrichtung verbindet. Es agiert als "Closed Shop" (Zugang nur über Makler). B2In ist die zentrale B2B-Plattform und Technologie. Local for Local ist das verbindende Prinzip innerhalb des Marktplatzes, das die Endkunden-Marken (style2own, stileigentum) mit den regionalen Händlern verknüpft.
Das B2in-Ökosystem ist ein hybrider Marktplatz, der den Immobilienkauf ("Moment of Need") mit der Einrichtung verbindet. Es agiert als "Closed Shop" (Zugang nur über Makler). B2in ist die zentrale B2B-Plattform und Technologie. Local for Local ist das verbindende Prinzip innerhalb des Marktplatzes, das die Endkunden-Marken (style2own, stileigentum) mit den regionalen Händlern verknüpft.
Der USP liegt in der Transparenz lokaler Verfügbarkeit (Säule A: Local Express) und exklusiven Insider-Konditionen (Säule B: Smart Club), abgesichert durch ein Cashback-System.
@ -95,7 +95,7 @@ Der USP liegt in der Transparenz lokaler Verfügbarkeit (Säule A: Local Express
Wir unterscheiden strikt zwischen dem B2B-Zugang (Partner) und den B2C-Einstiegen (Endkunden).
A. Der B2B-Kanal (Die Dachmarke)
Marke: B2In
Marke: B2in
Zielgruppe: Immobilienmakler, Händler, Hersteller.
@ -163,9 +163,9 @@ Ticket: Kunde zieht im Portal einen QR-Code für Händler X.
Kauf: Kunde kauft vor Ort, verhandelt Preise individuell.
Upload (Der Trigger): Kunde lädt Kaufbeleg im B2In-Portal hoch, um sein Cashback anzufordern.
Upload (Der Trigger): Kunde lädt Kaufbeleg im B2in-Portal hoch, um sein Cashback anzufordern.
Clearing: Händler bestätigt Umsatz im Backend -> Händler zahlt Gesamt-Provision an B2In.
Clearing: Händler bestätigt Umsatz im Backend -> Händler zahlt Gesamt-Provision an B2in.
Ausschüttung: Sobald Geld eingeht, verteilt das System automatisch:
@ -173,7 +173,7 @@ Provision an Makler (Lead-Vergütung).
Cashback an Kunden (Motivation & Datentreue).
Marge an B2In.
Marge an B2in.
@ -240,7 +240,7 @@ pending_merchant: Händler muss bestätigen ("Ja, Kunde war da und hat für 3.00
confirmed: Händler hat bestätigt -> Rechnung an Händler wird generiert.
paid: Händler hat Provision an B2In überwiesen.
paid: Händler hat Provision an B2in überwiesen.
distributed: Provision wurde an Makler/Kunde ausgeschüttet.
@ -264,7 +264,7 @@ Händler Setup-Buchung:
Ein kleiner "Service-Store" im Händler-Backend.
Button: "Setup-Paket buchen (399€)". Löst eine interne Notification an das B2In-Team aus (Ticket für Fotografen).
Button: "Setup-Paket buchen (399€)". Löst eine interne Notification an das B2in-Team aus (Ticket für Fotografen).
5. Frontend-Modul: Die Weichenstellung
Wie die Landingpages mit dem System reden.