59 KiB
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 ✅, Phase 2.8 Partner-Präsentation ✅ Docker Projekt läuft in Docker, root /var/www/html nutze php artisan ... nicht vendor/bin/sail artisan ...
0. Umsetzungsprotokoll
Phase 2.7: Admin-Produktverwaltung, Freigabe-Workflow & Statusaktionen – ABGESCHLOSSEN (13.02.2026)
Vollstaendige Admin-Produktverwaltung mit tabellarischer Uebersicht, Filtern, Freigabe-Workflow mit Ablehnungsgrund, sowie Archivieren/Verkauft-Aktionen fuer Haendler und Admin.
Produkt archivieren / als Verkauft markieren
- Neue
archiveProduct()undmarkAsSold()Methoden inform-standard.blade.php,form-teaser.blade.phpundproducts/index.blade.php - Edit-Formulare: Buttons "Als verkauft markieren" und "Archivieren" links neben Speichern-Button (nur im Edit-Modus)
- Produktliste: Dropdown-Menue (3-Punkte-Menue) je Produkt mit "Als verkauft" und "Archivieren" Optionen
wire:confirmDialoge fuer Sicherheitsabfrage- Activity-Log-Eintraege (action:
archived/sold) werden automatisch erstellt - Produkte im Status Archived/Sold zeigen kein Dropdown mehr
Erstellungsdatum in Produktliste
- Neue Spalte "Erstellt" in
products/index.blade.php(Format: dd.mm.YYYY) - Sowohl fuer Haendler als auch Admin sichtbar
Admin-Produktuebersicht (komplett neu)
admin/products/index.blade.phpkomplett umgebaut: von Card-/Tab-Layout zu tabellarischer Uebersicht- Statistik-Karten: Gesamt, Zur Freigabe, In Korrektur, Freigegeben (klickbar als Schnellfilter)
- Filter: Suche (Name, Artikelnummer), Status (alle ProductStatus-Werte), Produkttyp, Haendler, Kategorie
- Tabelle: Produkt (mit Bild), Haendler, Kategorie, Status, Kuration (Freigabe-Buttons), Erstellt, Aktionen
- Admin kann alle Produkte bearbeiten – Edit-Link fuehrt zum gleichen Formular wie fuer Haendler
- Suche durchsucht auch
b2in_article_numberundpartner_product_number
Freigabe-Workflow mit Ablehnungsgrund
- Freigeben: Direkt-Button in Kuration-Spalte (Pending → Active + is_curated)
- Korrektur: Inline-Formular (orange) mit Pflicht-Textfeld → Status
Correction,curation_notesgespeichert - Ablehnung (NEU): Inline-Formular (rot) mit Pflicht-Textfeld → Status
Archived, Ablehnungsgrund incuration_notesgespeichert - Activity-Log-Eintrag bei allen drei Aktionen (mit
notebei Korrektur/Ablehnung) Flux::toast()Benachrichtigungen statt Flash-Messages
Kuration-Hinweis beim Haendler
- Standard- und Teaser-Edit-Formulare zeigen prominenten Callout wenn
curation_notesvorhanden:- Korrektur (Status
correction): Gelbes Warning-Callout "Korrektur erforderlich" - Ablehnung (Status
archived): Rotes Danger-Callout "Produkt abgelehnt"
- Korrektur (Status
- Angezeigt oberhalb des Formulars, immer sichtbar
Admin Archiv/Verkauft in Admin-Uebersicht
- Dropdown-Menue analog zur Haendler-Produktliste
- Admin kann alle Produkte archivieren oder als verkauft markieren
Erstellte/geaenderte Dateien (5):
| Kategorie | Dateien | Details |
|---|---|---|
| Volt (3) | form-standard.blade.php, form-teaser.blade.php, products/index.blade.php |
Archive/Sold-Methoden, Kuration-Callout, Erstellungsdatum, Dropdown-Aktionen |
| Admin-Volt (1) | admin/products/index.blade.php |
Komplett umgebaut: Tabelle, Filter, Statistiken, Rejection mit Textfeld |
| Tests (1) | ProductCurationTest.php, ProductEditTest.php |
Aktualisiert + erweitert |
Neue/aktualisierte Tests (25):
- ProductCurationTest: 23 Tests (komplett ueberarbeitet fuer neues Tabellen-Layout, Rejection mit Pflicht-Textfeld, Archive/Sold, Filter, Kuration-Notes-Anzeige)
- ProductEditTest: +2 Tests (archiveProduct, markAsSold aus Edit)
Tests gesamt: 194 Produkt-Tests, alle bestanden ✅ (533 Assertions)
Phase 2.6: Refactoring, UX-Verbesserungen & Teaser-Erweiterung – ABGESCHLOSSEN (13.02.2026)
Umfassende Qualitaetsverbesserung der Produkt-Formulare: Code-Redundanzen eliminiert, UX-Workflow optimiert, Bildsortierung implementiert, Teaser-Produkte mit Produktnummern und korrektem Freigabe-Workflow ausgestattet.
Refactoring: Create + Edit zu einem Formular zusammengefuehrt
create.blade.php+edit.blade.php→form-standard.blade.php(Standard-Produkte)create-teaser.blade.php+edit-teaser.blade.php→form-teaser.blade.php(Teaser-Produkte)- Steuerung ueber
$isEditing-Flag inmount():?Product $product = null - Separate
saveNew()undsaveExisting()Methoden - Alte Einzeldateien geloescht
UX: Save-Verhalten bei Bearbeitung
- Bei Edit: Seite bleibt offen statt Redirect zur Produktliste
- Flux Toast-Notification ("Produkt wurde gespeichert" / "zur Freigabe eingereicht")
- Bei Standard-Produkten: aktiver Tab bleibt erhalten (kein Page-Reload)
<flux:toast />global im Sidebar-Layout ergaenzt- Bei Create: weiterhin Redirect zur Produktliste mit Flash-Message
Bildsortierung per Drag & Drop
- Vorhandene Bilder per HTML5 Drag & Drop umsortierbar (Alpine.js, keine externe Dependency)
- Erstes Bild = Standardbild, visuell markiert (blaues "Standard"-Badge + blauer Ring)
updateMediaOrder(array $orderedIds)Methode speichert Reihenfolge inorder_columnexistingMediawird immer nachorder_columnsortiert geladen- Drag-Feedback: halbtransparent, blauer Ring am Ziel, Grab-Cursor, Sortier-Icon bei Hover
Vorschaubild in Produktliste
products/index.blade.php: Erstes Bild (nachorder_column) als 40x40px Thumbnail vor dem Produktnamen- Platzhalter-Icon (Foto-Symbol) wenn kein Bild vorhanden
Teaser-Produkte: Produktnummern & Status-Fix
partnerProductNumberhinzugefuegt: wird bei Create automatisch generiert (z.B. P003-0001), bei Edit pre-filledb2inArticleNumberwird beim Speichern automatisch erzeugt (z.B. B2IN-000001)- Eigene "Produktnummern"-Karte im Formular (B2in-Badge + Partner-Nummer-Feld)
- Bug-Fix: Status "aktiv" im UI setzte Status direkt auf Active statt Pending. Jetzt korrekt: UI "aktiv" → DB
pending(zur Freigabe) - UI-Text vereinheitlicht: immer "Zur Freigabe einreichen" (nicht mehr "Aktiv – direkt veröffentlichen")
- Freigabe-Workflow-Hinweis erscheint bei Create und Edit
Routing-Aenderung:
products/create/standard → products.form-standard (products.create.standard)
products/create/teaser → products.form-teaser (products.create.teaser)
products/{product}/edit-standard → products.form-standard (products.edit.standard)
products/{product}/edit-teaser → products.form-teaser (products.edit.teaser)
Erstellte/geaenderte Dateien (8):
| Kategorie | Dateien | Details |
|---|---|---|
| Volt (2) | form-standard.blade.php, form-teaser.blade.php |
Merged Create+Edit, Toast, Bildsortierung, Produktnummern |
| Views (2) | index.blade.php, sidebar.blade.php |
Vorschaubild, <flux:toast /> |
| Routes (1) | routes/admin.php |
Neue Routennamen (.standard/.teaser) |
| Tests (4) | StandardProductCreateTest, ProductEditTest, TeaserProductCreateTest, TeaserProductEditTest |
Komponentennamen aktualisiert, 13 neue Tests |
Geloeschte Dateien: create.blade.php, edit.blade.php, create-teaser.blade.php, edit-teaser.blade.php
Neue Tests (13): Bildsortierung (5), Teaser-Status pending/draft (2), Teaser B2in-Artikelnummer (1), Teaser Partnernummer create+edit (3), Teaser Partnernummer pre-fill (2)
Tests gesamt: 109 Produkt-Tests, alle bestanden ✅
Phase 2.5: Produkt-Bearbeitung (Standard + Teaser) – ABGESCHLOSSEN (13.02.2026)
Beide Produkttypen koennen jetzt vollstaendig bearbeitet werden. Standard-Produkte (SmartOrder) nutzen das 8-Tab-Formular, Teaser-Produkte (LocalStock) ein vereinfachtes Einseiten-Formular. Die Produkt-Liste leitet automatisch zur richtigen Edit-Seite weiter.
Erstellte/geaenderte Dateien (4):
- Volt: resources/views/livewire/products/edit-teaser.blade.php (neu) - Teaser-Edit mit Pre-Fill, Status-Handling, Media-Verwaltung, Activity-Log
- Routes: routes/admin.php - products.edit.teaser Route hinzugefuegt
- Views: resources/views/livewire/products/index.blade.php - Edit-Link basierend auf product_type
- Tests: tests/Feature/TeaserProductEditTest.php (neu) - 24 Tests
Infrastruktur-Fix: Route-Cache und Test-DB-Migration bereinigt.
Tests gesamt: 24 Tests (TeaserProductEditTest) + 22 Tests (ProductEditTest), alle bestanden. Produkt-bezogene Tests gesamt: 133 Tests, alle bestanden.
Phase 2.3b: CSV-Felder Erweiterung (Moebeldatenliste) – ✅ ABGESCHLOSSEN (13.02.2026)
Änderung: Alle fehlenden Felder aus Moebeldatenliste Stand 4.11.2025.csv (70 Felder, 13 Sektionen) wurden als DB-Spalten und Formularfelder ergänzt. Von ~30 existierenden Feldern auf ~70 erweitert. Neue Tabelle product_wood_origins für EUDR-Compliance. Formular von 5 auf 8 Tabs umgebaut.
Erstellte/geänderte Dateien (13):
| Kategorie | Dateien | Details |
|---|---|---|
| Migrationen (4) | add_csv_fields_to_products_table, add_csv_fields_to_product_logistics_table, add_currency_to_product_variants_table, create_product_wood_origins_table |
+21 Spalten products, +4 Spalten product_logistics, +1 Spalte product_variants, neue Tabelle product_wood_origins |
| Models (4) | Product.php, ProductLogistics.php, ProductVariant.php, ProductWoodOrigin.php (neu) |
Fillable, Casts, woodOrigins() Relationship |
| Factories (1) | ProductWoodOriginFactory.php (neu) |
Factory mit Holzarten, Ländern, EUDR-Daten |
| Volt-Komponenten (1) | products/create.blade.php |
8-Tab-Layout, ~25 neue Properties, Wood-Origins-Repeater, erweiterte Validierung + Save-Logik |
| Tests (2) | StandardProductCreateTest.php (+12 Tests), Models/ProductWoodOriginTest.php (4 Tests, neu) |
Material, Logistik, Services, Nachhaltigkeit, EUDR, Scoring, Währung, Validierung |
Neue DB-Spalten (26): products: country_of_origin, main_material, surface_material, cover_material, color_finish, certificates(JSON), assembly_time_min, load_capacity_kg, delivery_type, assembly_service, service_radius_km, warranty_months, production_time_days, visible_from, visible_until, co2_footprint_kg, recycling_percentage, is_regional_production, storage_volume_liters, assembly_effort_score, design_score. product_logistics: packaging_type, packaging_recyclable_percent, is_palletizable, hs_code. product_variants: currency.
Neue Tabelle: product_wood_origins (1:n von products) – EUDR-Compliance mit Holzart, Herkunftsland, Region, Erntejahr, Forstbetrieb, Zertifikat, EUDR-ID
Pint-Formatierung: ✅ (keine Korrekturen nötig)
Tests gesamt: 33 Tests (StandardProductCreateTest) + 4 Tests (ProductWoodOriginTest), alle bestanden ✅
Phase 2.3: Standard-Produkt Erstellung (Maske 2) – ✅ ABGESCHLOSSEN (13.02.2026)
Änderung: Das Standard-Produkt-Formular (create.blade.php) wurde komplett neu geschrieben – von einer nicht-funktionalen Dummy-Vorlage zu einem voll funktionsfähigen class-based Volt Component mit 5 Tabs.
Erstellte/geänderte Dateien (3):
| Kategorie | Dateien | Details |
|---|---|---|
| Volt-Komponenten (1) | resources/views/livewire/products/create.blade.php |
Komplett neugeschrieben: 5-Tab-Layout (Basis, Bilder, Physisch, Kommerziell, Zuordnung), erstellt Product + ProductVariant + ProductLogistics + Media, inline Validierung, Preise EUR→Cents |
| Migrationen (1) | database/migrations/2026_02_13_..._make_tax_rate_id_nullable_on_product_variants_table.php |
tax_rate_id nullable gemacht (war NOT NULL ohne Default, tax_rates Tabelle leer) |
| Tests (1) | tests/Feature/StandardProductCreateTest.php |
17 neue Tests: Zugriff (3), Happy Path (2), Physisch+Logistik (1), Kommerziell (1), SEO (1), Validierung (8), Preistypen (1) |
Pint-Formatierung: ✅ (1 Fix: unused import in StandardProductCreateTest)
Tests gesamt: 17 Tests (StandardProductCreateTest), alle bestanden ✅
Phase 2 Ergänzung: Beide Rollen → Beide Produkttypen (13.02.2026)
Änderung: Sowohl Händler (Retailer) als auch Hersteller (Manufacturer) können nun BEIDE Produkttypen anlegen (Teaser + Standard). Vorher war die Erstellmaske rollenbasiert auf einen Typ beschränkt.
Erstellte/geänderte Dateien (3):
| Kategorie | Dateien | Details |
|---|---|---|
| Volt-Komponenten (1) | resources/views/livewire/products/index.blade.php |
Zwei Buttons ("Neues Teaser-Produkt" + "Neues Standard-Produkt"), neuer productTypeFilter State + Query-Filter, Produkttyp-Dropdown im Filterbereich |
| Tests (2) | tests/Feature/LocalFeedTest.php, tests/Feature/TeaserProductCreateTest.php |
+6 neue Tests: Produkttyp-Filter (3), Zwei-Button-Anzeige (3), Manufacturer Teaser-Zugriff (1), Manufacturer Teaser-Erstellung (1). Fix: mainImage → mainImages in bestehenden Tests |
Pint-Formatierung: ✅ (keine Korrekturen nötig)
Tests gesamt: 21 Tests (LocalFeedTest + TeaserProductCreateTest), alle bestanden ✅
Phase 3: Kunden-Frontend & Local Feed – ✅ KERN ABGESCHLOSSEN (12.02.2026)
Erstellte/geänderte Dateien (8):
| Kategorie | Dateien | Details |
|---|---|---|
| Actions (1) | app/Actions/Fortify/CreateNewUser.php |
origin aus config('app.theme') via UserOrigin::tryFrom(), hub_id aus Input |
| Models (1) | app/Models/Partner.php |
PartnerType Cast hinzugefügt (type Feld) |
| Volt-Komponenten (2) | resources/views/livewire/products/index.blade.php, resources/views/livewire/partner/profile.blade.php |
products/index: echte DB-Queries, Rollen-basierte Filterung (Admin/Customer/Partner). partner/profile: neue öffentliche Profilseite |
| Routes (1) | routes/admin.php |
partner.profile Route hinzugefügt |
| Tests (3) | CreateNewUserOriginTest, LocalFeedTest, PartnerProfilePageTest |
17 Tests – alle bestanden |
Wichtige Korrekturen (12.02.2026):
productsTabelle hat keineskuSpalte – aus Suche und Tabellenansicht entfernt- Partner
typewar nicht aufPartnerTypeEnum gecastet –PartnerType::classCast zuPartner.phphinzugefügt Volt::test()wirftModelNotFoundExceptiondirekt (kein HTTP 404) – 404-Tests nutzentoThrow()
Pint-Formatierung: ✅ (5 Dateien: Partner.php, CreateNewUser.php, 3 Test-Dateien)
Tests Phase 3 gesamt: 17 Tests, alle bestanden ✅
Phase 2: Händler-Profil & Produkt-Management – ✅ KERN ABGESCHLOSSEN (12.02.2026)
Erstellte/geänderte Dateien (11):
| Kategorie | Dateien | Details |
|---|---|---|
| Policies (2) | app/Policies/PartnerPolicy.php, ProductPolicy.php |
PartnerPolicy: viewAny/view/create/update/delete/curateProducts. ProductPolicy: viewAny/view/create/update/delete/curate |
| Volt-Komponenten (4) | admin/partners/index.blade.php, admin/partners/edit.blade.php, products/create-teaser.blade.php, admin/products/index.blade.php |
Partner-Übersicht, Profil-Edit (Story+Öffnungszeiten), Teaser-Erstellen (Typ A, Preislogik via Enum), Kuration-Queue |
| Routes (1) | routes/admin.php |
4 neue Routen: admin.partners.index, admin.partners.edit, products.create.teaser, admin.products.index |
| Seeders (1) | database/seeders/RoleSeeder.php |
curate products Permission hinzugefügt |
| Tests (5) | PartnerPolicyTest, ProductPolicyTest, PartnerProfileUpdateTest, TeaserProductCreateTest, ProductCurationTest |
48 Tests – alle bestanden |
Enums erweitert (12.02.2026):
app/Enums/ProductType.php:requiresTicket(): bool,allowedPriceTypes(): arraytests/Unit/Enums/ProductTypeTest.php: 4 neue Tests für Typ A/B Geschäftsregeln
Pint-Formatierung: ✅ (keine Style-Korrekturen nötig)
Wichtige Erkenntnisse:
- Volt
actingAs()istvoid– für Tests immer$this->actingAs($user)VORVolt::test()aufrufen - Für HTTP-Tests mit
partner.setupMiddleware:Partner::factory()->setupCompleted()->create()verwenden Storage::fake('public')+UploadedFile::fake()->image()für File-Upload-Tests
Phase 1: DB & Core-Vervollständigung – ✅ ABGESCHLOSSEN (12.02.2026)
Erstellte Dateien (29 Dateien):
| Kategorie | Dateien | Details |
|---|---|---|
| Enums (6) | app/Enums/ProductType.php, ProductStatus.php, PriceType.php, PartnerType.php, UserOrigin.php, CurationStatus.php |
Alle mit label(), Status-Enums zusätzlich mit color(), UserOrigin mit tonality() |
| Neue Models (4) | app/Models/Product.php, Media.php, Setting.php, ProductLogistics.php |
Product: 6 Relationships, 6 Scopes. Media: Polymorphe Beziehung. Setting: Key-Value mit getValue/setValue Helpers |
| Aktualisierte Models (11) | Attribute, AttributeValue, Category, Collection, Tag, ProductVariant, ShippingClass, TaxRate, Hub, Partner, User |
Alle Sparse Models mit Fillable, Casts, Relationships ergänzt. Partner: +products(), +media(). User: +hub(), +origin Cast |
| Migrationen (4) | 2026_02_12_000001 bis 000004 |
Users (+origin, +hub_id), Products (+product_type, +is_curated, +hub_id, +price_type, +is_available, +curated_at, +curated_by), Partners (+story_text, +opening_hours, +specialties, +founded_year), Settings-Tabelle |
| Factories (5) | ProductFactory, MediaFactory, PartnerFactory, HubFactory, BrandFactory |
Alle mit sinnvollen States (localStock, smartOrder, active, retailer, manufacturer, etc.) |
| Seeders (1) | SettingsSeeder |
6 Default-Settings: Ticket-Gültigkeit, Beleg-Deadline, Ticket-Limits, Provisions-Defaults |
| Config (1) | config/domains.php |
+local4local Domain (local4local.test / local4local.online) |
| Tests (7 Dateien) | 3× Unit (Enums), 4× Feature (Product, Setting, User, Partner) | 47 Tests, 84 Assertions – alle bestanden |
Migrationen auf Produktions-DB ausgeführt: ✅ Settings-Seeder ausgeführt: ✅ (6 Settings angelegt) Pint-Formatierung: ✅ (7 Style-Issues automatisch korrigiert)
Vorbestehende Test-Failures – BEHOBEN (12.02.2026): Alle 16 vorbestehenden Fehler plus 15 weitere Fehler (31 insgesamt) wurden systematisch behoben. Die gesamte Test-Suite besteht jetzt mit 139 Tests, 269 Assertions – alle bestanden ✅
Behobene Probleme:
phpunit.xml:BASIC_AUTH_ENABLED=falsehinzugefügt (BasicAuth-Middleware blockierte alle HTTP-Tests)admin/partners/index.blade.php: Flux UI v2 Shorthand<flux:columns>→<flux:table.columns>korrigiertconfig/livewire.php:component_layoutKey hinzugefügt (Livewire 4 Layout-Resolution)- Auth-Tests: Portal-Domain (
portal.b2in.test) für alle HTTP-Requests RegistrationTest: Komplett neu geschrieben für code-basierte Registrierung über/reg/{role}PasswordResetTest:CustomResetPasswordNotificationstatt Standard-ResetPasswordEmailVerificationTest: Redirect zupartner.setup.wizardstattdashboardProfileUpdateTest: SoftDeletes-Assertion (->trashed()) und Layout-RedirectDashboardTest:RefreshDatabaseTrait hinzugefügtProductCurationTest,PartnerProfileUpdateTest,TeaserProductCreateTest:actingAs()Chaining korrigiert (VoltactingAs()istvoid)TeaserProductCreateTest:mainImageUpload,setupCompleted()Factory-State,CategoryFactoryerstelltProductCurationTest: Authorization-Test angepasst (Component authorize inwith())CategoryModel:HasFactoryTrait hinzugefügt
1. Validierung: IST-Zustand vs. Konzeption
✅ Bereits implementiert und konzeptkonform
| Bereich | Status | Details |
|---|---|---|
| Hub-System | ✅ Fertig | hubs + hub_locations Tabellen, Hub Model mit Relationships |
| Partner-System | ✅ Basis fertig | partners Tabelle mit hub_id, type, Provisionsfelder, Parent/Child-Beziehungen |
| User-System | ✅ Basis fertig | Users mit partner_id, SoftDeletes, Spatie Roles |
| Rollen & Permissions | ✅ Fertig | Customer, Estate-Agent, Retailer, Manufacturer, Admin, Super-Admin (via Spatie) |
| Registrierungs-Codes | ✅ Fertig | registration_codes mit broker_partner_id für Makler→Kunden Attribution |
| Partner-Invitations | ✅ Fertig | partner_invitations mit Token, Expiry, Status |
| Multi-Domain-Routing | ✅ Fertig | ThemeMiddleware, ThemeServiceProvider, config/domains.php (5 Domains) |
| Auth-System | ✅ Fertig | Fortify + Sanctum, Login, Register, Passwort-Reset, Email-Verifizierung, 2FA |
| Admin-Portal | ✅ Basis fertig | Dashboard, User-Management, Partner-Management, Hub-Management, CMS |
| Partner-Setup-Wizard | ✅ Fertig | Setup-Workflow für neue Partner nach Registrierung |
| Produkt-DB-Struktur | ✅ Tabellen vorhanden | products, product_variants, categories, tags, brands, collections, attributes, media |
⚠️ Teilweise implementiert – Erweiterung nötig
| Bereich | Was fehlt | Konzept-Referenz |
|---|---|---|
| Product Model | Migration existiert, aber kein App\Models\Product – Model muss erstellt werden |
Abschnitt 2: Produkt-Modul |
| Media Model | Migration existiert, aber kein App\Models\Media – Model muss erstellt werden |
Für Produkt-Bilder, Partner-Galerie |
| Partner-Profil | Basis-Felder vorhanden, aber es fehlen: Team-Fotos, Showroom-Galerie, Story-Text, Öffnungszeiten | Abschnitt 3: "Faces" Profile |
| Produkt-Upload UI | livewire/products/create existiert als Blade, aber unvollständig |
Abschnitt 2: Händler-Upload |
| Produkt-Feed | livewire/products/index existiert, aber keine Hub-basierte Filterung |
Abschnitt 5: Local Feed |
| Sparse Models | Attribute, AttributeValue, Category, Collection, Tag, ProductVariant, ShippingClass, TaxRate haben keine Relationships definiert |
Allgemein |
❌ Nicht implementiert – Muss gebaut werden
| Bereich | Beschreibung | Konzept-Referenz |
|---|---|---|
User origin Feld |
Herkunft des Kunden (style2own / stileigentum) für Theme-Steuerung |
Abschnitt 1: Core-Modul |
User hub_id Feld |
Direkte Hub-Zuordnung für Kunden (nicht nur indirekt über Partner) | Abschnitt 1: Hub-Logik |
product_type Feld |
Unterscheidung local_stock (Säule A) vs. smart_order (Säule B) |
Abschnitt 2: Produkt-Modul |
is_curated Feld |
Admin-Freigabe-Flag für Produkt-Sichtbarkeit | Abschnitt 2: Produkt-Modul |
| Ticket-System | Komplett fehlend: Tickets, QR-Codes, Voucher-Generierung | Abschnitt 3A: Ticket-System |
| Transaction/Receipt | Beleg-Upload, State Machine (pending→confirmed→paid→distributed) | Abschnitt 3B: Clearing-System |
| Wallet/Ledger | Cashback-Wallets, Provisions-Split, Kassenbuch | Abschnitt 3C: Wallet-Logik |
| Kunden-Dashboard | "Mein Zuhause" – emotionaler Feed mit Produkten aus dem eigenen Hub | Abschnitt 4: User Journey |
| Setup-Buchung | Service-Store für Händler (z.B. Setup-Paket 399€) | Abschnitt 4: Partner-Modul |
| Enums | Keine PHP Enums vorhanden (ProductType, TransactionStatus, TicketStatus, etc.) | Best Practice |
| Form Requests | Keine Form Request Klassen vorhanden | Best Practice |
| Policies | Keine Authorization Policies vorhanden | Best Practice |
2. Entwicklungsphasen
Phase 1: Datenbank & Core-Vervollständigung ✅ ABGESCHLOSSEN
Geschätzter Aufwand: 2-3 Tage | Tatsächlich: 1 Tag (12.02.2026) Priorität: HÖCHSTE – Basis für alle weiteren Phasen
1.1 Fehlende Models erstellen
App\Models\Producterstellen mit allen Relationships:belongsTo(Partner),belongsTo(Brand),belongsTo(Collection)belongsToMany(Category),belongsToMany(Tag)hasMany(ProductVariant),morphMany(Media)hasMany(RelatedProduct)- Scopes:
active(),curated(),localStock(),smartOrder(),inHub($hubId)
App\Models\Mediaerstellen (polymorphe Beziehung)- Fehlende Relationships in Sparse Models ergänzen:
Attribute→hasMany(AttributeValue)AttributeValue→belongsTo(Attribute)Category→ self-referencing parent/children,belongsToMany(Product)Tag→belongsToMany(Product)ProductVariant→belongsTo(Product),belongsToMany(AttributeValue)- etc.
Partner::products()Relationship aktivieren (aktuell auskommentiert)
1.2 Migrationen: User-Erweiterung
Migration: add_origin_and_hub_id_to_users_table
origin(nullable string) –style2own|stileigentum| nullhub_id(nullable FK → hubs) – direkte Hub-Zuordnung für Kundenbroker_partner_id(nullable FK → partners) – direkte Makler-Attribution (Alternative zu indirekter Verknüpfung über Partner-Hierarchie; Entscheidung nötig: reichtpartner.parent_partner_idoder braucht User direkt ein Feld?)
Offene Frage 1: Soll der Kunde einen eigenen Partner-Eintrag bekommen (so wie jetzt über
partner_id→ Partner mitparent_partner_id) oder reicht eine direktehub_id+broker_partner_idauf dem User? Die aktuelle Architektur erstellt für jeden Kunden einen Partner-Eintrag. Das könnte für das Wallet/Ledger System nützlich sein (jeder Partner hat eigene Provision-Settings). Empfehlung: Beibehalten, aberhub_idundorigindirekt auf User setzen für schnelle Queries.
1.3 Migrationen: Produkt-Erweiterung
Migration: add_marketplace_fields_to_products_table
product_type(string, default:local_stock) –local_stock|smart_orderis_curated(boolean, default: false) – Admin-Freigabecurated_at(nullable datetime) – Zeitpunkt der Freigabecurated_by(nullable FK → users) – Wer hat freigegebenhub_id(nullable FK → hubs) – Direkte Hub-Zuordnung (ergänzend zu Partner→Hub)price_type(string) –fixed|from_price|on_request(Preisanzeige-Logik)price_display_text(nullable string) – z.B. "Ab 2.500 €" Freitextis_available(boolean, default: true) – Verfügbar/Verkauft Toggle für Händler
1.4 Migrationen: Partner-Profil Erweiterung
Migration: add_profile_fields_to_partners_table
story_text(nullable text) – "Seit 1950 in Herford..."opening_hours(nullable JSON) – Öffnungszeiten strukturiertspecialties(nullable JSON) – Fachgebiete/Spezialisierungenfounded_year(nullable integer) – Gründungsjahr
Hinweis: Team-Fotos und Showroom-Galerie werden über die polymorphe
mediaTabelle abgebildet (type:team_photo,showroom,gallery).
1.5 Migration: Settings-Tabelle
Migration: create_settings_table
idgroup(string) – z.B.tickets,marketplace,commissionskey(string, unique innerhalb group)value(text, nullable)type(string) –string|integer|boolean|jsondescription(nullable text) – Beschreibung für Admin-UItimestamps
Default-Werte per Seeder:
tickets.validity_days= 30tickets.receipt_upload_deadline_days= 30tickets.max_per_merchant_per_customer= 3tickets.max_merchants_per_customer= 4commissions.default_broker_rate= 0 (individuell)commissions.default_cashback_rate= 0 (individuell)
1.6 Domain-Ergänzung: local4local
config/domains.phpumlocal4localDomain erweitern:- Produktion:
local4local.online - Entwicklung:
local4local.test - Theme, Farben, Fonts definieren
- Produktion:
.envVariablen:DOMAIN_LOCAL4LOCAL=local4local.test- Routing in
routes/domains.phpeinbinden
1.7 Enums erstellen
App\Enums\ProductType–LocalStock,SmartOrderApp\Enums\ProductStatus–Draft,Active,Archived,SoldApp\Enums\PriceType–Fixed,FromPrice,OnRequestApp\Enums\PartnerType–Retailer,Manufacturer,EstateAgentApp\Enums\UserOrigin–Style2Own,StilEigentumApp\Enums\CurationStatus–Pending,Approved,Rejected
1.8 Factories & Seeders
ProductFactoryerstellenMediaFactoryerstellenSettingFactoryerstellenPartnerFactoryerweitern (fehlende Felder)ProductSeederfür TestdatenSettingsSeederfür Default-WerteHubSeedererweitern (wenn nötig)
1.9 Tests Phase 1
- Unit-Tests für alle neuen Models und Relationships
- Unit-Tests für Enums
- Feature-Tests für Migrations (Datenintegrität)
Phase 2: Händler-Profil & Produkt-Management – ✅ KERN ABGESCHLOSSEN (12.02.2026)
Geschätzter Aufwand: 5-6 Tage | Tatsächlich: 1 Tag (12.02.2026) Priorität: HOCH – Content-Erstellung ermöglichen
2.0 Architektur-Entscheidung: EINE Tabelle, ZWEI Masken
Entscheidung: Beide Produkttypen nutzen dieselbe
productsTabelle. Das Feldproduct_type(local_stock|smart_order) bestimmt, welche Eingabemaske und Validierung angewendet wird.
Begründung:
- Beides sind Produkte mit identischem Kern (Name, Beschreibung, Preis, Bilder, Partner, Hub, Kategorie)
- Der Local Feed zeigt beide Typen gemischt an – eine Tabelle ermöglicht einfache Queries ohne UNION
- Das Teaser-Produkt ist ein "leichtes" Produkt: die Felder, die es nicht braucht, bleiben
null - Ein Model, ein Satz Relationships, ein Satz Scopes – kein doppelter Code
- Filterung per Scope:
Product::localStock(),Product::smartOrder()
Kundenfeedback: Typ A vs. Typ B (12.02.2026)
Implementiert in
app/Enums/ProductType.php:
requiresTicket(): bool→LocalStock = true,SmartOrder = falseallowedPriceTypes(): array→LocalStock = [FromPrice, OnRequest],SmartOrder = [Fixed, FromPrice, OnRequest]
Typ A – Teaser-Produkte (product_type = 'local_stock')
Komplex → Beratung → Laden → Ticket zwingend erforderlich
- Aufwendige Konfiguration (Maße, Module, Materialien) – Abschluss nur im Laden
- Online nur Beispiele, Referenzen, grobe Preisindikationen
- Typisch: Küchenstudios, maßgefertigte Möbel (Cabinet), Einbausysteme
- Preistyp: Nur
from_priceoderon_request– Festpreis online nicht erlaubt - Ticket: zwingend (
ProductType::LocalStock->requiresTicket() === true)
Typ B – Standard-Produkte (product_type = 'smart_order')
Einfach → verständlich → skalierbar → Ticket optional / Direktkauf möglich
- Klare Varianten, vollständig online konfigurierbar, ca. 99 % des Sortiments
- Preistyp: Alle erlaubt (
fixed,from_price,on_request) - Ticket: optional – Direktkauf online möglich oder Ticket + Ladenbesuch
Maske 1: Typ A – Teaser-Produkt / Local Stock (Händler) Ziel: Extrem vereinfacht, handy-optimiert. Kein komplexes Warenwirtschafts-Monster.
| Feld | DB-Spalte | Pflicht | Beschreibung |
|---|---|---|---|
| Fotos | → media (morph) |
Ja (min. 1) | 1 Hauptbild + optional 2 Galerie-Bilder |
| Titel | name |
Ja | Produktname |
| Kurzbeschreibung | description_short |
Ja | Max. 180 Zeichen |
| Preistyp | price_type |
Ja | Nur from_price oder on_request (via allowedPriceTypes()) |
| Preisangabe | price_display_text |
Cond. | Pflicht wenn from_price (z.B. "Ab 2.500 €") |
| Kategorie | → category_product (Pivot) |
Ja | Dropdown-Auswahl |
| Status | status |
Ja | Verfügbar / Verkauft |
Automatisch gesetzt: product_type = 'local_stock', hub_id = Hub des Händlers, partner_id = Partner des Users.
PriceType::Fixed für Maske 1 nicht erlaubt – Validierung via ProductType::LocalStock->allowedPriceTypes().
Maske 2: Typ B – Konfigurations-Produkt / Smart Order (Hersteller)
Das bestehende 6-Tab-Formular (livewire/products/create.blade.php) mit 13 Sektionen:
Basis → Bilder → Physisch → Material & Herkunft → Kommerziell → Zuordnung & Verwaltung
Automatisch gesetzt: product_type = 'smart_order', partner_id = Partner des Users.
Hub-Zuordnung: Manuell wählbar (Hersteller können in mehreren Hubs aktiv sein).
Alle Preistypen erlaubt; Standard-Varianten mit Festpreis möglich.
Routing-Logik: ✅ AKTUALISIERT (13.02.2026)
// Beide Rollen können BEIDE Produkttypen anlegen.
// Die Produktliste zeigt zwei Buttons:
// "Neues Teaser-Produkt" → Maske 1 (Typ A: Teaser, vereinfacht, Ticket zwingend)
// "Neues Standard-Produkt" → Maske 2 (Typ B: Konfiguration, komplex, Ticket optional)
// Sichtbar für: Retailer, Manufacturer, Admin, Super-Admin
// Zusätzlich: Produkttyp-Filter in der Produktliste (Teaser / Standard / Alle)
2.1 Partner-Profil Erweiterung (Admin-Portal) – ✅ Kern implementiert
- Partner-Profil Formular erweitern:
- Story-Text Editor (
<flux:textarea>) - Öffnungszeiten-Eingabe (strukturiertes JSON-Formular, 7 Wochentage)
- Team-Fotos Upload (mehrere Bilder via
<flux:file-upload>) – ausstehend - Showroom-Galerie Upload – ausstehend
- Story-Text Editor (
- Form Request:
UpdatePartnerProfileRequest– inline Validierung verwendet - Policy:
PartnerPolicy(viewAny, view, update, delete, curateProducts) - Admin Partner-Übersicht:
admin/partners/index.blade.php(Suche, Filter, Tabelle)
2.2 Produkt-CRUD: Maske 1 – Teaser / Local Stock (Händler) – ✅ Implementiert
- Volt-Komponente:
livewire/products/form-teaser.blade.php(merged Create + Edit)- Einzelseiten-Formular (KEIN Tab-Layout)
- Bild-Upload via
WithFileUploads+ Drag & Drop Bildsortierung - Felder: Foto, Titel, Kurzbeschreibung, Preistyp (nur FromPrice/OnRequest), Preistext, Kategorie, Status, Produktnummer
- Produktnummern: Partner-Produktnummer (auto-generiert) + B2in-Artikelnummer (auto bei Save)
- Automatisch:
product_type = 'local_stock',hub_idvom Händler-Hub,partner_id - Preistyp-Validierung via
ProductType::LocalStock->allowedPriceTypes()(kein Festpreis) - Freigabe-Workflow: UI "aktiv" → DB
pending(korrekt zur Freigabe, nicht direkt Active)
- Produkt-Liste: Zwei Buttons ("Neues Teaser-Produkt" + "Neues Standard-Produkt") für alle Partner-Rollen
- Produkt-Liste: Produkttyp-Filter (Teaser / Standard / Alle)
- Produkt-Liste: Vorschaubild (erstes Bild nach order_column) vor Produktname
- Produkt bearbeiten: Pre-Fill, Status-Handling, Media-Verwaltung, Activity-Log, Toast-Notification
- Bildsortierung: Drag & Drop, erstes Bild = Standardbild, order_column in DB
- Produkt archivieren / als "Verkauft" markieren – in Edit-Formular und Produktliste (Dropdown-Menue)
- Form Request:
StoreTeaserProductRequest– inline Validierung verwendet - Policy:
ProductPolicy(viewAny, view, create, update, delete, curate)
2.3 Produkt-CRUD: Maske 2 – Standard / Smart Order – ✅ Implementiert (13.02.2026)
- Volt-Komponente:
livewire/products/form-standard.blade.php(merged Create + Edit)- 8-Tab-Layout: Basis, Bilder, Maße & Material, Verpackung & Versand, Kommerziell, Service & Garantie, Nachhaltigkeit, Zuordnung
- Erstellt Product + Master-Variante (ProductVariant) + ProductLogistics + Media
- Preis-Typ Logik: Festpreis / Ab-Preis / Preis auf Anfrage (alle 3 für SmartOrder erlaubt)
- Automatisch:
product_type = 'smart_order', Hub-Fallback auf Partner-Hub - File-Uploads: Mehrere Bilder (max 10, JPG/PNG, max 10 MB) + Drag & Drop Bildsortierung
- Preise in EUR eingeben → automatische Umrechnung in Cents bei Speicherung
- Alle optionalen Felder: MPN, EAN/GTIN, UVP, EK, Lagerstatus, Lieferzeit, SEO-Metadaten, Maße, Verpackung
- Validierung inline (kein separater Form Request – konsistent mit Teaser-Formular)
- Authorization: ProductPolicy + Rollenprüfung (Retailer, Manufacturer, Admin, Super-Admin)
- Bei Edit: Toast-Notification, aktiver Tab bleibt erhalten, kein Redirect
- Migration:
tax_rate_idaufproduct_variantsnullable gemacht (war NOT NULL ohne Default)
2.4 Admin: Produkt-Kuration (Approval Queue) – ✅ Vollstaendig implementiert
- Admin-View:
admin/products/index.blade.php– Tabelle mit Filtern (Status, Typ, Haendler, Kategorie, Suche) - Statistik-Karten: Gesamt, Zur Freigabe, In Korrektur, Freigegeben (klickbar als Schnellfilter)
- Approve Action:
is_curated=true,curated_at,curated_by+ Flux::toast() - Correction Action: Inline-Formular mit Pflicht-Textfeld →
curation_notes - Reject Action: Inline-Formular mit Pflicht-Ablehnungsgrund →
curation_notes+status=Archived - Autorisierung via
ProductPolicy::curate(curate productsPermission) - Admin kann alle Produkte bearbeiten (gleiche Edit-Formulare wie Haendler)
- Admin kann Produkte archivieren / als verkauft markieren (Dropdown-Menue)
- Erstellungsdatum in Tabelle sichtbar
- Kuration-Hinweis beim Haendler: Callout im Edit-Formular (Korrektur gelb, Ablehnung rot)
- Notification an Händler/Hersteller bei Freigabe/Ablehnung – ausstehend
- Admin kann
product_typebei 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
RoleSeederdefiniert, aber nie in die Produktions-DB eingespielt Attempt to read property "id" on nullbeim Öffnen von/admin/products- Lösung: Migration
2026_02_27_154145_add_curate_products_permission.phpPermission::firstOrCreate(['name' => 'curate products', 'guard_name' => 'web'])- Admin-Rolle bekommt Permission via
Role::where('name', 'Admin')->first()(keinfindByName= 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->partnerwarnull→ Crash beim Speichern - Lösung in
form-teaser.blade.phpundform-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:
selectedPartnerIdPflichtfeld für Admins bei Neuanlage
- Neues Property
- 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-500innerhalb von::placeholderPseudo-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
- Feature-Tests:
PartnerPolicyTest(10 Tests) – viewAny, view, update, curateProducts - Feature-Tests:
ProductPolicyTest(14 Tests) – alle Policy-Methoden - Feature-Tests:
PartnerProfileUpdateTest(8 Tests) – Profil-Update, Öffnungszeiten, Validierung - Feature-Tests:
TeaserProductCreateTest(18 Tests) – Happy Path, Preistyp-Validierung, Autorisierung, Status-Logik, Produktnummern - Feature-Tests:
ProductCurationTest(23 Tests) – Approve, Reject mit Pflicht-Textfeld, Correction, Archive/Sold, Filter, Kuration-Notes-Anzeige im Edit - Feature-Tests:
StandardProductCreateTest(39 Tests) – Zugriff, Happy Path, alle Tabs, Validierung, Preistypen, Produktnummern, Marken - Feature-Tests:
ProductEditTest(27 Tests) – Pre-Fill, Save, Status, Validierung, Media-Sortierung, Wood Origins, Activity, Archive/Sold - Feature-Tests:
TeaserProductEditTest(30 Tests) – Pre-Fill, Save, Status, Media-Sortierung, Produktnummern - Tests: Beide Rollen (Händler + Hersteller) können BEIDE Produkttypen anlegen (Teaser + Standard)
Tests Phase 2 gesamt: 109 Produkt-Tests + 48 Policy/Profil/Kuration-Tests, alle bestanden ✅
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
my-data.blade.phperweitert um:- Story-Text – Freitext max. 2000 Zeichen mit Live-Zeichenzähler, Feld:
story_text - Öffnungszeiten – 7-Tage-Eingabe (Mo–So),
open/close-Felder + "Geschlossen"-Checkbox, Feld:opening_hours(JSON) - Spezialisierungen – kommagetrennte Eingabe → gespeichert als JSON-Array, Feld:
specialties - Gründungsjahr – Zahlfeld mit Validierung (1800–heute), Feld:
founded_year - Team-Fotos – Mehrfach-Upload via
WithFileUploads, gespeichert alsMedia.type = 'team_photo' - Showroom-Galerie – Mehrfach-Upload,
Media.type = 'showroom' - Foto-Löschen per Klick (mit
wire:confirm)
- Story-Text – Freitext max. 2000 Zeichen mit Live-Zeichenzähler, Feld:
- Formular in 4 separate
flux:card-Abschnitte gegliedert: Stammdaten, Präsentation & Story, Öffnungszeiten, Fotos
2.8.2 Hersteller-Profil (Manufacturer) – Firma & Marke
my-data.blade.phperweitert um:- Story-Text – Unternehmensgeschichte, Feld:
story_text - Gründungsjahr – Feld:
founded_year - Spezialisierungen – Feld:
specialties - Marken-Bilder – Mehrfach-Upload,
Media.type = 'brand_image'
- Story-Text – Unternehmensgeschichte, Feld:
- Abschnitte: Stammdaten, Über das Unternehmen, Marke (mit Markenname, Beschreibung, Bilder)
2.8.3 Öffentliches Partnerprofil (livewire/partner/profile.blade.php)
- Story-Text anzeigen wenn vorhanden (bereits vorhanden)
- Öffnungszeiten anzeigen (bereits vorhanden)
- Spezialisierungen + Gründungsjahr anzeigen (bereits vorhanden)
- Showroom-Galerie – 3-spaltige Bild-Galerie unterhalb der Story
- Marken-Bilder (Hersteller) – 3-spaltige Galerie unter der Story
- Team-Fotos – 2-spaltige Galerie in der rechten Sidebar
2.8.4 Datenbank
- Alle Felder vorhanden:
story_text,opening_hours,specialties,founded_year(Migration2026_02_12_000003_add_profile_fields_to_partners_table) - Media-System: Custom
MediaModel mittype-Feld (kein Spatie) –Partner::media()morph-Relation bereits vorhanden
2.8.5 Tests Phase 2.8
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
3.1 User-Origin Tracking – ✅ Implementiert
- Bei Registrierung:
originautomatisch aus Domain setzen- Request von
style2own.test→origin = 'style2own' - Request von
stileigentum.test→origin = 'stileigentum'
- Request von
CreateNewUserAction (Fortify) erweitern umorigin+hub_id- Hub-Auswahl bei Registrierung (oder automatisch via Invite-Code des Maklers) – ausstehend
3.2 Kunden-Dashboard ("Mein Zuhause") – ✅ Kern implementiert
topOffersim Dashboard: echte Produkte aus dem Hub des Kunden ladenProduct::query()->where('status', Active)->where('is_curated', true)->...->take(3)- Fallback auf Dummy-Daten wenn noch keine Produkte vorhanden
- Eigene Dashboard-Seite für Customer-Rolle – ausstehend
- Theme-Steuerung basierend auf
origin(style2own/stileigentum) – ausstehend
3.3 Local Feed (Marktplatz-View) – ✅ Implementiert
products/indexmit echter DB-Abfrage und Rollen-Filterung:- Admin: sieht alle Produkte
- Customer: nur
active + is_curated + is_availableaus eigenem Hub - Retailer/Partner: nur eigene Produkte
- Suchfilter nach Name, Kategorie-Filter, Paginierung (20 Einträge)
- Produkt-Detailseite – ausstehend
- Filteroptionen: Preisbereich, Verfügbarkeit – ausstehend
3.4 Partner-Profilseite (öffentlich) – ✅ Implementiert
partner/profile.blade.php: Story, Öffnungszeiten, Produkte (max. 6), Spezialisierungen, Adresse- Route:
partner/{partnerId}/profile→partner.profile - Team-Fotos / Showroom-Galerie – ausstehend (abhängig von Media-Upload)
3.5 Tests Phase 3 – ✅ Abgeschlossen
CreateNewUserOriginTest(6 Tests) – Origin-Tracking, hub_idLocalFeedTest(5 Tests) – Hub-Filterung, Kuration, Rollen, Suche, KategoriePartnerProfilePageTest(6 Tests) – Profilseite, Story, Produkte, Auth
Tests Phase 3 gesamt: 17 Tests, alle bestanden ✅
Phase 4: Ticket-System & QR-Generierung
Geschätzter Aufwand: 3-4 Tage Priorität: HOCH – Kern der Monetarisierung
4.1 Datenbank: Tickets
Migration: create_tickets_table
id,uuid(public identifier)user_id(FK → users, Kunde)merchant_partner_id(FK → partners, Händler)product_id(nullable FK → products)hub_id(FK → hubs)code(unique string, z.B. "TK-2026-XXXXX")qr_code_path(nullable string, Pfad zur generierten QR-Datei)status(string:active|redeemed|expired)discount_type(string:percentage|fixed)discount_value(decimal)valid_until(datetime)redeemed_at(nullable datetime)timestamps
4.2 Ticket Model & Enum
App\Models\Ticketmit RelationshipsApp\Enums\TicketStatus–Active,Redeemed,ExpiredTicketFactoryerstellenTicketPolicyerstellen
4.3 Ticket-Generierung
- Composer-Paket für QR-Code Generierung (
simplesoftwareio/simple-qrcodeoder ähnlich) – Genehmigung einholen - Service:
App\Services\TicketServicegenerateTicket(User $user, Partner $merchant, ?Product $product): TicketgenerateQrCode(Ticket $ticket): string(gibt Pfad zurück)validateTicket(string $code): ?TicketredeemTicket(Ticket $ticket): void
- Livewire-Komponente: "Ticket ziehen" Button auf Produktdetailseite
- Mail/PDF-Generierung mit QR-Code + Händler-Info
4.4 Händler: Ticket-Einlösung
- Händler-Dashboard: "Eingelöste Tickets" Übersicht
- QR-Code Scanner (Optional, Phase 5)
- Manuelle Code-Eingabe zum Einlösen
4.5 Tests Phase 4
- Unit-Tests für TicketService
- Feature-Tests für Ticket-Generierung
- Feature-Tests für Ticket-Einlösung
- Tests für QR-Code Generierung
Phase 5: Transaction-Engine & Clearing
Geschätzter Aufwand: 5-6 Tage Priorität: MITTEL – Aufbauend auf Ticket-System
5.1 Datenbank: Transaktionen
Migration: create_transactions_table
id,uuidticket_id(FK → tickets)user_id(FK → users, Kunde)merchant_partner_id(FK → partners, Händler)broker_partner_id(nullable FK → partners, Makler)amount(integer, in Cents)receipt_image_path(nullable string, hochgeladener Beleg)receipt_amount(nullable integer, vom Kunden angegebener Betrag in Cents)status(string: State Machine)merchant_confirmed_at(nullable datetime)merchant_confirmed_amount(nullable integer, in Cents)invoice_generated_at(nullable datetime)paid_at(nullable datetime)distributed_at(nullable datetime)notes(nullable text)timestamps
5.2 Transaction State Machine
Enum: App\Enums\TransactionStatus
PendingReceipt– Ticket eingelöst, Kunde muss Beleg hochladenPendingMerchant– Beleg hochgeladen, Händler muss bestätigenConfirmed– Händler hat bestätigt → Rechnung an Händler generierenInvoiced– Rechnung erstellt → Zahlung ausstehendPaid– Händler hat Provision an B2in überwiesenDistributed– Provisionen an Makler/Kunde ausgeschüttetRejected– Händler hat abgelehntDisputed– Streitfall
5.3 Beleg-Upload (Kunden-View)
- Livewire-Komponente: Beleg-Upload Formular
- Foto-Upload des Kaufbelegs
- Betrag-Eingabe
- Verknüpfung mit Ticket
- Validierung: Ticket muss
redeemedsein - Mail-Notification an Händler: "Neuer Beleg zur Bestätigung"
5.4 Händler-Bestätigung
- Händler-Dashboard: "Offene Umsatz-Bestätigungen"
- Beleg anzeigen mit Bestätigungs-/Ablehnungs-Buttons
- Händler kann Betrag korrigieren (falls Kunde falschen Betrag eingegeben hat)
- Bei Bestätigung: Event
TransactionConfirmedfeuern
5.5 Tests Phase 5
- Unit-Tests für State Machine Übergänge
- Feature-Tests für Beleg-Upload
- Feature-Tests für Händler-Bestätigung
- Tests für Events und Notifications
Phase 6: Wallet, Cashback & Provisions-System
Geschätzter Aufwand: 4-5 Tage Priorität: MITTEL – Monetarisierung
6.1 Datenbank: Wallets & Ledger
Migration: create_wallets_table
idpartner_id(nullable FK → partners)user_id(nullable FK → users)balance(integer, in Cents)type(string:cashback|commission|platform)timestamps
Migration: create_ledger_entries_table
id,uuidwallet_id(FK → wallets)transaction_id(nullable FK → transactions)type(string:credit|debit)amount(integer, in Cents, immer positiv)balance_after(integer, in Cents)description(string)timestamps
6.2 Provisions-Berechnung
- Service:
App\Services\CommissionServicecalculateSplit(Transaction $transaction): CommissionSplit- Berechnet: Makler-Anteil, Kunden-Cashback, B2in-Marge
- Nutzt
partner.provision_rate_percentageundpartner.provision_fixed_amount
- Event Listener für
TransactionPaid:- Erstellt Ledger-Einträge
- Aktualisiert Wallet-Balances
- Setzt Transaction-Status auf
distributed
6.3 Wallet-Views
- Kunden-Dashboard: "Mein Cashback" (Guthaben, Historie)
- Makler-Dashboard: "Meine Provisionen" (Guthaben, Historie)
- Admin: Wallet-Übersicht aller Partner und Kunden
6.4 Tests Phase 6
- Unit-Tests für CommissionService (Splits korrekt berechnet)
- Feature-Tests für Wallet-Operationen
- Feature-Tests für Event-basierte Distribution
- Edge Cases: Rundung, Null-Beträge, fehlende Provisionsregeln
Phase 7: Frontend-Polish & Domains
Geschätzter Aufwand: 3-4 Tage Priorität: MITTEL
7.1 Style2Own vs. StilEigentum Feinschliff
- CSS-Variablen-Sets für beide Brands finalisieren
- Wording-Datenbank: "Du" vs. "Sie" Ansprache in allen Texten
- Landingpage-Anpassungen je Brand
7.2 "Local for Local" Branding
- Hub-spezifisches Branding (z.B. "Local for Local OWL")
- Hub-Landingpages mit regionalen Inhalten
- Domain-Routing für
localforlocal.de(wenn verfügbar)
7.3 Responsive & Mobile
- Händler-Upload Formular mobil-optimiert ("Handy-optimiert" laut Konzept)
- Ticket-Anzeige mobil-optimiert (QR-Code gut scanbar)
- Kunden-Dashboard responsive
7.4 Tests Phase 7
- Browser-Tests (Laravel Dusk) für Multi-Domain
- Responsive Tests
3. Beantwortete Fragen & Entscheidungen
Architektur-Entscheidungen
Frage 1: Kunden-Partner-Beziehung ✅
Entscheidung: Beibehalten. Jeder Kunde behält seinen Partner-Eintrag. Zusätzlich
hub_idundorigindirekt auf User für schnelle Queries.
Frage 2: Hub-Zuordnung bei Produkten ✅
Entscheidung: Direktes
hub_idFeld auf Products. Hersteller können Produkte in mehreren Hubs anbieten.
Frage 3: QR-Code Paket ✅
Entscheidung:
chillerlan/php-qrcode
Frage 4: PDF-Generierung für Tickets ✅
Entscheidung:
spatie/laravel-pdf
Frage 5: Makler-Invite und Hub-Zuweisung ✅
Entscheidung: Automatisch vom Makler übernehmen. Admin kann manuell ändern.
Frage 6: Domain Local for Local ✅
Entscheidung:
local4local.online(Produktion) /local4local.test(Entwicklung). Muss inconfig/domains.phpergänzt werden.
Frage 11: Produkttypen – Eine oder zwei Tabellen? ✅
Entscheidung: EINE
productsTabelle, ZWEI Eingabemasken. Das Feldproduct_type(local_stock|smart_order) steuert welche Maske und Validierung greift. Siehe Phase 2.0 für Details.
- Maske 1 (Teaser/Local Stock): Vereinfacht, handy-optimiert, für Händler
- Maske 2 (Konfiguration/Smart Order): Komplex mit 6 Tabs, für Hersteller
Business-Logic Entscheidungen
Frage 7: Provisions-Split ✅
Entscheidung: Individuell pro Partner. Admin-Settings-Seite mit Feldern für:
- Makler-Provision (%)
- Kunden-Cashback (%)
- B2in-Marge (Rest) Felder werden pro Partner im Admin-Backend konfiguriert.
Frage 8: Ticket-Gültigkeit ✅
Entscheidung: Default 30 Tage. Konfigurierbar über Admin-Settings-Seite.
Frage 9: Beleg-Upload Deadline ✅
Entscheidung: Default 30 Tage. Konfigurierbar über Admin-Settings-Seite.
Frage 10: Ticket-Begrenzung pro Kunde ✅
Entscheidung: Produkte sind immer an einen Händler gebunden. Ein Kunde kann Tickets bei verschiedenen Händlern für ähnliche Produkte ziehen. Begrenzung über Admin-Settings:
- Max. Tickets pro Händler (z.B. 3)
- Max. Händler pro Zeitraum (z.B. 4)
- Konfigurierbar durch Admin
Benötigte Admin-Settings-Seite (Neue Anforderung)
Aus den Antworten ergibt sich die Notwendigkeit einer zentralen Admin-Settings-Seite für:
- Ticket-Gültigkeit (Tage)
- Beleg-Upload Deadline (Tage)
- Max. Tickets pro Händler pro Kunde
- Max. Händler pro Kunde pro Zeitraum
- Standard-Provisions-Split (Default-Werte für neue Partner)
Umsetzung:
settingsTabelle (key-value) + Admin-Settings-View in Phase 4/5. Alternativ:config/marketplace.phpmit.env-Variablen für nicht-dynamische Werte.
4. Technische Richtlinien für die Entwicklung
Architektur-Prinzipien
- Eloquent-First: Keine
DB::Facades, immerModel::query() - Form Requests: Jede Validierung in eigene Request-Klassen
- Policies: Jede Autorisierung über Laravel Policies
- Events/Listeners: Für alle Seiteneffekte (Mail, Wallet-Buchung, Notifications)
- Services: Business-Logik in Service-Klassen (
TicketService,CommissionService) - Enums: Alle Status-Werte als PHP 8.1+ Enums
Namenskonventionen
- Models: Singular, PascalCase (
Ticket,LedgerEntry) - Tabellen: Plural, snake_case (
tickets,ledger_entries) - Enums: PascalCase Keys (
LocalStock,SmartOrder) - Services:
{Domain}Service(TicketService,CommissionService) - Policies:
{Model}Policy(TicketPolicy,TransactionPolicy) - Form Requests:
{Action}{Model}Request(StoreProductRequest,UpdatePartnerProfileRequest) - Events: Past Tense (
TransactionConfirmed,TicketRedeemed)
Testing-Strategie
- Jede Phase muss vollständig getestet sein bevor die nächste beginnt
- Feature-Tests für alle Endpoints und Livewire-Komponenten
- Unit-Tests für Services und komplexe Business-Logik
- Pest als Test-Framework, Factories für Testdaten
- Mindestens: Happy Path + Validation + Authorization pro Feature
Reihenfolge der Entwicklung (innerhalb jeder Phase)
- Migration(s) erstellen und ausführen
- Model(s) mit Relationships, Casts, Scopes
- Enum(s) falls nötig
- Factory und Seeder
- Form Request(s)
- Policy
- Service-Klasse (falls komplexe Logik)
- Livewire/Volt Komponente
- Tests schreiben und ausführen
pint --dirtyfür Code-Formatierung
5. Abhängigkeiten zwischen Phasen
Phase 1 (Core) ──────────┬──────────> Phase 2 (Produkte)
│ │
│ ▼
└──────────> Phase 3 (Frontend) ──> Phase 7 (Polish)
│
▼
Phase 4 (Tickets)
│
▼
Phase 5 (Transactions)
│
▼
Phase 6 (Wallet/Cashback)
- Phase 1 ist Voraussetzung für alles
- Phase 2 und 3 können teilweise parallel laufen
- Phase 4 setzt Phase 3 voraus (Produktdetailseite muss existieren)
- Phase 5 setzt Phase 4 voraus (Tickets müssen existieren)
- Phase 6 setzt Phase 5 voraus (Transaktionen müssen existieren)
- Phase 7 kann teilweise parallel zu Phase 4-6 laufen
6. Neue Permissions (zu ergänzen)
Folgende Permissions müssen im RoleSeeder ergänzt werden:
| Permission | Beschreibung | Rollen |
|---|---|---|
curate products |
Produkte freigeben/ablehnen | Admin, Super-Admin |
view tickets |
Eigene Tickets sehen | Customer |
create tickets |
Ticket generieren | Customer |
redeem tickets |
Ticket einlösen | Retailer |
view transactions |
Transaktionen sehen | Customer, Retailer, Estate-Agent, Admin |
confirm transactions |
Umsatz bestätigen | Retailer |
upload receipts |
Beleg hochladen | Customer |
view wallet |
Wallet-Guthaben sehen | Customer, Estate-Agent, Retailer |
manage wallets |
Wallets verwalten | Admin |
view commissions |
Provisionen einsehen | Estate-Agent, Admin |
manage setup packages |
Setup-Pakete verwalten | Admin |
book setup package |
Setup-Paket buchen | Retailer |
Dieses Dokument dient als lebende Referenz für die Entwicklung. Bei Änderungen am Konzept muss dieser Plan entsprechend aktualisiert werden.
ochladen | Customer |
| view wallet | Wallet-Guthaben sehen | Customer, Estate-Agent, Retailer |
| manage wallets | Wallets verwalten | Admin |
| view commissions | Provisionen einsehen | Estate-Agent, Admin |
| manage setup packages | Setup-Pakete verwalten | Admin |
| book setup package | Setup-Paket buchen | Retailer |
Dieses Dokument dient als lebende Referenz für die Entwicklung. Bei Änderungen am Konzept muss dieser Plan entsprechend aktualisiert werden.