# procurex-app

Spring-Boot-Bootstrap-Modul (Confluence Seite 09): bindet alle Bounded-Context-Module unter
`com.procurex.*` zu einem einzigen deploybaren Artefakt (modularer Monolith).

## Voraussetzung: enterprise-infrastructure

`enterprise-infrastructure` (`C:\Entwicklung\lernen\enterprise-workspace\shared\enterprise-infrastructure`) muss vorher mit dem Profil
`postgres` laufen:

```
docker compose --profile postgres up -d postgres
```

Datenbank `procurex_db` + eigener Benutzer `procurex_user` sind in
`shared/enterprise-infrastructure/postgres/init-project-databases.sh` registriert (Konvention: ein Eintrag pro
angeschlossenem Projekt, siehe `shared/enterprise-infrastructure/README.md`). Läuft die Postgres-Instanz bereits
mit bestehenden Daten (Init-Skript läuft nur beim allerersten Start eines leeren Volumes), wird
die Datenbank stattdessen einmalig direkt angelegt:

```
docker exec -e PGPASSWORD=admin shared-postgres psql --username admin --dbname postgres \
  -c "CREATE USER procurex_user WITH PASSWORD 'aendern-vor-erstem-start-procurex';" \
  -c "CREATE DATABASE procurex_db OWNER procurex_user;" \
  -c "REVOKE ALL ON DATABASE procurex_db FROM PUBLIC;" \
  -c "GRANT ALL PRIVILEGES ON DATABASE procurex_db TO procurex_user;"
```

## Voraussetzung: Keycloak (PRX-19, Umsetzung)

`enterprise-infrastructure` mit dem Profil `keycloak` (braucht zusaetzlich `postgres`):

```
docker compose --profile postgres --profile keycloak up -d keycloak
```

Realm `procurex`, Clients und Rollen einmalig anlegen — siehe `../keycloak/README.md`
(Realm-Export dort zum Importieren, oder Admin-Konsole `http://keycloak.localhost`, admin/admin).

## Starten – lokal mit Maven

Vor dem Start muss `PROCUREX_DB_PASSWORD` gesetzt sein. Es gibt absichtlich kein
`changeme`-Fallback mehr; ein fehlendes Datenbank-Secret führt zu einem sichtbaren
Konfigurationsfehler statt zu einem scheinbar erfolgreichen unsicheren Start.

```
mvn -pl procurex-app -am spring-boot:run
```

Nutzt per Default den Host-Port `5433` von `enterprise-infrastructure` (siehe `application.yml`).
Umgebungsvariablen: `PROCUREX_DB_HOST`, `PROCUREX_DB_PORT`, `PROCUREX_DB_NAME`, `PROCUREX_DB_USER`,
`PROCUREX_DB_PASSWORD`, `PROCUREX_CORS_ALLOWED_ORIGINS`, `PROCUREX_OIDC_ISSUER_URI`,
`PROCUREX_OIDC_JWK_SET_URI`.

## Security (PRX-19, Umsetzung)

`procurex-app` ist OAuth2 Resource Server gegen Keycloak (`SecurityConfig`). `/actuator/health/**`
und `/actuator/prometheus` sind offen, alle `/api/**`-Endpunkte brauchen ein gueltiges JWT;
schreibende Endpunkte zusaetzlich die passende Rolle (`ROLE_SUPPLIER_ADMIN`, `ROLE_BUYER`,
`ROLE_APPROVER`, `ROLE_RECEIVING`, `ROLE_FINANCE` — `ROLE_ADMIN` darf ueberall). Rollen kommen aus
dem `realm_access.roles`-Claim des Access Tokens. Live verifiziert (Token via Direct-Grant-Testlauf,
siehe Commit-Historie): korrekte Rolle → Erfolg/fachlicher Fehler, falsche Rolle → 403, kein
Token → 401.

