diff --git a/README.md b/README.md index 9bda959..20823a3 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,118 @@ git tag vX.X.X && git push origin vX.X.X > [!CAUTION] > The workflow only tells if a deployment started on portainer. It can not detect if the API container or any other container fails on start as long as the container gets marked as "running". +--- + +## Shipping flow + +```mermaid +flowchart TD + A[Customer views product] --> B[Add to cart] + B --> C[Start checkout] + C --> D[Create Order: DRAFT] + D --> E["Reserve inventory (StockReservation)"] + E -->|insufficient stock| E1[Reject / show out of stock] + E -->|ok| F["Create Payment Session/Intent (PSP)"] + F --> G[Customer completes payment on PSP] + G --> H[PSP sends Webhook: payment_succeeded / payment_failed] + + H --> I{"Webhook verified + idempotent?"} + I -->|duplicate| I1["Return 200 OK (no-op)"] + I -->|new| J[DB txn: Payment=SUCCEEDED] + J --> K[Order status -> PAID] + K --> L[Commit inventory: decrement on_hand + release reservation] + L --> M[Enqueue fulfillment job: CREATE_LABEL] + + M --> N["Worker calls Carrier API (e.g. DHL)"] + N --> O{"Label created?"} + O -->|no| O1[Retry/backoff + alert admin] + O -->|yes| P[Shipment: tracking + label stored] + P --> Q[Order status -> READY_TO_SHIP] + Q --> R["Admin pick & pack with packing slip"] + R --> S[Hand over to carrier / pickup scan] + S --> T[Order status -> SHIPPED] + T --> U[Send tracking email to customer] + + H -->|payment_failed| V[Payment=FAILED] + V --> W[Order status -> CANCELLED] + W --> X[Release reservation] +``` + +### Phase 0 — Foundations (do this first) + +1. Core domain + DB schema +- Product/SKU, Inventory, Order, OrderItem, Customer, Address + +2. Order state machine +- DRAFT → PENDING_PAYMENT → PAID → READY_TO_SHIP → SHIPPED +- PENDING_PAYMENT → CANCELLED (timeout / failed) + +3. Idempotency & constraints +- Unique constraints for “one shipment label per order” +- Event inbox table (dedupe webhook events) + +### Phase 1 — Checkout (money + stock safety) + +1. Cart + Checkout draft API +- Create draft order and line items with price snapshots + +2. Inventory reservations +- Reserve on checkout start; expiry + cleanup job + +3. Payment provider integration (PSP) +- Create payment session/intent; store provider reference + +4. Webhook endpoint (authoritative payment confirmation) +- Signature verification +- Idempotent processing +- Transactional transition to PAID + commit inventory + +### Phase 2 — Admin fulfillment without carrier integration (ship manually first) + +1. Admin order list + detail +- Filter by status: PAID / READY_TO_SHIP / SHIPPED + +2. Pick/pack documents +- Packing slip + pick list (PDF optional, HTML fine initially) + +3. Manual shipment marking +- Admin enters tracking number manually +- Customer notification email + +(At this point you have a working webshop that can get paid, manage stock correctly, and ship manually.) + +### Phase 3 — Automation: DHL label generation + +1. Background job system +- Worker + queue; retry/backoff; dead-letter + alerts + +2. Carrier abstraction layer +- CarrierAdapter.create_label(order) interface + +3. DHL adapter +- Create label, store PDF/ZPL, tracking number + +4. Admin label printing + shipment workflow +- “Create label” / “Recreate label” rules +- Print label + packing slip from admin + +### Phase 4 — Operational hardening (what makes it production-grade) + +1. Outbox pattern for reliable job enqueueing + +2. Observability +- Structured logs, correlation IDs, metrics + +3. Fraud/edge-case handling +- Payment reversals/refunds → inventory & order adjustments + +4. Returns + refunds (basic RMA) + +5. Partial fulfillment / split shipments (optional, later) + +### Practical MVP scope (fastest path that still won’t bite you) + +- Webhook-driven payment confirmation +- Reservation-based inventory +- Admin pick/pack + manual tracking +- Then add DHL labels \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..0dac574 --- /dev/null +++ b/TODO.md @@ -0,0 +1,267 @@ +# TODO — OpenTaberna API Implementation Roadmap + +Ordered by dependency. Each phase builds on the previous. +CRUD for items (`crud-item-store`) is handled by a partner and not listed here. + +--- + +## Shared Infrastructure (already done ✅) + +- [x] Config module (`shared/config/`) +- [x] Logger module (`shared/logger/`) +- [x] Exceptions module (`shared/exceptions/`) +- [x] Responses module (`shared/responses/`) +- [x] Database module (`shared/database/`) — async SQLAlchemy 2.0, BaseRepository, migrations, health +- [x] Keycloak auth (`authorize/keycloak.py`) +- [x] FastAPI app skeleton (`main.py`) + +--- + +## Model Convention + +> **All models throughout this project are pure Pydantic models.** +> SQLAlchemy's `Base` / `DeclarativeBase` is **not used** for domain entities. +> Database interaction happens via the `BaseRepository` with raw SQL or query builders — models are validated and serialized exclusively through Pydantic. + +Pattern per entity: + +``` +models/ +├── customer.py +│ ├── CustomerBase(BaseModel) # shared fields +│ ├── CustomerCreate(CustomerBase) # input / write schema +│ ├── CustomerUpdate(BaseModel) # partial update (all Optional) +│ └── CustomerResponse(CustomerBase) # output / read schema, incl. id + timestamps +``` + +- Use `model_config = ConfigDict(from_attributes=True)` on response models if mapping from DB rows +- Timestamps (`created_at`, `updated_at`) are read-only response fields, never in Create/Update schemas +- Primary keys are always in response models only, never in Create schemas + +--- + +## Phase 0 — Domain Models & DB Schema + +> Prerequisite for everything. No service can be built without these. + +### 0.1 Customer & Address + +- [ ] `CustomerBase`, `CustomerCreate`, `CustomerUpdate`, `CustomerResponse` — fields: id, keycloak_user_id, email, name, created_at, updated_at +- [ ] `AddressBase`, `AddressCreate`, `AddressUpdate`, `AddressResponse` — fields: id, customer_id, street, city, zip, country, is_default, created_at, updated_at +- [ ] Alembic migration for `customers` + `addresses` (schema defined separately from Pydantic models) +- [ ] `CustomerRepository(BaseRepository)` — typed to `CustomerResponse` +- [ ] `AddressRepository(BaseRepository)` — typed to `AddressResponse` + +### 0.2 Inventory + +> Depends on partner's `Product`/`SKU` models being accessible. + +- [ ] `InventoryItemBase`, `InventoryItemCreate`, `InventoryItemUpdate`, `InventoryItemResponse` — fields: id, sku_id, on_hand, reserved, created_at, updated_at +- [ ] `StockReservationBase`, `StockReservationCreate`, `StockReservationResponse` — fields: id, inventory_item_id, order_id, quantity, expires_at, status (`ReservationStatus` enum: ACTIVE / COMMITTED / EXPIRED / RELEASED), created_at, updated_at +- [ ] DB constraint (in migration): `on_hand >= 0`, `reserved >= 0`, `on_hand >= reserved` +- [ ] Alembic migration for `inventory_items` + `stock_reservations` +- [ ] `InventoryRepository(BaseRepository)` +- [ ] `StockReservationRepository(BaseRepository)` + +### 0.3 Order & OrderItem + +- [ ] `OrderStatus` enum: `DRAFT` → `PENDING_PAYMENT` → `PAID` → `READY_TO_SHIP` → `SHIPPED` → `CANCELLED` +- [ ] `OrderBase`, `OrderCreate`, `OrderUpdate`, `OrderResponse` — fields: id, customer_id, status, total_amount, currency, deleted_at, created_at, updated_at +- [ ] `OrderItemBase`, `OrderItemCreate`, `OrderItemResponse` — fields: id, order_id, sku_id, quantity, unit_price (price snapshot at order time), created_at, updated_at +- [ ] Alembic migration for `orders` + `order_items` +- [ ] `OrderRepository(BaseRepository)` +- [ ] `OrderItemRepository(BaseRepository)` + +### 0.4 Payment + +- [ ] `PaymentStatus` enum: PENDING / SUCCEEDED / FAILED / REFUNDED +- [ ] `PaymentProvider` enum: STRIPE / … +- [ ] `PaymentBase`, `PaymentCreate`, `PaymentUpdate`, `PaymentResponse` — fields: id, order_id, provider, provider_reference, amount, currency, status, created_at, updated_at +- [ ] Alembic migration for `payments` (unique constraint on `order_id`, unique on `provider_reference`) +- [ ] `PaymentRepository(BaseRepository)` + +### 0.5 Webhook Event Inbox (idempotency) + +- [ ] `WebhookEventCreate`, `WebhookEventResponse` — fields: id, provider, event_id, payload (dict), processed_at, created_at +- [ ] Alembic migration for `webhook_events` (unique constraint on `(provider, event_id)`) +- [ ] `WebhookEventRepository(BaseRepository)` + +### 0.6 Shipment + +- [ ] `Carrier` enum: DHL / MANUAL +- [ ] `ShipmentStatus` enum: PENDING / LABEL_CREATED / HANDED_OVER +- [ ] `ShipmentBase`, `ShipmentCreate`, `ShipmentUpdate`, `ShipmentResponse` — fields: id, order_id, carrier, tracking_number, label_url, label_format (PDF / ZPL), status, created_at, updated_at +- [ ] Alembic migration for `shipments` (unique constraint on `order_id`) +- [ ] `ShipmentRepository(BaseRepository)` + +--- + +## Phase 1 — Checkout & Payment (`services/order-processing/`) + +> Create service: `src/app/services/order-processing/` + +### 1.1 Cart / Draft Order API + +- [ ] `POST /orders` — create draft order with line items (price snapshot from SKU) +- [ ] `GET /orders/{id}` — retrieve order (customer-scoped via Keycloak token) +- [ ] `DELETE /orders/{id}` — cancel draft order +- [ ] Pydantic models: `OrderCreate`, `OrderItemCreate`, `OrderResponse`, `OrderDetailResponse` +- [ ] Business logic: validate SKUs exist, calculate totals, create `Order` in `DRAFT` status +- [ ] Register router in `main.py` + +### 1.2 Inventory Reservation + +- [ ] `functions/reserve_inventory.py` — atomic check-and-reserve (single DB transaction) + - Check `on_hand - reserved >= requested quantity` + - Insert `StockReservation` (status=ACTIVE, expires_at = now + configurable TTL) + - Increment `reserved` on `InventoryItem` +- [ ] `functions/release_reservation.py` — set reservation to RELEASED, decrement `reserved` +- [ ] `functions/commit_reservation.py` — set reservation to COMMITTED, decrement `on_hand` + `reserved` +- [ ] `functions/expire_reservations.py` — background cleanup: expire stale reservations and release stock +- [ ] Add `RESERVATION_TTL_MINUTES` to `Settings` + +### 1.3 Checkout Endpoint + +- [ ] `POST /orders/{id}/checkout` — transition `DRAFT` → `PENDING_PAYMENT` + - Reserve inventory (reject with 409 if insufficient stock, include which SKUs) + - Create PSP payment session/intent (see 1.4) + - Return PSP client secret / redirect URL + +### 1.4 PSP Integration (Stripe recommended as first adapter) + +- [ ] `services/payment_provider/` subfolder inside `order-processing` +- [ ] `PaymentProviderAdapter` interface (abstract base): `create_session(order) → ProviderSession`, `verify_webhook(headers, body) → WebhookPayload` +- [ ] `StripeAdapter` implementing the interface +- [ ] Add `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` to `Settings` + +### 1.5 Webhook Endpoint + +- [ ] `POST /webhooks/stripe` — raw body endpoint (do NOT parse body as JSON before signature check) +- [ ] Signature verification via `StripeAdapter.verify_webhook()` +- [ ] Idempotency check: lookup `(provider="stripe", event_id=stripe_event_id)` in `webhook_events` — return 200 if already processed +- [ ] On `payment_intent.succeeded`: + - DB transaction: insert `WebhookEvent`, update `Payment` → SUCCEEDED, update `Order` → PAID, call `commit_reservation()` + - Enqueue fulfillment job (Phase 3; use a no-op stub for now) +- [ ] On `payment_intent.payment_failed`: + - DB transaction: insert `WebhookEvent`, update `Payment` → FAILED, update `Order` → CANCELLED, call `release_reservation()` +- [ ] Register webhook router in `main.py` + +--- + +## Phase 2 — Admin Fulfillment (`services/admin/`) + +> Requires Phase 1 complete. No carrier integration yet — ship manually. + +- [ ] Create service: `src/app/services/admin/` +- [ ] Keycloak role guard for all admin routes (e.g. `role="admin"`) + +### 2.1 Admin Order Management + +- [ ] `GET /admin/orders` — list orders, filter by status, paginated (`PaginatedResponse`) +- [ ] `GET /admin/orders/{id}` — order detail with items, customer, address, payment, shipment +- [ ] `PATCH /admin/orders/{id}/status` — manual status override with audit log + +### 2.2 Pick & Pack Documents + +- [ ] `GET /admin/orders/{id}/packing-slip` — HTML response (print-friendly) listing items, quantities, customer address +- [ ] `GET /admin/orders/{id}/pick-list` — aggregate pick list across multiple orders (batch picking) + +### 2.3 Manual Shipment Marking + +- [ ] `POST /admin/orders/{id}/shipments` — create `Shipment` with manual tracking number; transition `Order` → `READY_TO_SHIP` +- [ ] `POST /admin/orders/{id}/ship` — mark as handed over to carrier; transition `Order` → `SHIPPED` +- [ ] Trigger customer notification email on `SHIPPED` (see 2.4) + +### 2.4 Customer Notification Email + +- [ ] `functions/send_tracking_email.py` — send tracking number + carrier link to customer +- [ ] Add `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `EMAIL_FROM` to `Settings` +- [ ] Use async SMTP (e.g. `aiosmtplib`); templated HTML email + +--- + +## Phase 3 — Automated Label Generation (`services/fulfillment/`) + +> Requires Phase 2 complete. Replaces the manual shipping step with automation. + +### 3.1 Background Job System + +- [ ] Evaluate and add queue backend: **ARQ** (Redis-based, async, fits FastAPI well) recommended +- [ ] Add `REDIS_URL` to `Settings` +- [ ] Worker entry point: `src/app/worker.py` +- [ ] Job: `create_label_job(order_id: int)` — retries (max 5), exponential backoff, dead-letter logging + +### 3.2 Carrier Abstraction Layer + +- [ ] `services/fulfillment/carrier/interface.py` — `CarrierAdapter` abstract base + ``` + create_label(order: OrderResponse, shipment: ShipmentResponse) → LabelResult + LabelResult(BaseModel): tracking_number, label_url, label_format + ``` +- [ ] `ManualCarrierAdapter` — no-op adapter (used in Phase 2, keeps interface consistent) + +### 3.3 DHL Adapter + +- [ ] `services/fulfillment/carrier/dhl.py` — `DhlAdapter(CarrierAdapter)` +- [ ] DHL Parcel DE Shipping API (REST): create shipment, retrieve label PDF/ZPL +- [ ] Add `DHL_API_KEY`, `DHL_ACCOUNT_NUMBER`, `DHL_PRODUCT` to `Settings` +- [ ] Store label binary in object storage (S3 / MinIO) — add `STORAGE_*` settings +- [ ] Handle DHL error responses with proper `AppException` subclass + +### 3.4 Admin Label Workflow + +- [ ] `POST /admin/orders/{id}/label` — trigger `create_label_job` manually (or re-trigger on failure) +- [ ] `GET /admin/orders/{id}/label` — download label PDF/ZPL (proxy from storage) +- [ ] Rules: only allowed when `Order.status == PAID` and no committed label exists + +### 3.5 Outbox Pattern (reliable job enqueueing) + +- [ ] `OutboxEventCreate`, `OutboxEventResponse` Pydantic models — fields: id, event_type, payload (dict), enqueued_at, created_at +- [ ] Replace direct ARQ enqueue in webhook handler with outbox insert (same transaction as order update) +- [ ] Background poller: read un-enqueued outbox events, push to ARQ, mark enqueued + +--- + +## Phase 4 — Operational Hardening + +### 4.1 Observability + +- [ ] Correlation ID middleware — inject `X-Request-ID` into every request's log context (already supported by `shared/logger/context.py`) +- [ ] Structured log fields: `order_id`, `payment_id`, `user_id` on all relevant log statements +- [ ] Health endpoints: `GET /health` (liveness) + `GET /health/ready` (DB + Redis checks via `shared/database/health.py`) +- [ ] Prometheus metrics endpoint (optional; add `prometheus-fastapi-instrumentator`) + +### 4.2 Reservation Expiry Job (production-grade) + +- [ ] ARQ scheduled job: run `expire_reservations()` every N minutes +- [ ] Alert admin on repeated expiry failures + +### 4.3 Payment Reversals / Refunds + +- [ ] Handle `charge.refunded` / `payment_intent.canceled` Stripe webhooks +- [ ] `Payment` → REFUNDED, `Order` → CANCELLED, `release_reservation()` if not yet committed +- [ ] If already committed/shipped: create `Refund` record (separate model), flag for manual review + +### 4.4 Returns & RMA (basic) + +- [ ] `ReturnStatus` enum: REQUESTED / APPROVED / RECEIVED / REFUNDED +- [ ] `ReturnCreate`, `ReturnUpdate`, `ReturnResponse` Pydantic models — fields: id, order_id, reason, status, created_at, updated_at +- [ ] `POST /orders/{id}/returns` — customer requests return +- [ ] `PATCH /admin/returns/{id}` — admin approves and processes + +### 4.5 Security Hardening + +- [ ] Restrict CORS `origins` in `main.py` (currently `["*"]`) +- [ ] Rate limiting on webhook endpoint (e.g. `slowapi`) +- [ ] Replace `secret_key` default `"CHANGE_ME_IN_PRODUCTION"` with startup validation + +--- + +## Cross-Cutting Tasks (do as you go) + +- [ ] Write pytest tests for every new service module (mirror `tests/` structure) +- [ ] Add each new model to Alembic `env.py` imports so auto-generate works +- [ ] Register every new service router in `main.py` +- [ ] Keep `Settings` as the single source of truth for all env vars — no hardcoded values +- [ ] Use `shared/exceptions/` for all error cases — never return raw HTTP exceptions from business logic +- [ ] Use `shared/responses/` factory helpers (`success()`, `paginated()`, `error_from_exception()`) in all routers diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..7209a7e --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,208 @@ +# Roadmap — OpenTaberna API + +Implementierungsplan für den vollständigen Kunden-Kaufprozess: +**Kunde betritt den Shop → sieht ein Item → klickt → kauft.** + +Basis: `crud-item-store` (Items) ist fertig. Alle anderen Services werden hier geplant. + +--- + +## Shipping Flow (Referenz) + +```mermaid +flowchart TD + A[Customer views product] --> B[Add to cart] + B --> C[Start checkout] + C --> D[Create Order: DRAFT] + D --> E["Reserve inventory (StockReservation)"] + E -->|insufficient stock| E1[Reject / show out of stock] + E -->|ok| F["Create Payment Session/Intent (PSP)"] + F --> G[Customer completes payment on PSP] + G --> H[PSP sends Webhook: payment_succeeded / payment_failed] + + H --> I{"Webhook verified + idempotent?"} + I -->|duplicate| I1["Return 200 OK (no-op)"] + I -->|new| J[DB txn: Payment=SUCCEEDED] + J --> K[Order status -> PAID] + K --> L[Commit inventory: decrement on_hand + release reservation] + L --> M[Enqueue fulfillment job: CREATE_LABEL] + + M --> N["Worker calls Carrier API (e.g. DHL)"] + N --> O{"Label created?"} + O -->|no| O1[Retry/backoff + alert admin] + O -->|yes| P[Shipment: tracking + label stored] + P --> Q[Order status -> READY_TO_SHIP] + Q --> R["Admin pick & pack with packing slip"] + R --> S[Hand over to carrier / pickup scan] + S --> T[Order status -> SHIPPED] + T --> U[Send tracking email to customer] + + H -->|payment_failed| V[Payment=FAILED] + V --> W[Order status -> CANCELLED] + W --> X[Release reservation] +``` + +--- + +## Alle Endpunkte + +### Bereits vorhanden — `crud-item-store` + +| Methode | Pfad | Was sie tut | +|---|---|---| +| `GET` | `/items` | Item-Liste mit Paginierung | +| `GET` | `/items/{sku}` | Ein Item per SKU | +| `GET` | `/items/{uuid}` | Ein Item per UUID | +| `POST` | `/items` | Item anlegen | +| `PATCH` | `/items/{uuid}` | Item aktualisieren | +| `DELETE` | `/items/{uuid}` | Item löschen | + +--- + +### `customers` — Phase 0/1 + +| Methode | Pfad | Was sie tut | +|---|---|---| +| `GET` | `/customers/me` | Eigenes Kundenprofil abrufen (legt bei erstem Aufruf automatisch an) | +| `PATCH` | `/customers/me` | Eigenes Profil aktualisieren | +| `GET` | `/customers/me/addresses` | Alle Adressen des Kunden | +| `POST` | `/customers/me/addresses` | Neue Adresse anlegen | +| `PATCH` | `/customers/me/addresses/{id}` | Adresse aktualisieren | +| `DELETE` | `/customers/me/addresses/{id}` | Adresse löschen | + +--- + +### `order-processing` — Phase 1 + +| Methode | Pfad | Was sie tut | +|---|---|---| +| `POST` | `/orders` | Draft-Order anlegen mit Line Items + Preissnapshot | +| `GET` | `/orders/{id}` | Order abrufen (nur eigene, Keycloak-gesichert) | +| `DELETE` | `/orders/{id}` | Draft-Order stornieren (nur DRAFT erlaubt) | +| `POST` | `/orders/{id}/checkout` | DRAFT → PENDING_PAYMENT: Inventar reservieren + PSP-Session erstellen, gibt PSP-Client-Secret zurück | +| `POST` | `/webhooks/stripe` | Stripe-Webhook: Signaturprüfung, Idempotenz, PAID oder CANCELLED je nach Event | + +--- + +### `admin` — Phase 2 + +| Methode | Pfad | Was sie tut | +|---|---|---| +| `GET` | `/admin/orders` | Alle Orders, filterbar nach Status, paginiert | +| `GET` | `/admin/orders/{id}` | Order-Detail mit Items, Kunde, Adresse, Payment, Shipment | +| `PATCH` | `/admin/orders/{id}/status` | Manueller Status-Override mit Audit-Log | +| `GET` | `/admin/orders/{id}/packing-slip` | HTML-Packing-Slip (druckbar) für eine Order | +| `GET` | `/admin/orders/pick-list` | Aggregierte Pick-Liste über mehrere Orders (Batch-Picking) | +| `POST` | `/admin/orders/{id}/shipments` | Shipment anlegen mit manueller Tracking-Nummer → Order → READY_TO_SHIP | +| `POST` | `/admin/orders/{id}/ship` | Order → SHIPPED, löst Tracking-E-Mail aus | + +--- + +### `fulfillment` — Phase 3 + +| Methode | Pfad | Was sie tut | +|---|---|---| +| `POST` | `/admin/orders/{id}/label` | DHL-Label-Job manuell anstoßen (oder bei Fehler neu triggern) | +| `GET` | `/admin/orders/{id}/label` | Label-PDF/ZPL herunterladen (Proxy aus Storage) | + +--- + +### `returns` — Phase 4 + +| Methode | Pfad | Was sie tut | +|---|---|---| +| `POST` | `/orders/{id}/returns` | Kunde beantragt Rücksendung | +| `PATCH` | `/admin/returns/{id}` | Admin genehmigt/verarbeitet die Rücksendung | + +--- + +### Infra / Health — Phase 4 + +| Methode | Pfad | Was sie tut | +|---|---|---| +| `GET` | `/health` | Liveness-Check (API läuft) | +| `GET` | `/health/ready` | Readiness-Check (DB + Redis erreichbar) | + +--- + +## Implementierungsreihenfolge + +### Phase 0 — Domain Models & DB Schema *(Blockiert alles andere)* + +Kein Feature-Endpunkt ohne diese Grundlage. Beide können parallel arbeiten. + +| Du | Kollege | +|---|---| +| **0.1** Customer + Address — Pydantic-Modelle, Alembic-Migration, Repository | **0.2** Inventory: `InventoryItem`, `StockReservation`, DB-Constraints (`on_hand >= reserved`), Repository | +| **0.5** Webhook Event Inbox — Modell + Migration (kurz, unabhängig) | *(parallel zu 0.2)* | +| → dann: **0.3** Order + OrderItem — abhängig von 0.1 + 0.2 | | +| **0.4** Payment-Modell + Migration | **0.6** Shipment-Modell + Migration | + +**Fortschritt:** `[ ] 0.1` `[ ] 0.2` `[ ] 0.3` `[ ] 0.4` `[ ] 0.5` `[ ] 0.6` + +--- + +### Phase 1 — Checkout & Payment (`services/order-processing/`) + +| Du | Kollege | +|---|---| +| **1.1** `POST /orders` — Draft-Order + Business-Logik (Preissnapshot, Validierung, DRAFT-Status) | **1.4** PSP-Integration — `PaymentProviderAdapter` Interface + `StripeAdapter` (völlig unabhängig) | +| **1.2** Inventar-Funktionen — `reserve_inventory`, `release_reservation`, `commit_reservation`, `expire_reservations` | *(parallel zu 1.1)* | +| → dann: **1.3** `POST /orders/{id}/checkout` — braucht 1.1 + 1.2 + 1.4 | | +| → dann: **1.5** `POST /webhooks/stripe` — Signaturprüfung, Idempotenz, DB-Transaktion | | + +> Nach Phase 1: funktionierender Webshop der Geld einnehmen und Stock sicher reservieren kann. + +**Fortschritt:** `[ ] 1.1` `[ ] 1.2` `[ ] 1.3` `[ ] 1.4` `[ ] 1.5` + +--- + +### Phase 2 — Admin-Fulfillment (`services/admin/`) *(kein Carrier nötig)* + +| Du | Kollege | +|---|---| +| **2.1** `GET /admin/orders` + `GET /admin/orders/{id}` — Liste + Detail, paginiert, Status-Filter | **2.4** `send_tracking_email.py` + SMTP-Settings + HTML-E-Mail-Template | +| **2.2** `GET /admin/orders/{id}/packing-slip` + `pick-list` — HTML-Dokumente | *(parallel zu 2.1)* | +| → dann: **2.3** `POST /admin/orders/{id}/shipments` + `POST /admin/orders/{id}/ship` | | + +> Nach Phase 2: **vollständiges lauffähiges MVP** — zahlender Kunde, korrekter Lagerbestand, manuell versendbar. + +**Fortschritt:** `[ ] 2.1` `[ ] 2.2` `[ ] 2.3` `[ ] 2.4` + +--- + +### Phase 3 — DHL-Label-Generierung (`services/fulfillment/`) + +| Du | Kollege | +|---|---| +| **3.1** ARQ Job-System — Redis-Queue, `worker.py`, Retry/Backoff, Dead-Letter-Logging | **3.2** `CarrierAdapter` Interface + `ManualCarrierAdapter` (no-op, hält Interface konsistent) | +| **3.3** `DhlAdapter` — DHL Parcel DE REST API, Label PDF/ZPL, Storage (S3/MinIO) | **3.5** Outbox-Pattern — Outbox-Tabelle, Poller, zuverlässiges Job-Enqueuing in gleicher DB-Transaktion | +| **3.4** Admin-Label-Endpunkte `POST/GET /admin/orders/{id}/label` | | + +**Fortschritt:** `[ ] 3.1` `[ ] 3.2` `[ ] 3.3` `[ ] 3.4` `[ ] 3.5` + +--- + +### Phase 4 — Operational Hardening *(laufend parallel möglich)* + +| Task | Was | +|---|---| +| **4.1** Observability | Correlation-ID-Middleware, strukturierte Logs mit `order_id`/`user_id`, `/health` + `/health/ready` | +| **4.2** Reservation-Expiry-Job | ARQ Scheduled Job — `expire_reservations()` alle N Minuten | +| **4.3** Payment Reversals | Stripe `charge.refunded` / `payment_intent.canceled` Webhooks → REFUNDED/CANCELLED | +| **4.4** Returns / RMA | `POST /orders/{id}/returns`, `PATCH /admin/returns/{id}` | +| **4.5** Security Hardening | CORS einschränken, Rate-Limiting auf Webhook (`slowapi`), `CHANGE_ME`-Startup-Validation | + +**Fortschritt:** `[ ] 4.1` `[ ] 4.2` `[ ] 4.3` `[ ] 4.4` `[ ] 4.5` + +--- + +## Kritische Reihenfolge + +``` +Phase 0 (parallel) → Phase 1 (parallel) → Phase 2 (parallel) → MVP ✓ → Phase 3 → Phase 4 +``` + +**Kritischer Pfad für den ersten zahlenden Kunden:** `GET /items` → `POST /orders` → `POST /orders/{id}/checkout` + +**Gesamt: ~26 Endpunkte** (6 vorhanden, 20 zu bauen) diff --git a/docs/taberna board.md b/docs/taberna board.md new file mode 100644 index 0000000..a3ad163 --- /dev/null +++ b/docs/taberna board.md @@ -0,0 +1,57 @@ +--- +kanban-plugin: board +--- + +## Backlog + +- [ ] **Database Layer Setup** 📦 #database #infrastructure
Setup PostgreSQL with SQLAlchemy/Tortoise ORM, connection pooling, migrations (Alembic), and health checks +- [ ] **Authentication & Authorization** 🔐 #security #auth
Implement Keycloak integration, JWT token handling, role-based access control (RBAC), and permission middleware +- [ ] **Item Store CRUD** 📝 #feature #crud
Complete CRUD operations for item-store service using shared modules (exceptions, responses, logger, config) +- [ ] **API Documentation** 📚 #docs #api
Setup Swagger/OpenAPI documentation with examples, schemas, and authentication flows +- [ ] **Caching Layer** ⚡ #performance #cache
Implement Redis caching for frequently accessed data with TTL management +- [ ] **Background Jobs** ⏰ #jobs #async
Setup Celery/ARQ for async tasks (email notifications, data processing, scheduled jobs) +- [ ] **File Upload Service** 📤 #feature #files
Handle file uploads with S3/MinIO integration, validation, and thumbnail generation +- [ ] **Search & Filtering** 🔍 #feature #search
Implement full-text search with Elasticsearch/PostgreSQL and advanced filtering +- [ ] **Rate Limiting** 🚦 #security #middleware
Add rate limiting middleware to prevent abuse and DDoS attacks +- [ ] **Monitoring & Metrics** 📊 #observability #metrics
Setup Prometheus metrics, Grafana dashboards, and alerting + + +## Next + +- [ ] **Shared Modules Integration** 🔧 #refactor #shared
Ensure all services use config, logger, exceptions, and responses modules correctly +- [ ] **Testing Strategy** 🧪 #testing #quality
Write unit tests, integration tests, and E2E tests for all features. Target 80% coverage +- [ ] **Docker Compose Setup** 🐳 #devops #docker
Create docker-compose.yml with all services (API, PostgreSQL, Redis, Keycloak) for local development +- [ ] **CI/CD Pipeline** 🚀 #devops #automation
Setup GitHub Actions for testing, linting, building, and deploying to staging/production +- [ ] **Error Handling Middleware** ⚠️ #middleware #errors
Create global exception handler using shared exception module for consistent error responses + + +## In Progress + +- [ ] **Shared Modules Documentation** 📖 #docs #shared
Document how to use config, logger, exceptions, and responses in features (✅ completed docs/shared-modules.md) + + +## Wait + +- [ ] **API Versioning** 🔢 #api #architecture
Design and implement API versioning strategy (URL-based vs header-based) +- [ ] **WebSocket Support** 🔌 #feature #realtime
Add WebSocket support for real-time notifications and updates (waiting for use case clarification) +- [ ] **Multi-tenancy** 🏢 #architecture #multitenancy
Implement tenant isolation and data segregation (waiting for requirements) +- [ ] **Payment Integration** 💳 #feature #payments
Integrate payment gateway (Stripe/PayPal) for transactions (waiting for business logic) + + +## Done + +**Complete** +- [x] **Config Module** ⚙️ #shared #config
✅ Environment-based settings, secrets management, singleton pattern +- [x] **Logger Module** 📝 #shared #logging
✅ Structured logging, context management, sensitive data filtering +- [x] **Exception Module** ❌ #shared #exceptions
✅ Custom exception hierarchy, auto-logging, helper functions +- [x] **Response Module** ✅ #shared #responses
✅ Generic success/error responses, pagination, exception integration +- [x] **Project Structure** 🏗️ #architecture #setup
✅ Modular FastAPI structure with services, shared modules, and documentation + + + + +%% kanban:settings +``` +{"kanban-plugin":"board","hide-tags-in-title":true,"tag-colors":[],"hide-card-count":false,"show-checkboxes":false,"show-relative-date":true,"hide-date-display":false,"hide-date-in-title":true,"hide-tags-display":false,"date-colors":[{"isToday":false,"distance":3,"unit":"days","direction":"after","color":"rgba(255, 218, 0, 1)"},{"isToday":false,"distance":1,"unit":"days","direction":"after","color":"rgba(255, 80, 0, 1)"},{"distance":1,"unit":"days","direction":"after","color":"rgba(255, 0, 0, 1)","isBefore":true}],"move-dates":false,"append-archive-date":true,"archive-with-date":true,"date-picker-week-start":1,"list-collapse":[false,null,false,null,false],"new-note-folder":"Uni","max-archive-size":20} +``` +%% \ No newline at end of file