|||> Status: APPROVED by QA + CPTO (2026-07-03) — Ready for Implementation
|||> Version: 0.8-CPTO
|> CTO Sign-off: 2026-07-03 — Technology Stack, Cloud Architecture, Data Model vollständig und genehmigt
|> CPTO Sign-off: 2026-07-03 — Full PRD reviewed and approved
|> Erstellt: 2026-07-03
|> Owner: Product Manager
|> Issue: PRD-001
|> QA Review: 2026-07-03 — 5 Critical Issues, 6 Partial Issues — PM-Fixes in v0.4 adressiert
|> Verbleibend: CI-003 (CTO: Tech Stack/Cloud/Data Model)
Das PRD v0.6 ist abnahmefähig. Alle vorherigen Critical Issues sind adressiert.
| ID | Section | Letzter Stand |
|---|---|---|
| CI-001 | Vision & Mission | ✅ FIXED (v0.3) |
| CI-002 | Pain Points | ✅ FIXED (v0.3) |
| CI-003 | Tech Stack / Cloud / Data Model | ✅ CTO-APPROVED (v0.5) |
| CI-004 | Core Workflows | ✅ FIXED (v0.3) |
| CI-005 | Attributes Priority | ✅ FIXED (v0.4) |
| ID | Issue | Empfehlung |
|---|---|---|
| PI-001 | 2 Personas vorhanden, aber unvalidiert | Vor Beta-Start mit echten Nutzern validieren |
| PI-002 | Keine Gherkin Akzeptanzkriterien | Vor Implementierung nachziehen (Issue an DEV) |
| PI-003 | Store-spezifische Konfiguration partial | Vor Phase-2 finalisieren |
| PI-004 | API-Endpunkte nicht detailliert | API-Spezifikation als separates Issue (Implementierung) |
| PI-005 | Mitigation-Massnahmen | ✅ FIXED |
| PI-006 | Einige Offene Fragen noch "offen" | MVP-Deadline + Beta-Kunden als prioritär klären |
Verdict: ✅ APPROVED — PRD ist bereit für CPTO-Review und Implementierungsstart.
Cartly befähigt kleine Fashion-Einzelhändler, ihre Filialen effizient zu führen — ohne Enterprise-Komplexität. In 5 Jahren ist Cartly die Standardlösung für 1–20-Mitarbeiter-Fashion-Businesses im DACH-Raum.
Kleine Retailer verdienen dieselbe Technologie wie große Ketten. Wir bauen eine Plattform, die einfach funktioniert: kein Consulting, keine Implementierungsprojekte, keine monatelange Einführung. Anmelden, verkaufen, wachsen.
Aktuelle Situation:
Konsequenz:
Cartly: Eine integrierte ERP+POS SaaS-Plattform speziell für Fashion Retailer.
Für kleinere Fashion-Einzelhändler, die eine einfache, cloud-basierte Lösung für Verkauf und Verwaltung suchen, ist Cartly eine All-in-One SaaS-Plattform, die POS und ERP vereint — ohne die Komplexität etablierter Enterprise-Systeme.
|| User Type | Role | Hauptaufgaben |
|-----------|------|---------------|
|| Owner/Admin | Entscheidungsträger | Einrichtung, Reporting, Kostenkontrolle |
|| Store Manager | Tagesgeschäft | Verkauf, Bestellung, Inventur |
|| Sales Associate | Verkauf | Kassieren, Kundenberatung |
| Feld | Beschreibung |
|---|---|
| Name | Sarah, 34 |
| Rolle | Store Managerin — führt den Laden allein, 2 Teilzeitkräfte |
| Tagesablauf | Öffnet Laden → verkauft → macht Kasse → bestellt nach → macht Inventur |
| Tech-Skills | WhatsApp, Instagram, Online-Banking. Kein ERP, kein POS-Training. |
| Pain Points | "Ich verliere den Überblick, was noch da ist. Am liebsten würde ich abends in einer App sehen, was heute war." |
| Zitat | "Ich will nicht stundenlang am PC sitzen. Das muss so einfach sein wie mein Handy." |
| Ziel mit Cartly | 5 Minuten morgens, um zu sehen was los ist. schnell kassieren, schnell nachbestellen. |
| Feld | Beschreibung |
|---|---|
| Name | Marcus, 48 |
| Rolle | Inhaber — 2 Filialen, überlegt eine dritte |
| Background | BWL, hat bisher mit Excel und einer alten Kassensoftware gearbeitet |
| Pain Points | "Ich sehe erst am Monatsende, wie es läuft. Und meine Mitarbeiterin muss alles zweimal eingeben." |
| Zitat | "Ich will von überall sehen können, wie meine Läden laufen — ohne jemanden fragen zu müssen." |
| Ziel mit Cartly | Echtzeit-Dashboard über alle Filialen. Kein Medienbruch mehr. Korrekte Zahlen auf Knopfdruck. |
| ID | Requirement | Priority | Notes |
|---|---|---|---|
| FA-001 | Email/Password Login | Must | |
| FA-002 | Password Reset Flow | Must | |
| FA-003 | Session Management | Must | JWT-based |
| FA-004 | Role-Based Access Control | Must | Admin/Manager/Sales |
| ID | Requirement | Priority | Notes |
|---|---|---|---|
| PM-001 | Company anlegen & bearbeiten | Must | |
| PM-002 | Stores anlegen (1-n) | Must | |
| PM-003 | User Management (CRUD) | Must | |
| PM-004 | Rechte/Rollen zuweisen | Must | |
| PM-005 | Stammdaten: Products | Must | |
| PM-006 | Stammdaten: Categories | Must | |
| PM-007 | Stammdaten: Attributes (Size, Color, Season) | Must | Fashion-spezifisch — ohne Größen/Farben kein Fashion-Sortiment möglich |
| Layer | Technology | Rationale |
|---|---|---|
| Frontend | React 18 + TypeScript + Vite | TypeScript从头强Typisierung, Vite = schnell Builds, exzellentes PWA-Ökosystem (Workbox) |
| UI-Komponenten | Radix UI + Tailwind CSS | Accessible Components, Customizable, Fashion-Retail Brand-Ready |
| State Management | Zustand + TanStack Query | Leichtgewichtig, TypeScript-nativ, Server State via TanStack |
| Backend | Node.js + Fastify + TypeScript | Schnellster HTTP-Layer (Benchmark-Sieger), TypeScript-nativ, Plugin-Ökosystem |
| ORM | Prisma | Type-safe, Auto-Migrations, Multi-Tenant via tenant_id,.excellente DX |
| Database | PostgreSQL 16 (Supabase / Neon) | Zuverlässig, JSONB für flexible Schemata, Row-Level Security für Multi-Tenant |
| Cache / Session | Redis (Upstash) | Serverless-kompatibel, niedrige Latenz,Pub/Sub für Real-time |
| Auth | JWT (Access + Refresh) + HTTP-only Cookies | Bewährt, keine Server-Side Session nötig, GDPR-konform |
| File Storage | S3 (Backblaze B2 als Backup) | Günstig, skalierbar, CDN-fertig |
| Search | Meilisearch | Open Source, schnell, typo-tolerant, Fashion-spezifisch (Facets für Größe/Farbe) |
| AI | OpenRouter (einheitlicher Proxy) | Provider-agnostisch, Kostenkontrolle, Single-Endpoint |
| Infrastructure | Docker + Railway / Render (MVP) → EKS (Scale) | Railway = schnell deployed, keine AWS-Komplexität für MVP; Migration zu Kubernetes bei Scale |
| Monitoring | Sentry (Errors) + Highlight.io (Sessions) | Error Tracking + Session Replay für schnellere Debugging |
| CI/CD | GitHub Actions | Integriert, kostenlos für Open Source-Repos, Actions-Ökosystem |
Warum NICHT andere Optionen:
┌─────────────────────────────────────────────────────────────┐
│ AWS / Cloudflare │
│ CloudFront CDN ──► S3 (Static Assets) + Route 53 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐ HTTPS/TLS ┌─────────────────────────────────────────────┐
│ Browser (PWA) │◄──────────────────► │ Application Load Balancer │
│ │ └─────────────────────────────────────────────┘
└──────────────────┘ │ │
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────┐
│ API Server (Fastify) │ │ Web Server (Static) │
│ Container: Node.js │ │ Container: Nginx │
│ - JWT Auth Middleware │ │ - SPA serving │
│ - Rate Limiting │ │ - Asset caching │
│ - Tenant Routing │ └─────────────────────────┘
│ - AI Proxy (OpenRouter) │
└─────────────────────────────┘
│ │ │
┌────────────┘ │ └────────────┐
▼ ▼ ▼
┌──────────────────────┐ ┌────────────────┐ ┌────────────────────────┐
│ PostgreSQL 16 │ │ Redis │ │ S3 / Backblaze B2 │
│ - Row-Level Sec. │ │ - Sessions │ │ - Product Images │
│ - tenant_id FKs │ │ - Cache │ │ - Exports │
│ - pgcrypto │ │ - Pub/Sub │ │ - Backups │
└──────────────────────┘ └────────────────┘ └────────────────────────┘
│
▼
┌──────────────────────┐
│ Meilisearch │
│ - Product Search │
│ - Typo-tolerant │
└──────────────────────┘
Multi-Tenant Strategie:
tenant_id als Primary Key in ALLEN Tabellentenant:{id}: Key-PrefixInfrastructure als Code:
Security:
┌─────────────────────────────────────────────────────────────────────────────┐
│ CORE ENTITIES │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Company │──────►│ Store │──────►│ User │
│──────────────│ 1:n │──────────────│ 1:n │──────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ name │ │ company_id(FK)│ │ store_id (FK) │
│ settings │ │ name │ │ email │
│ theme │ │ address │ │ password_hash│
│ tier │ │ timezone │ │ role │
│ created_at │ │ currency │ │ created_at │
└──────────────┘ │ is_active │ └──────────────┘
└──────────────┘ │
│ n:1
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Category │◄──────│ Product │──────►│ SKU │
│──────────────│ 1:n │──────────────│ 1:n │──────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ company_id(FK)│ │ company_id(FK)│ │ product_id(FK)│
│ parent_id(FK) │ │ category_id │ │ sku_code │
│ name │ │ name │ │ price │
│ slug │ │ description │ │ cost_price │
│ sort_order │ │ images[] │ │ stock_qty │
│ is_active │ │ attributes{} │ │ size │
└──────────────┘ │ is_active │ │ color │
└──────────────┘ │ barcode │
└──────────────┘
│
│ 1:n
▼
┌──────────────┐
│ SaleItem │
│──────────────│
│ id (PK) │
│ sale_id (FK) │
│ sku_id (FK) │
│ qty │
│ unit_price │
│ discount │
└──────────────┘
┌──────────────┐ ┌──────────────┐
│ Sale │──────►│ Customer │
│──────────────│ n:1 │──────────────│
│ id (PK) │ │ id (PK) │
│ store_id(FK) │ │ company_id │
│ user_id(FK) │ │ email │
│ customer_id │ │ name │
│ total │ │ phone │
│ tax │ │ created_at │
│ payment_method│ │ preferences{}│
│ status │ └──────────────┘
│ created_at │
└──────────────┘
Key Design Decisions:
tenant_id (company_id) ist in ALLEN Tabellen — Multi-Tenant via PostgreSQL Row-Level SecurityUUID v7 als Primary Keys — zeitlich sortierbar, keine Information Leakageattributes{} als JSONB für Fashion-spezifische Felder (Saison, Kollektion, Material)settings{} als JSONB für flexible Company/Store-Konfigurationdeleted_at — keine harten Deletes für Audit-Trailcreated_at, updated_at auf allen EntitätenDiese Leitlinien sind für ALLE Entwicklungsarbeit bindend:
theme.primaryColor = #...
theme.secondaryColor = #...
theme.accentColor = #...
theme.fontFamily = ...
theme.logoUrl = ...
product.name = "Cartly" (konfigurierbar)
company.name = "..." (konfigurierbar)
/api/v1/, /api/v2/| Feature | Good | Better | Best |
|---|---|---|---|
| API Calls/Monat | 100 | 1.000 | 10.000+ |
| Stores | 1 | 3 | unlimited |
| User | 2 | 10 | unlimited |
| Reports | Basic | Advanced | Full |
| Support | Email + Chat | Dedicated | |
| Custom Theme | ❌ | ✅ | ✅ |
| Custom Domain | ❌ | ❌ | ✅ |
| Beta Features | ❌ | ✅ | ✅ |
| SLA | 99% | 99.5% | 99.9% |
features:
- id: advanced_analytics
tier: better
limits:
api_calls_per_month: 1000
max_users: 10
beta: false
- id: pos_offline_mode
tier: best
limits:
api_calls_per_month: 10000
beta: true
Sign Up → Email Verification → Company Setup → First Store → Invite Team → Dashboard
1. Sales Associate meldet sich am Tablet/Web-App an
2. Produkt aus Katalog wählen (Suchbegriff, Barcode-Scan, Kategorie-Browse)
3. Warenkorb wird geführt — Positionen können entfernt/angepasst werden
4. Zahlung erfassen (Bar, Kartenzahlung, Split-Payment)
5. Beleg wird erzeugt (digital + Druck)
6. Bestand wird in Echtzeit reduziert
7. Transaktion wird im Backend gebucht
1. Admin/Manager öffnet Produktverwaltung
2. Produktdetails erfassen: Name, SKU, Preis, Kategorie, Attribute (Größe/Farbe/Saison)
3. Bestand initial setzen (pro Filiale)
4. Produkt wird im Katalog sichtbar
5. optional: Produktfoto hochladen
1. Manager öffnet Bestellmaske
2. Produkte + Mengen auswählen (Bestand prüfen)
3. Bestellung absenden → an Zentrallager/Supplier
4. Wareneingang wird im System gebucht
5. Bestand wird aktualisiert
1. Owner/Manager öffnet Dashboard
2. Zeitraum wählen (Tag, Woche, Monat,自定义)
3. Kennzahlen sehen: Umsatz, Anzahl Transaktionen, Ø Warenkorbwert, Top-Produkte
| Priority | Features |
|---|---|
| Must | Auth, RBAC, Company Setup, Store Setup, User Management, Product Stammdaten |
| Should | Dashboard, Basic Reporting, Category Management |
| Could | Notifications, User Preferences |
| Won't (Phase 1) | CRM, Supplier Management, Advanced Analytics |
|| ID | Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|----|-------|-------------------|--------|------------|
| TR-001 | Multi-Tenant Performance — viele Kunden auf Shared Infrastructure | Mittel | Hoch | PostgreSQL Row-Level Security + Connection Pooling (PgBouncer); Performance-Tests mit repräsentativen Lastprofilen |
| TR-002 | Payment Integration Komplexität | Hoch | Hoch | Stripe als Primary Payment Processor (einfache API, gute Doku); Andere Provider erst in späteren Phasen |
| TR-003 | TSE-Integration (Deutschland) | Mittel | Hoch | Cloud-basierte TSE (fiskaltray.cloud);kein lokales Hardware-Dependency;_frühzeitige Einbindung eines Steuerberaters |
| TR-004 | Beta-Kunden finden sich nicht | Mittel | Hoch | Chief of Staff.startet Outreach in Netzwerk; Early-Adopter-Anreiz (3 Monate gratis) |
| TR-005 | Scope Creep durch Kundenfeedback | Hoch | Mittel | MoSCoW-Priorisierung enforces; keine Features außerhalb Phase 1 ohne CPTO-Entscheidung |
| Frage | Owner | Status | Deadline | |
|---|---|---|---|---|
| MVP-Deadline? | Chief of Staff / CPTO | offen | 2026-07-10 | |
| Erste Beta-Kunden? | Chief of Staff | offen | 2026-07-15 | |
| Payment-Integration MUST-HAVE? | CPTO | offen | 2026-07-10 | |
| Accounting-Integration MUST-HAVE? | CPTO | offen | 2026-07-10 | |
| CI/CD Toolchain gewählt? | CTO | offen | 2026-07-15 | |
| Hosting-Provider gewählt? | CTO | offen | 2026-07-15 |
| Term | Definition |
|---|---|
| POS | Point of Sale — Kassensystem |
| ERP | Enterprise Resource Planning — Unternehmenssoftware |
| Stammdaten | Master Data — zentrale Referenzdaten (Products, Customers, etc.) |
| RBAC | Role-Based Access Control |
| Multi-Tenant | Eine Instanz bedient mehrere Kunden |
Diese Szenarien definieren die Abnahmebedingungen für Phase-1 Must-Have Features.
Jedes Szenario folgt dem Given-When-Then Muster und wird als automatisierter Test implementiert.
Feature: Benutzer-Registrierung
Scenario: Erfolgreiche Registrierung eines neuen Benutzers
Given der Benutzer sich nicht im System befindet
And die Company "Fashion Store GmbH" existiert
When der Benutzer sich mit Email "sarah@example.com" und Passwort "SecurePass123!" registriert
Then wird ein neues User-Konto erstellt
And die Email-Adresse "sarah@example.com" ist verifiziert
And der User hat die Rolle "Admin" innerhalb der Company
And der Benutzer erhält eine JWT mit Gültigkeit von maximal 15 Minuten
Scenario: Registrierung mit invalider Email-Adresse schlägt fehl
Given der Benutzer sich nicht im System befindet
When der Benutzer sich mit Email "invalid-email" und Passwort "SecurePass123!" registriert
Then wird eine Fehlermeldung zurückgegeben: "Ungültige Email-Adresse"
And kein User-Konto wird erstellt
Scenario: Registrierung mit zu kurzem Passwort schlägt fehl
Given der Benutzer sich nicht im System befindet
When der Benutzer sich mit Email "sarah@example.com" und Passwort "123" registriert
Then wird eine Fehlermeldung zurückgegeben: "Passwort muss mindestens 8 Zeichen haben"
And kein User-Konto wird erstellt
Scenario: Bereits registrierte Email kann nicht erneut registriert werden
Given der Benutzer mit Email "sarah@example.com" existiert bereits
When der Benutzer sich mit Email "sarah@example.com" und Passwort "SecurePass123!" registriert
Then wird eine Fehlermeldung zurückgegeben: "Email-Adresse bereits registriert"
And kein zweites User-Konto wird erstellt
Feature: Benutzer-Login
Scenario: Erfolgreicher Login mit gültigen Credentials
Given der User "sarah@example.com" existiert mit Passwort-Hash
And der User ist nicht aktuell eingeloggt
When der Benutzer sich mit Email "sarah@example.com" und Passwort "SecurePass123!" einloggt
Then wird ein Access-Token (JWT) zurückgegeben
And ein Refresh-Token wird als HTTP-only Cookie gesetzt
And die Session ist 24 Stunden gültig
Scenario: Login mit falschem Passwort scheitert
Given der User "sarah@example.com" existiert mit Passwort-Hash
When der Benutzer sich mit Email "sarah@example.com" und Passwort "WrongPassword!" einloggt
Then wird HTTP 401 zurückgegeben
And die Fehlermeldung lautet: "Ungültige Anmeldedaten"
And kein Access-Token wird ausgestellt
Scenario: Login mit unbekannter Email scheitert
Given kein User mit Email "unknown@example.com" existiert
When der Benutzer sich mit Email "unknown@example.com" und Passwort "AnyPass123!" einloggt
Then wird HTTP 401 zurückgegeben
And die Fehlermeldung lautet: "Ungültige Anmeldedaten"
Scenario: Password Reset Email wird gesendet
Given der User "sarah@example.com" existiert
When der Benutzer einen Password-Reset für "sarah@example.com" anfordert
Then wird eine Email mit Reset-Link an "sarah@example.com" gesendet
And der Reset-Token ist 1 Stunde gültig
And der Reset-Token kann nur einmal verwendet werden
Scenario: Password Reset mit abgelaufenem Token scheitert
Given ein abgelaufener Reset-Token existiert für "sarah@example.com"
When der Benutzer einen neues Passwort "NewSecure123!" mit dem abgelaufenen Token setzt
Then wird HTTP 400 zurückgegeben
And die Fehlermeldung lautet: "Reset-Token ist abgelaufen oder ungültig"
Scenario: API-Zugriff mit abgelaufenem Access-Token wird abgelehnt
Given der Benutzer hat einen abgelaufenen Access-Token
When der Benutzer eine API-Anfrage mit diesem Token stellt
Then wird HTTP 401 zurückgegeben
And die Fehlermeldung lautet: "Token abgelaufen"
Scenario: Refresh Token wird bei Login ausgetauscht (Rotation)
Given der Benutzer hat einen gültigen Refresh-Token
When der Benutzer sich mit Email und Passwort einloggt
Then wird ein neuer Refresh-Token ausgestellt
And der alte Refresh-Token wird invalidiert
And der alte Refresh-Token kann nicht mehr verwendet werden
Feature: RBAC — Admin Zugriff
Scenario: Admin sieht alle Stores der Company
Given der User "admin@store.com" hat die Rolle "Admin"
And die Company hat 3 Stores: "Store Berlin", "Store Hamburg", "Store München"
When der Admin eine GET-Anfrage auf "/api/v1/stores" stellt
Then werden alle 3 Stores zurückgegeben
Scenario: Admin kann einen neuen User erstellen
Given der User "admin@store.com" hat die Rolle "Admin"
When der Admin einen POST auf "/api/v1/users" mit Email "newuser@store.com" stellt
Then wird HTTP 201 zurückgegeben
And der neue User "newuser@store.com" existiert
Feature: RBAC — Manager Zugriff
Scenario: Manager sieht nur seinen zugewiesenen Store
Given der User "manager@store.com" hat die Rolle "Manager"
And der Manager ist dem Store "Store Berlin" zugewiesen
And die Company hat weitere Stores: "Store Hamburg", "Store München"
When der Manager eine GET-Anfrage auf "/api/v1/stores" stellt
Then wird nur "Store Berlin" zurückgegeben
And "Store Hamburg" und "Store München" sind nicht in der Antwort
Scenario: Manager kann keine User außerhalb seines Stores anlegen
Given der User "manager@store.com" hat die Rolle "Manager"
And der Manager ist dem Store "Store Berlin" zugewiesen
When der Manager einen POST auf "/api/v1/users" mit Email "other@store.com" und Store "Store Hamburg" stellt
Then wird HTTP 403 zurückgegeben
And die Fehlermeldung lautet: "Zugriff verweigert: Store nicht zugewiesen"
Feature: RBAC — Sales Zugriff
Scenario: Sales Associate wird am User-Management Endpunkt blockiert
Given der User "sales@store.com" hat die Rolle "Sales"
When der Sales Associate eine GET-Anfrage auf "/api/v1/users" stellt
Then wird HTTP 403 zurückgegeben
And die Fehlermeldung lautet: "Unzureichende Berechtigungen"
Scenario: Sales Associate kann einen Verkauf abschließen
Given der User "sales@store.com" hat die Rolle "Sales"
And der Sales Associate ist dem Store "Store Berlin" zugewiesen
And Produkt "T-Shirt Basic" mit SKU "TS-001" existiert im Store
And Bestand von SKU "TS-001" ist 10
When der Sales Associate einen POST auf "/api/v1/sales" mit SKU "TS-001" und Menge 2 stellt
Then wird HTTP 201 zurückgegeben
And der Verkauf wird in der Datenbank gespeichert
And Bestand von SKU "TS-001" wird auf 8 reduziert
Scenario: User kann nicht auf Daten einer anderen Company zugreifen
Given User "user@companyA.com" gehört zu Company "CompanyA"
And User ist nicht Mitglied von Company "CompanyB"
When der User "user@companyA.com" eine GET-Anfrage auf "/api/v1/companies/CompanyB/stores" stellt
Then wird HTTP 403 zurückgegeben
And die Fehlermeldung lautet: "Zugriff verweigert"
Scenario: User kann eigene Rolle nicht selbst ändern
Given der User "sales@store.com" hat die Rolle "Sales"
When der Sales Associate einen PUT auf "/api/v1/users/me" mit neuer Rolle "Admin" stellt
Then wird HTTP 403 zurückgegeben
And die Rolle bleibt "Sales"
Feature: Company-Verwaltung
Scenario: Admin legt neue Company an
Given der User "owner@newstore.com" ist authentifiziert
When der User eine POST-Anfrage auf "/api/v1/companies" mit folgendem Body stellt:
"""
{
"name": "Fashion Store GmbH",
"tier": "good",
"timezone": "Europe/Berlin",
"currency": "EUR"
}
"""
Then wird HTTP 201 zurückgegeben
And die Company "Fashion Store GmbH" existiert in der Datenbank
And der User "owner@newstore.com" ist Admin dieser Company
Scenario: Doppelter Company-Name wird abgelehnt
Given die Company "Fashion Store GmbH" existiert bereits
When ein User eine POST-Anfrage auf "/api/v1/companies" mit Name "Fashion Store GmbH" stellt
Then wird HTTP 409 zurückgegeben
And die Fehlermeldung lautet: "Company-Name bereits vergeben"
Feature: Store-Verwaltung
Scenario: Admin legt neuen Store an
Given die Company "Fashion Store GmbH" existiert
And der User "admin@store.com" ist Admin der Company
When der Admin eine POST-Anfrage auf "/api/v1/companies/{companyId}/stores" mit folgendem Body stellt:
"""
{
"name": "Store Berlin",
"address": "Alexanderplatz 1, 10178 Berlin",
"timezone": "Europe/Berlin",
"currency": "EUR"
}
"""
Then wird HTTP 201 zurückgegeben
And der Store "Store Berlin" existiert
And der Store ist der Company "Fashion Store GmbH" zugeordnet
Scenario: Store-Name muss innerhalb der Company eindeutig sein
Given die Company "Fashion Store GmbH" hat bereits einen Store "Store Berlin"
When der Admin einen neuen Store "Store Berlin" für dieselbe Company anlegt
Then wird HTTP 409 zurückgegeben
And die Fehlermeldung lautet: "Store-Name bereits vorhanden"
Scenario: Admin kann Company-Theme setzen
Given die Company "Fashion Store GmbH" existiert
And der User "admin@store.com" ist Admin der Company
When der Admin eine PATCH-Anfrage auf "/api/v1/companies/{companyId}" mit folgendem Body stellt:
"""
{
"theme": {
"primaryColor": "#1A73E8",
"secondaryColor": "#F8F9FA",
"accentColor": "#FF6D00",
"fontFamily": "Inter"
}
}
"""
Then wird HTTP 200 zurückgegeben
And das Theme ist in der Company gespeichert
Feature: User Management
Scenario: Admin lädt neuen User per Email ein
Given der User "admin@store.com" hat die Rolle "Admin"
When der Admin eine POST-Anfrage auf "/api/v1/users/invite" mit Email "newuser@store.com" und Rolle "Manager" stellt
Then wird HTTP 201 zurückgegeben
And eine Einladungs-Email wird an "newuser@store.com" gesendet
And der User hat initial die Rolle "Manager"
Scenario: Admin ändert User-Rolle von Sales zu Manager
Given der User "sales@store.com" hat die Rolle "Sales"
And der User "admin@store.com" hat die Rolle "Admin"
When der Admin eine PATCH-Anfrage auf "/api/v1/users/{salesUserId}" mit neuer Rolle "Manager" stellt
Then wird HTTP 200 zurückgegeben
And der User "sales@store.com" hat jetzt die Rolle "Manager"
Scenario: Admin deaktiviert User-Konto
Given der User "oldemployee@store.com" existiert mit Status "active"
And der User "admin@store.com" hat die Rolle "Admin"
When der Admin eine DELETE-Anfrage auf "/api/v1/users/{oldUserId}" stellt
Then wird HTTP 200 zurückgegeben
And der User hat Status "inactive"
And der User existiert weiterhin in der Datenbank (Soft Delete)
And der User kann sich nicht mehr einloggen
Scenario: Admin sieht nur aktive User in der User-Liste
Given die Company hat 5 aktive und 2 inaktive User
When der Admin eine GET-Anfrage auf "/api/v1/users" stellt
Then werden nur die 5 aktiven User zurückgegeben
And inaktive User sind nicht in der Liste
Feature: Produkt-Stammdaten
Scenario: Admin legt neues Produkt an
Given die Company "Fashion Store GmbH" existiert
And die Kategorie "T-Shirts" existiert
And der User "admin@store.com" hat die Rolle "Admin"
When der Admin eine POST-Anfrage auf "/api/v1/products" mit folgendem Body stellt:
"""
{
"name": "Basic T-Shirt",
"sku": "TS-BASIC-001",
"categoryId": "{categoryId}",
"price": 29.99,
"costPrice": 12.00,
"attributes": {
"size": ["S", "M", "L", "XL"],
"color": ["Schwarz", "Weiß"],
"season": "Sommer 2026"
}
}
"""
Then wird HTTP 201 zurückgegeben
And das Produkt "Basic T-Shirt" existiert mit SKU "TS-BASIC-001"
Scenario: Produkt mit duplicater SKU wird abgelehnt
Given das Produkt mit SKU "TS-BASIC-001" existiert bereits
When der Admin ein neues Produkt mit SKU "TS-BASIC-001" anlegt
Then wird HTTP 409 zurückgegeben
And die Fehlermeldung lautet: "SKU bereits vorhanden"
Scenario: Produkt ohne Pflichtfelder wird abgelehnt
Given der User "admin@store.com" hat die Rolle "Admin"
When der Admin eine POST-Anfrage auf "/api/v1/products" mit unvollständigen Daten stellt:
"""
{
"name": "Basic T-Shirt"
}
"""
Then wird HTTP 400 zurückgegeben
And die Fehlermeldung listet die fehlenden Pflichtfelder auf
Scenario: Bestand eines Produkts pro Store setzen
Given das Produkt "Basic T-Shirt" SKU "TS-BASIC-001" existiert
And der Store "Store Berlin" existiert
When der Manager eine POST-Anfrage auf "/api/v1/stores/{storeId}/inventory" mit SKU "TS-BASIC-001" und Menge 50 stellt
Then wird HTTP 200 zurückgegeben
And der Bestand für Store "Store Berlin" beträgt 50
Scenario: Bestand sinkt nach Verkauf
Given das Produkt "Basic T-Shirt" SKU "TS-BASIC-001" hat Bestand 10 im Store "Store Berlin"
When ein Verkauf mit 3 Einheiten von SKU "TS-BASIC-001" abgeschlossen wird
Then wird der Bestand auf 7 reduziert
And ein Inventory-Log-Eintrag wird erstellt
Feature: Kategorien
Scenario: Admin legt Hauptkategorie und Unterkategorie an
Given der User "admin@store.com" hat die Rolle "Admin"
When der Admin eine POST-Anfrage auf "/api/v1/categories" mit Name "Bekleidung" und parentId "null" stellt
Then wird HTTP 201 zurückgegeben
And die Kategorie "Bekleidung" existiert
When der Admin eine POST-Anfrage auf "/api/v1/categories" mit Name "T-Shirts" und parentId "{parentCategoryId}" stellt
Then wird HTTP 201 zurückgegeben
And die Kategorie "T-Shirts" ist eine Unterkategorie von "Bekleidung"
Scenario: Produktliste zeigt nur Produkte der ausgewählten Kategorie
Given es gibt 10 Produkte in Kategorie "T-Shirts"
And es gibt 5 Produkte in Kategorie "Hosen"
When eine GET-Anfrage auf "/api/v1/products?categoryId={tshirtCategoryId}" gestellt wird
Then werden genau 10 Produkte zurückgegeben
And alle gehören zur Kategorie "T-Shirts"
Scenario: Produkt mit Fashion-spezifischen Attributen anlegen
Given der User "admin@store.com" hat die Rolle "Admin"
When der Admin eine POST-Anfrage auf "/api/v1/products" mit Attributen stellt:
"""
{
"name": "Sommerkleid",
"sku": "SK-001",
"attributes": {
"size": ["XS", "S", "M", "L", "XL"],
"color": ["Rot", "Blau", "Grün"],
"season": "Sommer 2026",
"material": "Baumwolle"
}
}
"""
Then wird HTTP 201 zurückgegeben
And das Produkt hat alle Attribute gespeichert
Feature: Multi-Store Support
Scenario: Manager ist für mehrere Stores verantwortlich
Given die Company hat 3 Stores: "Store A", "Store B", "Store C"
And der User "manager@store.com" hat die Rolle "Manager"
When der Admin den Manager den Stores "Store A" und "Store B" zuordnet
Then wird HTTP 200 zurückgegeben
And der Manager kann auf "Store A" und "Store B" zugreifen
And der Manager kann nicht auf "Store C" zugreifen
Scenario: Gleiches Produkt hat unterschiedlichen Bestand pro Store
Given das Produkt "Basic T-Shirt" SKU "TS-BASIC-001" existiert
And Store "Store Berlin" hat Bestand 50
And Store "Store Hamburg" hat Bestand 30
When eine GET-Anfrage auf "/api/v1/stores/{berlinStoreId}/inventory/TS-BASIC-001" gestellt wird
Then wird Bestand 50 zurückgegeben
When eine GET-Anfrage auf "/api/v1/stores/{hamburgStoreId}/inventory/TS-BASIC-001" gestellt wird
Then wird Bestand 30 zurückgegeben
Scenario: Verkauf wird immer dem aktuellen Store zugeordnet
Given der User "sales@store.com" ist Sales im Store "Store Berlin"
When der Sales Associate einen Verkauf abschließt
Then wird der Verkauf Store "Store Berlin" zugeordnet
And der Verkauf ist nicht in anderen Stores sichtbar
Scenario: Manager kann keine Verkäufe eines anderen Stores sehen
Given der User "manager@store.com" ist nur Manager für Store "Store Berlin"
When der Manager eine GET-Anfrage auf "/api/v1/stores/{otherStoreId}/sales" stellt
Then wird HTTP 403 zurückgegeben
Feature: Basic Dashboard
Scenario: Admin sieht Dashboard mit Kennzahlen
Given die Company hat Verkäufe der letzten 7 Tage
And die Company hat 3 Stores
When der User eine GET-Anfrage auf "/api/v1/dashboard" stellt
Then werden folgende Kennzahlen zurückgegeben:
| Kennzahl | Typ |
| totalRevenueToday | number |
| totalRevenueWeek | number |
| transactionCountToday | number |
| transactionCountWeek | number |
| topProducts | array |
And die Daten sind für alle Stores der Company aggregiert (Admin)
Scenario: Manager sieht Dashboard nur für seinen Store
Given der User "manager@store.com" hat die Rolle "Manager"
And der Manager ist nur für Store "Store Berlin" zuständig
When der Manager eine GET-Anfrage auf "/api/v1/dashboard" stellt
Then werden nur Daten für "Store Berlin" angezeigt
And keine Daten anderer Stores sind sichtbar
Feature: Edge Case — Bestand-Konkurrenz
Scenario: Zwei parallele Verkäufe des letzten Produkts
Given das Produkt SKU "LIMITED-001" hat nur noch Bestand 1
And zwei Sales Associates starten gleichzeitig einen Verkauf über je 1 Einheit
When beide Verkäufe nahezu gleichzeitig abgeschlossen werden
Then erhält genau einer der Verkäufe HTTP 201
And der andere erhält HTTP 409 mit "Unzureichender Bestand"
And der Bestand ist nie negativ
Scenario: Dashboard bei Company ohne Stores
Given die Company hat keine Stores
When der Admin eine GET-Anfrage auf "/api/v1/dashboard" stellt
Then wird HTTP 200 zurückgegeben
And alle Kennzahlen sind 0 oder leer
And eine Info-Meldung wird zurückgegeben: "Noch keine Stores angelegt"
Scenario: Verkauf mit 0-Bestand wird abgelehnt
Given das Produkt SKU "OUT-OF-STOCK" hat Bestand 0
When ein Sales Associate einen Verkauf mit SKU "OUT-OF-STOCK" versucht
Then wird HTTP 409 zurückgegeben
And die Fehlermeldung lautet: "Produkt nicht verfügbar"
Scenario: API gibt 400 bei ungültiger UUID zurück
Given ein User ist authentifiziert
When eine GET-Anfrage auf "/api/v1/products/invalid-uuid" gestellt wird
Then wird HTTP 400 zurückgegeben
And die Fehlermeldung lautet: "Ungültige ID: muss eine gültige UUID sein"
Scenario: User kann nicht zwei Rollen gleichzeitig haben
Given der User "ambiguous@store.com" hat die Rolle "Sales"
When ein Admin versucht, dem User zusätzlich die Rolle "Manager" zuzuweisen
Then wird HTTP 400 zurückgegeben
And die Fehlermeldung lautet: "User kann nur eine Rolle haben"
Verdict: ✅ APPROVED — Ready for Implementation
Action Items aus CPTO-Review:
| Version | Datum | Änderung | Autor |
|---|---|---|---|
| 0.9 | 2026-07-03 | QA-001: Gherkin Akzeptanzkriterien für Phase-1 Must-Have Features ergänzt | DEV |
| 0.8 | 2026-07-03 | PM submitted for CPTO-Review | Product Manager |
| 0.7 | 2026-07-03 | QA APPROVED: QA-Review-Block aktualisiert, Checkbox-Todos entfernt, APPROVED fuer CPTO-Review | QA |
| 0.6 | 2026-07-03 | FINALIZED: Status-Update. PRD produktionsreif für CPTO-Review. | Product Manager |
| 0.5 | 2026-07-03 | CTO-APPROVED: Tech Stack, Cloud Architecture, Data Model signiert | CTO |
| 0.4 | 2026-07-03 | PM adressiert alle 5 Critical Issues aus QA-Review | Product Manager |
| 0.3 | 2026-07-03 | Vision & Mission ergänzt; Pain Points 1-3 ausgearbeitet; Core Workflows WF-1 bis WF-4 dokumentiert | Product Manager |
| 0.2 | 2026-07-03 | Technische Leitlinien, Pricing-Modell, AI Features, Feature Flags ergänzt | Product Manager |
| 0.1 | 2026-07-03 | Initiale Version — Grundstruktur, Zielgruppe, MoSCoW-Priorisierung | Product Manager |
Dieses PRD wird kontinuierlich aktualisiert. Jede Änderung muss als Comment im Issue dokumentiert werden.