**Gefundener und behobener Bug dabei:** `@PathVariable`-Parameter ohne expliziten Namen scheiterten
zur Laufzeit (`IllegalArgumentException`), weil das Root-POM nicht von `spring-boot-starter-parent`
erbt und daher `-parameters` nicht automatisch beim Compile gesetzt wird — behoben über
`maven.compiler.parameters=true` im Root-`pom.xml`. War vorher unbemerkt, weil die Unit-Tests die
Controller-Schicht nicht durchlaufen (nur Domain/Application-Service, siehe Root-README).

## Voraussetzung: Kafka (PRX-17, Umsetzung)

`enterprise-infrastructure` mit dem Profil `kafka`:

```
docker compose --profile kafka up -d kafka kafka-ui
```

## Events (PRX-17, Umsetzung)

Transactional-Outbox-Pattern (Confluence Seite 10): jede fachliche Aenderung schreibt in
derselben Transaktion einen Outbox-Eintrag ins eigene Schema (`<schema>.outbox_event`); ein
`@Scheduled`-Relay pro Modul (`com.procurex.common.outbox.AbstractOutboxRelay`, alle 2s) sendet
unveroeffentlichte Eintraege an Kafka und markiert sie danach als `published`. Live verifiziert
(Nachricht via `kafka-console-consumer` gegenlesen).

| Topic | Modul | Ausgeloest durch |
|---|---|---|
| `procurex.ordering.order-submitted.v1` | procurex-ordering | PRX-2 Warenkorb absenden |
| `procurex.approval.order-approved.v1` | procurex-approval | PRX-5 Genehmigung |
| `procurex.approval.order-rejected.v1` | procurex-approval | PRX-5 Ablehnung |
| `procurex.receiving.goods-received.v1` | procurex-receiving | PRX-10 Wareneingang |
| `procurex.invoicing.invoice-matched.v1` | procurex-invoicing | PRX-11 Rechnungspruefung ohne Abweichung |
| `procurex.invoicing.invoice-discrepancy-flagged.v1` | procurex-invoicing | PRX-9 Mengenabweichung |

**Gefundener und behobener Bug dabei:** Alle vier Module hatten anfangs eine Klasse namens
`OutboxEvent`/`OutboxEventRepository` im jeweils eigenen Package — Spring registriert Data-JPA-
Repository-Beans aber standardmaessig nach einfachem Klassennamen (nicht vollqualifiziert), und
Hibernate ebenso den Entity-Namen, was zu Bean-Kollisionen beim Start fuehrte
(`BeanDefinitionOverrideException`). Behoben durch eindeutige Namen pro Modul
(`OrderingOutboxEvent`, `ApprovalOutboxEvent`, `ReceivingOutboxEvent`, `InvoicingOutboxEvent`).

**Erster Konsument (PRX-34, Umsetzung):** `procurex-audit` (`AuditLogConsumer`) schreibt alle
sechs Topics unveraendert in `audit.audit_log_entry` — rein additiv, beeinflusst keine fachlichen
Ablaeufe. Idempotenz: `AbstractOutboxRelay` haengt die eigene Outbox-Event-ID als Kafka-Header
`event-id` an (nicht im Payload, der bleibt reine fachliche DTO); ein eindeutiger DB-Index auf
`event_id` faengt doppelte Zustellung bei Redelivery ab, unabhaengig von Kafka-Offsets. Live
verifiziert: Testnachricht mit `event-id`-Header manuell publiziert → landet in
`audit_log_entry`; dieselbe Nachricht erneut publiziert → weiterhin genau 1 Zeile (Dedup
bestaetigt). Aeltere, vor diesem Feature publizierte Nachrichten ohne den Header werden erkannt
und uebersprungen (geloggt als WARN), nicht fehlerhaft verarbeitet.

Im Test deaktiviert (`procurex.audit.consumer-enabled: false`, siehe
`procurex-app/src/test/resources/application.yml`) aus demselben Grund wie das Outbox-Relay:
Tests sollen nicht von einem erreichbaren Kafka-Broker abhaengen.

Weiterhin keine DLQ-Umsetzung (nur die Namenskonvention `<topic>.dlq` ist in Confluence Seite 10
dokumentiert) — bei diesem einen, rein additiven Consumer bisher nicht notwendig.

## Integrationstests (PRX-8, PRX-24, Umsetzung)

