docs: add README and update design doc for post-plan features
New README.md covers quick start, tech stack, running tests, AI backend switching, admin features, and architecture overview. Design doc updated (2026-05-18): Tavily replaces SerpAPI, specs_text field, pipeline model cache step, findExistingCustomer in order flow, required-attribute UX, user permissions UI, article detail-first nav, updated open items. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
376171303e
commit
f0d9f374e6
2 changed files with 162 additions and 14 deletions
133
README.md
Normal file
133
README.md
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
# SuperSeller3000
|
||||||
|
|
||||||
|
Admin middleware for managing and selling refurbished IT hardware. Handles article ingestion via AI pipeline (photo → vision → specs → eBay text), multi-channel publishing (eBay), and order processing with Frappe ERP invoicing.
|
||||||
|
|
||||||
|
**Live:** `https://ss3k.schaunwama.de/admin`
|
||||||
|
**Full design doc:** `docs/superpowers/specs/2026-05-13-superseller3000-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env .env.local # fill in secrets (DB, Redis, AI keys, eBay, Frappe)
|
||||||
|
docker compose up -d
|
||||||
|
docker compose exec app php bin/console doctrine:migrations:migrate --env=prod
|
||||||
|
docker compose exec app php bin/console app:users:create --env=prod
|
||||||
|
```
|
||||||
|
|
||||||
|
Workers start automatically via Docker Compose (`worker-ai`, `worker-orders`, `worker-channel`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|---|---|
|
||||||
|
| Language / Framework | PHP 8.4 · Symfony 8.4 |
|
||||||
|
| ORM | Doctrine ORM |
|
||||||
|
| Database | PostgreSQL 17 — schemas: `app`, `logs`, `logs_archive` |
|
||||||
|
| Queue | Symfony Messenger + Redis Streams |
|
||||||
|
| AI | Ollama (local, SSH-tunnel) **or** Mistral Cloud API — alias-switchable in `services.yaml` |
|
||||||
|
| Web Search | Tavily API (`TAVILY_API_KEY`) |
|
||||||
|
| Admin | EasyAdmin 5 |
|
||||||
|
| Auth | Symfony Security + TOTP 2FA (scheb/2fa-totp) |
|
||||||
|
| Proxy | Caddy 2 (Auto-HTTPS) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests (fast, no external deps)
|
||||||
|
docker compose exec app php vendor/bin/phpunit tests/Unit/ --testdox
|
||||||
|
|
||||||
|
# Integration tests (requires staging credentials in .env.local)
|
||||||
|
bin/test-integration
|
||||||
|
|
||||||
|
# Single integration file
|
||||||
|
bin/test-integration tests/Integration/path/to/Test.php
|
||||||
|
|
||||||
|
# Static analysis + code style
|
||||||
|
docker compose exec app composer phpstan
|
||||||
|
docker compose exec app composer cs-check
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AI Backend
|
||||||
|
|
||||||
|
Switch between Ollama and Mistral in `config/services.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Ollama (default — local via SSH tunnel)
|
||||||
|
App\Infrastructure\AI\OllamaClientInterface:
|
||||||
|
alias: App\Infrastructure\AI\OllamaClient
|
||||||
|
|
||||||
|
# Mistral Cloud
|
||||||
|
App\Infrastructure\AI\OllamaClientInterface:
|
||||||
|
alias: App\Infrastructure\AI\MistralClient
|
||||||
|
```
|
||||||
|
|
||||||
|
Set models via env vars: `AI_TEXT_MODEL`, `AI_VISION_MODEL`. After switching: `docker compose exec app php bin/console cache:clear`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Admin Panel Features
|
||||||
|
|
||||||
|
- **Articles** — list (click row → detail view, Edit as action), ingest form with stock quantity, attribute validation with required-field highlighting
|
||||||
|
- **AI Pipeline** — per-job progress view with step tracking; failed jobs show the real error; re-run AI from article detail
|
||||||
|
- **Article Types** — configurable attribute schemas (name, type, unit, options, required flag); attributes drive specs-research field list
|
||||||
|
- **Users** — permission checkboxes per user (`ARTICLES_MANAGE`, `PIPELINE_RUN`, `ORDERS_MANAGE`, etc.)
|
||||||
|
- **Orders / Customers / Invoices** — full read/manage view
|
||||||
|
- **Prompt Templates** — editable via admin (DB-backed, `{{variable}}` substitution)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Article Ingest
|
||||||
|
|
||||||
|
Admin → **New Article** (or via API):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST https://ss3k.schaunwama.de/api/pipeline/photo-upload \
|
||||||
|
-H "X-Api-Key: <key>" \
|
||||||
|
-F "articleTypeId=<uuid>" \
|
||||||
|
-F "condition=good" \
|
||||||
|
-F "stock=1" \
|
||||||
|
-F "photo=@/path/to/photo.jpg"
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipeline: Photo → OllamaVision (model number) → **DB model cache check** → *(cache hit: copy + done)* / *(cache miss: Tavily search → JsonCoding → Validation → Draft → eBay text)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Keys
|
||||||
|
|
||||||
|
Generated via console only (raw key shown once):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec app php bin/console app:api-keys:create --env=prod
|
||||||
|
```
|
||||||
|
|
||||||
|
Use as `X-Api-Key: <rawKey>` header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Hexagonal (Domain / Application / Infrastructure). See design doc for full details.
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
Domain/ # Pure PHP — Article, ArticleType, Order, Customer, AIPipelineJob …
|
||||||
|
Application/ # Use cases, orchestration via interfaces
|
||||||
|
Infrastructure/
|
||||||
|
AI/ # OllamaClient, MistralClient, 4 AI agents
|
||||||
|
Channel/ # EbayAdapter, FrappeErpAdapter
|
||||||
|
Search/ # TavilyWebSearch
|
||||||
|
Persistence/ # Doctrine repositories (PostgreSQL)
|
||||||
|
Messenger/ # Message classes + handlers (3 queues: ai_pipeline, orders, channel_sync)
|
||||||
|
Http/ # Symfony controllers, EasyAdmin CRUD, webhooks
|
||||||
|
```
|
||||||
|
|
||||||
|
All plans (1–6) complete. Active development tracked via Claude Code sessions.
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# SuperSeller3000 — Design-Dokument
|
# SuperSeller3000 — Design-Dokument
|
||||||
|
|
||||||
**Datum:** 2026-05-13 · **Zuletzt aktualisiert:** 2026-05-17
|
**Datum:** 2026-05-13 · **Zuletzt aktualisiert:** 2026-05-18
|
||||||
**Status:** In Betrieb (ss3k.schaunwama.de)
|
**Status:** In Betrieb (ss3k.schaunwama.de)
|
||||||
**Kontext:** Middleware zur Artikelverwaltung, KI-gestützten Erfassung und Multi-Plattform-Verkauf von refurbished IT-Hardware.
|
**Kontext:** Middleware zur Artikelverwaltung, KI-gestützten Erfassung und Multi-Plattform-Verkauf von refurbished IT-Hardware.
|
||||||
|
|
||||||
|
|
@ -52,6 +52,7 @@ Jeder externe Dienst (eBay, Frappe ERP, Ollama, SMTP) implementiert ein Interfac
|
||||||
| Datenbank | PostgreSQL 17 (eine Instanz, drei Schemas: `app`, `logs`, `logs_archive`) |
|
| Datenbank | PostgreSQL 17 (eine Instanz, drei Schemas: `app`, `logs`, `logs_archive`) |
|
||||||
| Cache / Queue | Redis 7 |
|
| Cache / Queue | Redis 7 |
|
||||||
| AI | **Ollama** (lokal, SSH-Tunnel + autossh) **oder Mistral Cloud API** — per Alias in `services.yaml` umschaltbar; Modelle via `AI_TEXT_MODEL` / `AI_VISION_MODEL` env vars konfigurierbar |
|
| AI | **Ollama** (lokal, SSH-Tunnel + autossh) **oder Mistral Cloud API** — per Alias in `services.yaml` umschaltbar; Modelle via `AI_TEXT_MODEL` / `AI_VISION_MODEL` env vars konfigurierbar |
|
||||||
|
| Web-Suche | Tavily API (`TAVILY_API_KEY`) — liefert strukturierte Suchergebnisse für SpecsResearchAgent |
|
||||||
| Admin-Panel | EasyAdmin 5 (`#[AdminDashboard]` Attribut, `type: easyadmin.routes` Loader) |
|
| Admin-Panel | EasyAdmin 5 (`#[AdminDashboard]` Attribut, `type: easyadmin.routes` Loader) |
|
||||||
| Auth | Symfony Security + scheb/two-factor-bundle (TOTP) |
|
| Auth | Symfony Security + scheb/two-factor-bundle (TOTP) |
|
||||||
| API-Auth | API-Key (eigene Entity, bcrypt-Hash) + Symfony Voters; Keys via `app:api-keys:create` anlegen |
|
| API-Auth | API-Key (eigene Entity, bcrypt-Hash) + Symfony Voters; Keys via `app:api-keys:create` anlegen |
|
||||||
|
|
@ -122,6 +123,7 @@ Article
|
||||||
ebay_listing_id: string?
|
ebay_listing_id: string?
|
||||||
ebay_title: string? # AI-generiert, editierbar vor Freigabe
|
ebay_title: string? # AI-generiert, editierbar vor Freigabe
|
||||||
ebay_description: text? # AI-generiert, editierbar vor Freigabe
|
ebay_description: text? # AI-generiert, editierbar vor Freigabe
|
||||||
|
specs_text: text? # Freitext-Specs vom SpecsResearchAgent; wird als ERP-Rechnungsposition genutzt
|
||||||
→ AttributeValue[]
|
→ AttributeValue[]
|
||||||
→ ArticlePhoto[]
|
→ ArticlePhoto[]
|
||||||
|
|
||||||
|
|
@ -215,17 +217,20 @@ ApiKey
|
||||||
```
|
```
|
||||||
1. Foto-Upload → PhotoUploadMessage in Queue (redis://ai_pipeline)
|
1. Foto-Upload → PhotoUploadMessage in Queue (redis://ai_pipeline)
|
||||||
2. OllamaVisionAgent — LLaVA liest Typenschild
|
2. OllamaVisionAgent — LLaVA liest Typenschild
|
||||||
Output: Modellbezeichnung + Seriennummer (nur was sichtbar)
|
Output: Hersteller, Modellname, Modellnummer, Seriennummer
|
||||||
3. SpecsResearchAgent — Web-Suche mit Modellbezeichnung → vollständige Specs (Freitext)
|
3. Model-Cache-Check — findCompletedByModelNumber() in DB
|
||||||
Web-Suche ist Pflicht (kein reines LLM-Wissen — zu unzuverlässig für Hardware)
|
Treffer → copy ebayTitle/ebayDescription/specsText/attributes → Schritt 6 (kein AI mehr)
|
||||||
4. JsonCodingAgent — strukturierter Ollama-Call: Specs-Text → JSON gegen ArticleType-Schema
|
Kein Treffer → weiter mit Schritt 4
|
||||||
5. ValidationGate — alle Pflichtfelder gesetzt? (Schema + eBay-Kategorie-Pflichtfelder)
|
4. SpecsResearchAgent — Tavily-Suche mit Modellbezeichnung → vollständige Specs (Freitext)
|
||||||
✓ → Schritt 6
|
Pflichtfeld-Liste kommt aus ArticleType.AttributeDefinitions ({{fields}}-Platzhalter im Prompt)
|
||||||
✗ → Retry ab Schritt 4 mit missing_fields im Prompt (max. 3×)
|
5. JsonCodingAgent — strukturierter Ollama-Call: Specs-Text → JSON gegen ArticleType-Schema
|
||||||
|
6. ValidationGate — alle Pflichtfelder gesetzt? (Schema + eBay-Kategorie-Pflichtfelder)
|
||||||
|
✓ → Schritt 7
|
||||||
|
✗ → Retry ab Schritt 5 mit missing_fields im Prompt (max. 3×)
|
||||||
→ needs_review nach 3 Fehlversuchen
|
→ needs_review nach 3 Fehlversuchen
|
||||||
6. DraftArticleCreator — Article anlegen (status: draft), Inventurnummer vergeben
|
7. DraftArticleCreator — Article anlegen (status: draft), Inventurnummer vergeben
|
||||||
7. EbayTextAgent — Titel + Beschreibung aus Attributen generieren, am Artikel speichern
|
8. EbayTextAgent — Titel + Beschreibung aus Attributen generieren, am Artikel speichern
|
||||||
8. → Freigabe-Queue (manuell)
|
9. → Freigabe-Queue (manuell)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 Pipeline B — PXE-Inventur
|
### 4.2 Pipeline B — PXE-Inventur
|
||||||
|
|
@ -311,7 +316,7 @@ Kein Match → neuer Kunde anlegen.
|
||||||
|
|
||||||
**Regel:** `lowercase` ist die einzige Toleranz. "Kirchstr 1" ≠ "Kirchstraße 1" → zwei Kunden. Datenmischung ist schlimmer als ein Duplikat. Manuelle Zusammenführung folgt als spätere Admin-Funktion.
|
**Regel:** `lowercase` ist die einzige Toleranz. "Kirchstr 1" ≠ "Kirchstraße 1" → zwei Kunden. Datenmischung ist schlimmer als ein Duplikat. Manuelle Zusammenführung folgt als spätere Admin-Funktion.
|
||||||
|
|
||||||
**Neu-Anlage:** Frappe-ERP-Kunde erstellen mit Custom Fields (`superseller_customer_id`, `ebay_user_id`) → `frappe_customer_id` in Middleware zurückschreiben.
|
**Neu-Anlage:** `findExistingCustomer()` prüft zuerst Frappe ERP nach Name + Adresse (zweistufig: Name-GET + Address-GET). Treffer → bestehende Frappe-ID verwenden. Kein Treffer → Frappe-ERP-Kunde anlegen mit Custom Fields (`superseller_customer_id`, `ebay_user_id`) → `frappe_customer_id` in Middleware zurückschreiben.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -410,9 +415,19 @@ Passwort ändern: `/account/password`
|
||||||
docker compose exec app php bin/console app:users:create --env=prod
|
docker compose exec app php bin/console app:users:create --env=prod
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Artikel-Navigation:** Klick auf eine Zeile in der Liste → **Detail-Ansicht** (read-only). Edit-Button in der Zeile und auf der Detail-Seite führt zum Formular.
|
||||||
|
|
||||||
**Artikel-Typen & Merkmale:**
|
**Artikel-Typen & Merkmale:**
|
||||||
1. Admin → **Attributes** → neues Merkmal anlegen (Name, Typ, Einheit, Optionen)
|
1. Admin → **Attributes** → neues Merkmal anlegen (Name, Typ, Einheit, Optionen, Pflichtfeld ja/nein)
|
||||||
2. Admin → **Article Types** → neuen Typ anlegen → Merkmale per Autocomplete zuweisen
|
2. Admin → **Article Types** → neuen Typ anlegen → Merkmale per Autocomplete zuweisen
|
||||||
|
3. Pflichtmerkmale werden im Artikel-Formular mit rotem * markiert und per Browser-Validierung erzwungen.
|
||||||
|
|
||||||
|
**User-Berechtigungen:**
|
||||||
|
Admin → **Users** → Edit → Bereich „Permissions" — Checkboxen pro Berechtigung:
|
||||||
|
`ARTICLES_MANAGE`, `PIPELINE_RUN`, `ORDERS_MANAGE`, `USERS_MANAGE`, `PROMPTS_MANAGE`, `SETTINGS_MANAGE`
|
||||||
|
|
||||||
|
**Manuelle Erfassung über Admin:**
|
||||||
|
Admin → **New Article** → Artikel-Typ, Zustand, Anzahl (stock), Foto wählen → Pipeline startet automatisch.
|
||||||
|
|
||||||
### 11.2 API-Keys
|
### 11.2 API-Keys
|
||||||
|
|
||||||
|
|
@ -473,7 +488,7 @@ curl https://ss3k.schaunwama.de/api/pipeline/jobs/<jobId> \
|
||||||
|
|
||||||
- Eigener Versand (Versandlabel-Generierung, weiterer Workflow-Schritt)
|
- Eigener Versand (Versandlabel-Generierung, weiterer Workflow-Schritt)
|
||||||
- Weitere Verkaufsplattformen (Amazon, Kaufland) — nur neuer Channel-Adapter nötig
|
- Weitere Verkaufsplattformen (Amazon, Kaufland) — nur neuer Channel-Adapter nötig
|
||||||
- SpecsResearchAgent: konkrete Web-Such-Strategie definieren (SerpAPI, direkte Hersteller-Seiten, o.ä.)
|
|
||||||
- Manuelle Kunden-Zusammenführung als Admin-Funktion
|
- Manuelle Kunden-Zusammenführung als Admin-Funktion
|
||||||
- eBay-Promotions / Preisanpassungen
|
- eBay-Promotions / Preisanpassungen
|
||||||
- `ebay_title` / `ebay_description` sind aktuell eBay-spezifisch auf `Article` — bei weiteren Plattformen zu `platform_texts: jsonb` generalisieren
|
- `ebay_title` / `ebay_description` sind aktuell eBay-spezifisch auf `Article` — bei weiteren Plattformen zu `platform_texts: jsonb` generalisieren
|
||||||
|
- PXE-Pipeline: `JsonCodingHandler` überspringt SpecsResearch — vollständig implementiert aber noch nicht produktiv genutzt
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue