Skip to content

feat(export): send reviewed positions to the ERP (contract 1.1.0) (#46) - #54

Merged
Fluory merged 4 commits into
mainfrom
claude/feat-erp-line-items-46
Sep 24, 2026
Merged

Fluory merged 4 commits into
mainfrom
claude/feat-erp-line-items-46

Conversation

@Fluory

@Fluory Fluory commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Warum

Fixes #46 · Bisher bekam das ERP nur die Kopfdaten (Firma, Ansprechperson, Liefertermin) – die geprüften Positionen fehlten. Ohne Positionen ist der Export für ein echtes Angebot wertlos.

Arbeitsstand

  • Ziel: Jede freigegebene Anfrage schickt ihre geprüften Positionen (inkl. Korrekturen) ans ERP – additiv, Vertrag 1.1.0.
  • Nicht-Ziele: echter ERP-Adapter, Preise/Artikelnummern, /v2.
  • Erledigt: Vertrag + generierte Typen + zod-Schema, Payload mit lineItems, Grenzprüfung bei der Freigabe, Tests (test-first), Doku, CHANGELOG, frischer Review eingearbeitet (eigener Ablehnungscode export_too_large, Test gegen veraltete Korrekturen), verify grün.
  • Offen: menschliche Freigabe der Vertragsänderung (öffentliche Schnittstelle, api-Regel), dann Review/Merge durch den Orchestrator.
  • Annahmen: Positionen ohne jeden Wert werden trotzdem mit ihrer Nummer übertragen (das ERP sieht dieselbe Reihenfolge wie die Prüfansicht).
  • Nächster kleinster Schritt: Freigabe der Vertragsänderung durch den Orchestrator.

Was ist passiert (Klartext)

Das ERP bekommt jetzt nicht nur „wer fragt an und bis wann“, sondern auch „was genau“: jede Position mit Beschreibung, Menge, Einheit, Werkstoff und Maßen – so, wie die Sachbearbeitung sie geprüft und ggf. korrigiert hat. Der Vertrag mit dem ERP wird dafür nur erweitert (Version 1.1.0): Anfragen ohne Positionen sehen exakt aus wie vorher, ein alter Empfänger merkt also nichts. Ist ein Wert zu lang, sind es mehr als 200 Positionen oder würde die Nachricht zu groß, wird die Freigabe verweigert – dann kann die Sachbearbeitung noch korrigieren. Nichts wird stillschweigend abgeschnitten. Hat eine Anfrage mehr als 200 Positionen, sagt die Meldung ehrlich, dass sie zu groß für den Export ist und direkt im ERP erfasst werden muss.

Plan-Pflicht (SYSTEM.md §4)

  • Kein Auslöser – keine Modulgrenze, öffentliche API, Migration, Auth/Rechte, kein Zahlungs-/Daten-/Infrapfad, höchstens zwei Module, keine Architekturvarianten, umkehrbar
  • Auslöser zutreffend – Impact Manifest ausgefüllt (Plan vor Code)

Impact Manifest

  • Betroffene Module: export (Vertrag, Payload, Job-Deps), review (currentLineItemValues, Freigabeprüfung), worker (Verdrahtung).
  • Schnittstellen / Datenänderungen: contracts/erp-export.openapi.yaml 1.0.0 → 1.1.0: optionales lineItems (1–200 × LineItem{position ≥ 1, description, quantity, unit, material, dimensions: string|null ≤ 500}, additionalProperties: false). Keine Migration. ExportDeps bekommt lineItemValues (intern).
  • Akzeptanzkriterien: feat(export): send reviewed line items to the ERP (contract v2) #46 – Positionen im ERP-Body; Korrekturen gewinnen; ohne Positionen unveränderter Body; Vertragsgrenzen blockieren die Freigabe.
  • Testplan: Integration export.test (Positionen mit Korrektur, kein lineItems ohne Positionen, value_too_long bei überlangem Positionswert, export_too_large bei 201 Positionen, Export = Prüfansicht nach neuerem Lauf), Unit payload.test.ts (Grenzen: Feld, Anzahl, Body-Größe), Drift-Tests des Vertrags.
  • Verifizierte Fakten: Exactly-once unverändert (Idempotency-Key, eindeutige Export-Zeile, Row-Lock); der Mock hasht den Body – nach der Freigabe sind Werte eingefroren, der Body bleibt deterministisch. Korrekturen älter als der letzte Lauf werden ignoriert (wie in der Prüfansicht).
  • Offene Annahmen: –
  • Nicht-Ziele: siehe oben.
  • Risiken und Rollback: Ein Empfänger mit strikter 1.0.0-Validierung würde Anfragen mit Positionen ablehnen – der Mock ist auf 1.1.0; Rollback per Revert.

Geändert

  • contracts/erp-export.openapi.yaml, src/features/export/erp-export.contract.ts (generiert), contract.ts: LineItem, lineItems
  • src/features/export/payload.ts: toLineItems, ERP_LIMITS, exportLimitViolations mit Positionen und Body-Schätzung, buildQuoteRequest mit lineItemValues
  • src/features/export/export-job.ts, index.ts, src/worker.ts: Verdrahtung
  • src/features/review/review.ts: currentLineItemValues, Grenzprüfung in approveRequest (value_too_long / export_too_large)
  • src/app/requests/[id]/messages.ts: Meldung für export_too_large
  • Tests: tests/integration/export.test.ts, neu src/features/export/payload.test.ts, Deps in duplicates/request-list/log-privacy
  • Doku: docs/technical/api.md, CHANGELOG.md

Nachweis (SYSTEM.md §11)

  • verify:changed: grün – export.test 11/11 (Mutationsprobe: ohne die Laufregel rot), payload.test.ts 5/5
  • verify: grün nach den Review-Änderungen – unit 159, integration 118 (lint, typecheck, unit, integration gegen Postgres + S3, depcruise, build, audit high)
  • verify:full / E2E-Spec: nicht betroffen (keine UI-Änderung außer einem Meldungstext)
  • Manueller Prüfnachweis: –
  • Frischer Review (P1 vor Ready-for-review; Architektur/API/DB immer): feat(export): send reviewed positions to the ERP (contract 1.1.0) (#46) #54 (comment) – nichts blockierend, Befunde eingearbeitet bzw. begründet

Doku-Entscheidung (genau eine)

  • Keine langlebige Doku betroffen – Begründung: –
  • Doku betroffen und im selben PR aktualisiert:
    • Produktdoku (P0: README): –
    • Technische Doku: docs/technical/api.md
    • Architekturkarte: –
    • ADR: –
    • CHANGELOG [Unreleased] (sichtbares Feature oder Verhalten – im selben PR, nie „später")

Entferntes oder Umbenanntes: docs/ + README gegrept, Treffer bereinigt: nichts entfernt

Dateigrößen und neue Bausteine (SYSTEM.md §7)

Dateien über 500 Zeilen im Diff (Ausnahmen: generierter Code, Lockfiles, Fixtures, Migrationen, Schemas, Ressourcen, Doku, Konfiguration):

  • keine
  • bewusst belassen – Begründung: –
  • im selben PR nach fachlicher Verantwortung geteilt
  • Folge-Issue –

Über 800 Zeilen mit neuer Fachlogik oder über 1000 Zeilen (P1/P2): nicht betroffen

Neue Shared-Komponente, Utility-Datei, Adapter oder fachlicher Service:

  • nein
  • ja – gesucht nach: –

Subagent-Einsätze

  • Frischer Review-Subagent (read-only) – nichts blockierend; 3 should / 3 nit, Umgang im PR-Kommentar.

Risiken / offene Punkte

  • Öffentliche Vertragsänderung → menschliche Freigabe nötig (api-Regel, SYSTEM.md §5).
  • ExportDeps.lineItemValues ist Pflicht: Worker und alle Test-Deps sind angepasst.
  • Rollout: Anfragen, die vor dem Deploy freigegeben, aber noch nicht exportiert wurden, gehen danach mit Positionen raus. Hatte ein echtes ERP den 1.0.0-Body schon gespeichert (Antwort verloren), lehnt es den Retry mit geändertem Body mit 409 ab → Exportfehler, kein Duplikat. Im Pilot (Mock) folgenlos; vor einem echten ERP die Export-Warteschlange vor dem Deploy leeren.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1

… position values block approval (#46, test-first)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
Adds the optional lineItems array to the ERP contract, builds it from the
latest run's positions with item corrections, and refuses the approval when
a position value, the position count or the body size would break the
contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
…r stale item corrections (#46 review)

Too many positions or a too large body cannot be fixed by a correction,
so the clerk gets export_too_large instead of value_too_long. The export
test now checks the refusal codes and that the exported positions equal
what the review shows after a newer run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1

Fluory commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Frischer Review (read-only Subagent) – Ergebnis und Umgang

Nichts blockierend. Geprüft: Export = Prüfansicht (gleicher Lauf, gleiche Reihenfolge, gleiche Korrekturregel), deterministischer Body bei Retries, Exactly-once unverändert (Row-Lock, Export-Zeile, Idempotency-Key), Vertrag yaml = zod = Typen, Mock validiert mit dem gemeinsamen quoteRequestSchema, Body-Schätzung sicher (≈200 Byte Hülle vs. 1024 Byte Reserve), alle Lesezugriffe im Tenant-Kontext.

Befund Umgang
should – >200 Positionen / Body zu groß zeigte „Wert zu lang – bitte korrigieren“, obwohl sich das nicht korrigieren lässt behoben in 3efdce3: eigener Code export_too_large mit passender Meldung („bitte ablehnen und direkt im ERP erfassen“); Integrationstest
should – vor dem Deploy freigegebene Anfragen werden danach mit Positionen exportiert; ein echter ERP würde einen Retry mit geändertem Body mit 409 ablehnen im PR unter Risiken dokumentiert (Rollout-Hinweis); keine Duplikate möglich, schlimmstenfalls Exportfehler mit „Erneut verarbeiten“
should – kein Test, dass eine Positionskorrektur vor einem neueren Lauf ignoriert wird, und kein Abgleich Export ↔ Prüfansicht behoben in 3efdce3: neuer Integrationstest; Mutationsprobe (Regel entfernt) → Test rot
nit – Ablehnungstest prüfte nur den Typ behoben: prüft jetzt value_too_long
nit – kein Mock-Test für ungültige Positionen (400) bleibt: der Mock nutzt dasselbe zod-Schema, das payload.test.ts und die Drift-Tests abdecken
nit – Testdaten nutzen Kopf-Texte als Positionswerte bleibt: die Zitate müssen in den festen Fixture-Segmenten stehen (Belegpflicht); fachlich egal

verify nach den Änderungen grün (unit 159, integration 118).


Generated by Claude Code

@Fluory
Fluory marked this pull request as ready for review September 24, 2026 11:19
@Fluory
Fluory merged commit f9af8b9 into main Sep 24, 2026
2 checks passed
@Fluory Fluory mentioned this pull request Oct 2, 2026
4 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(export): send reviewed line items to the ERP (contract v2)

2 participants