Drei Testklassen unter `src/test/java/com/procurex/app` decken echtes DB-Verhalten ab, das reine
Mockito-Unit-Tests in den Einzelmodulen strukturell nicht pruefen koennen (dort existiert keine
echte Datenbank/Transaktion):

| Testklasse | Prueft |
|---|---|
| `ApprovalConcurrencyIntegrationTest` | PRX-8 Optimistic Locking: zwei gleichzeitige Genehmigungsentscheidungen auf derselben Bestellung — genau eine gewinnt, die andere bekommt `ConflictException` |
| `UniqueConstraintIntegrationTest` | DB-Unique-Constraints (Supplier `taxId`, Approval `order_id`) |
| `ProcureToPayFlowIntegrationTest` | Kompletter Procure-to-Pay-Fluss (PRX-2 bis PRX-11) ueber echte, Spring-verwaltete Services statt Mocks — happy path (MATCHED) und Mengenabweichung (DISCREPANCY) |

`IntegrationTestBase` startet dafuer per Testcontainers (`@ServiceConnection`) je Testklasse einen
echten PostgreSQL-16-Container. `@DirtiesContext(AFTER_CLASS)` verhindert, dass Spring nach dem
Container-Stopp eine veraltete dynamische JDBC-URL aus dem Context-Cache wiederverwendet.

**Lokaler Lauf mit Podman:** Docker Desktop 4.84 lieferte dem verwendeten Docker-Java-Client eine
unvollstaendige 400-Antwort. Die stabile lokale Loesung ist Podman 6.x mit einer gestarteten
Podman-Machine und deren Docker-kompatibler Named Pipe. Testcontainers ist dafuer auf 2.0.4
aktualisiert. `enterprise-infrastructure` wird von diesen Tests bewusst nicht verwendet; es bleibt der Laufzeit-
Stack fuer Compose und die manuellen Integrationschecks.

```powershell
podman machine start podman-machine-default
$env:TESTCONTAINERS_RYUK_DISABLED = 'true'
mvn -pl procurex-app -am test `
  "-Dprocurex.integration=true" `
  "-Ddocker.host=npipe:////./pipe/docker_engine"
```

In PowerShell muessen die Properties wegen des Punkts im Namen quotiert werden. Einzelne Tests
koennen mit `-Dtest=ApprovalConcurrencyIntegrationTest` ausgewaehlt werden. Ohne die Eigenschaft
`procurex.integration` aktiviert das `fast-tests`-Profil; damit bleiben die teuren
Testcontainers-Laeufe im normalen Quality-Gate ausgeschlossen.

## Contract-Tests (PRX-35, Umsetzung)

Spring Cloud Contract Verifier generiert aus Contract-DSL-Dateien unter
`src/test/resources/contracts/<modul>/*.groovy` JUnit-5-Tests, die den echten Controller (mit
gemocktem Service, `@WebMvcTest`) gegen die im Contract festgelegte Anfrage/Antwort pruefen —
anders als die Testcontainers-Integrationstests oben braucht das **keine Datenbank** und laeuft
daher unabhaengig von der obigen Docker-Desktop-Einschraenkung, auch in der Jenkins-Pipeline.

| Contract | Prueft |
|---|---|
| `contracts/supplier/shouldCreateSupplier.groovy` | `POST /api/v1/suppliers` liefert 201 mit der erwarteten `SupplierResponse`-Form |
| `contracts/supplier/shouldRejectSupplierWithoutName.groovy` | Fehlender Pflichtname liefert 400 als RFC-7807 Problem Detail mit `errors`-Liste |
| `contracts/requisition/shouldSubmitCart.groovy` | `POST /api/v1/cart/submit` liefert 201 mit `orderId`/Status `PENDING_APPROVAL` |

