diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2222bc --- /dev/null +++ b/README.md @@ -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: " \ + -F "articleTypeId=" \ + -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: ` 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. diff --git a/docs/superpowers/specs/2026-05-13-superseller3000-design.md b/docs/superpowers/specs/2026-05-13-superseller3000-design.md index 9242900..2be931d 100644 --- a/docs/superpowers/specs/2026-05-13-superseller3000-design.md +++ b/docs/superpowers/specs/2026-05-13-superseller3000-design.md @@ -1,6 +1,6 @@ # 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) **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`) | | 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 | +| Web-Suche | Tavily API (`TAVILY_API_KEY`) — liefert strukturierte Suchergebnisse für SpecsResearchAgent | | Admin-Panel | EasyAdmin 5 (`#[AdminDashboard]` Attribut, `type: easyadmin.routes` Loader) | | 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 | @@ -122,6 +123,7 @@ Article ebay_listing_id: string? ebay_title: string? # 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[] → ArticlePhoto[] @@ -215,17 +217,20 @@ ApiKey ``` 1. Foto-Upload → PhotoUploadMessage in Queue (redis://ai_pipeline) 2. OllamaVisionAgent — LLaVA liest Typenschild - Output: Modellbezeichnung + Seriennummer (nur was sichtbar) -3. SpecsResearchAgent — Web-Suche mit Modellbezeichnung → vollständige Specs (Freitext) - Web-Suche ist Pflicht (kein reines LLM-Wissen — zu unzuverlässig für Hardware) -4. JsonCodingAgent — strukturierter Ollama-Call: Specs-Text → JSON gegen ArticleType-Schema -5. ValidationGate — alle Pflichtfelder gesetzt? (Schema + eBay-Kategorie-Pflichtfelder) - ✓ → Schritt 6 - ✗ → Retry ab Schritt 4 mit missing_fields im Prompt (max. 3×) + Output: Hersteller, Modellname, Modellnummer, Seriennummer +3. Model-Cache-Check — findCompletedByModelNumber() in DB + Treffer → copy ebayTitle/ebayDescription/specsText/attributes → Schritt 6 (kein AI mehr) + Kein Treffer → weiter mit Schritt 4 +4. SpecsResearchAgent — Tavily-Suche mit Modellbezeichnung → vollständige Specs (Freitext) + Pflichtfeld-Liste kommt aus ArticleType.AttributeDefinitions ({{fields}}-Platzhalter im Prompt) +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 -6. DraftArticleCreator — Article anlegen (status: draft), Inventurnummer vergeben -7. EbayTextAgent — Titel + Beschreibung aus Attributen generieren, am Artikel speichern -8. → Freigabe-Queue (manuell) +7. DraftArticleCreator — Article anlegen (status: draft), Inventurnummer vergeben +8. EbayTextAgent — Titel + Beschreibung aus Attributen generieren, am Artikel speichern +9. → Freigabe-Queue (manuell) ``` ### 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. -**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 ``` +**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:** -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 +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 @@ -473,7 +488,7 @@ curl https://ss3k.schaunwama.de/api/pipeline/jobs/ \ - Eigener Versand (Versandlabel-Generierung, weiterer Workflow-Schritt) - 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 - eBay-Promotions / Preisanpassungen - `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