Je eine Basisklasse pro benoetigter Rolle (`com.procurex.app.contracts.SupplierAdminContractBase`,
`BuyerContractBase`) simuliert die von `SecurityConfig` verlangte Rolle ueber
`SecurityMockMvcRequestPostProcessors.jwt()` (kein echtes Keycloak-Token noetig, der `JwtDecoder`
wird nur fuer die Bean-Verdrahtung gemockt, nie aufgerufen). Jede Basisklasse bringt ihre eigene,
schmale `@SpringBootConfiguration` mit (scannt nur das jeweilige Modul-Package plus
`com.procurex.common.web` fuer den `GlobalExceptionHandler`) — ein Scan von ganz `com.procurex`
wuerde auch fremde Controller und `ProcurexApplication`s `@EnableJpaRepositories` mitziehen und
wieder einen echten Datenbankzugriff erzwingen.

```
mvn -pl procurex-app -am test
```

## CI (PRX-32, Umsetzung)

`Jenkinsfile` im Repo-Root, laeuft im geteilten Jenkins aus `enterprise-infrastructure` (`jenkins.localhost`,
Job `procurex-pipeline`, angelegt per Configuration-as-Code in
`shared/enterprise-infrastructure/jenkins/casc.yaml` — zweiter angeschlossener Job neben dem eines anderen Projekts,
siehe dortige Kommentare zum Multi-Projekt-Muster). Vier Stufen:

1. **Backend: Build & Test** — `mvn test` reactor-weit; `procurex-app`s drei Testcontainers-
Integrationstests sind dabei über das standardmäßig aktive `fast-tests`-Profil ausgenommen (siehe
oben), die Contract-Tests aus PRX-35 laufen normal mit.
2. **Frontend: Install & Test** — `npm ci` + `ng test` (Vitest+jsdom), in einem eigenen
   `node:22-alpine`-Docker-Agent statt dem am Jenkins-Controller fest installierten Node 20 (der
   ist auf das andere angeschlossene Projekt abgestimmt — eigener Agent nur fuer diese Stufen
   vermeidet, das geteilte Jenkins-Image fuer alle anzupassen).
3. **Frontend: Build** — `ng build` (production).
4. **Docker Images** — baut `procurex-app`- und `procurex-frontend`-Images lokal im
   Docker-Daemon des Jenkins-Hosts (ueber den in `shared/enterprise-infrastructure/jenkins/docker-compose.yml`
   dokumentierten Socket-Mount). Kein Registry-Push, kein Deployment — bewusst nicht Teil dieses
   Tickets (siehe PRX-22 fuer Kubernetes/Helm). Der Docker-CLI dort hat keinen `buildx`
   installiert (nur der Legacy-Builder) — `procurex-app/Dockerfile` verzichtet deshalb bewusst auf
   BuildKit-spezifische Features wie `--mount=type=cache`, um auf jedem Docker-Setup zu bauen.

**Voraussetzung:** `procurex-platform` ist ein privates GitHub-Repo, der Checkout braucht daher ein
Jenkins-Credential (`github-procurex-pat`, Typ „Username with password“, Passwort = Fine-grained
GitHub PAT mit `Contents: Read-only` nur fuer dieses Repo) — einmalig angelegt, der Wert steht
bewusst nicht in `casc.yaml` (gleiches Prinzip wie bei den dortigen KUBECONFIG-Credentials).

## Starten – Docker (PRX-21)

Siehe `../docker-compose.yml` im Repo-Root (bindet `procurex-app` und `procurex-frontend` über
das externe `proxy`-Netzwerk an Traefik an). Kurzfassung:

```
cd ..\..\shared\enterprise-infrastructure
docker compose --profile postgres up -d postgres
docker compose up -d traefik

cd ..\..\projects\procurex-platform
cp .env.example .env   # Passwort ggf. anpassen, muss zum enterprise-infrastructure-Eintrag passen
docker compose up -d --build
```

Danach erreichbar unter `http://procurex-api.localhost` (Backend) und `http://procurex.localhost`
(Frontend) – getestet inkl. Flyway-Migrationen, `/actuator/health` und CORS-Preflight von
`procurex.localhost` aus.

## Endpunkte
- `/actuator/health`, `/actuator/prometheus` (Confluence Seite 14)
- Alle `/api/v1/*`-Endpunkte der einzelnen Module, siehe deren jeweiliges README.md
