Add ASP.NET Core backend hosting business rules; FE consumes via typed client

Move the authoritative business rules off the frontend into a real backend,
realising the BFF-lite + decision-DTO design (ADR-0001) that until now lived
only in static mock JSON.

Backend (backend/):
- ASP.NET Core (.NET 10) minimal API, contract-first, Swagger UI at /swagger.
- DDD Domain/ rules layer: profession derivation + applicable policy questions
  (DiplomaRules), herregistratie eligibility + reason (HerregistratieRule),
  scholing threshold (IntakePolicy), submit rejections + reference generation
  (SubmissionRules). In-memory seeded data, ProblemDetails (RFC 7807) errors.
- 27 xUnit tests: rule units + endpoint integration incl. BRP no-address and
  DUO not-found fallbacks and 422 submit paths.

Frontend (only infrastructure/ + contracts/ change, as the architecture promised):
- NSwag-generated typed client (api-client.ts), routed through Angular HttpClient
  via a small fetch adapter so the ?scenario= interceptor still applies.
- GET adapters use resource({ loader: client.x }); submit commands call the client
  and map ProblemDetails -> err. The hardcoded uren==0 / manual-diploma rules are
  deleted (now server-side). Domain, stores, UI and format validators unchanged.
- Deleted the now-dead public/mock/*.json.

Tooling/docs:
- npm start proxies /api -> backend; npm run gen:api regenerates the client;
  docker compose up runs both (bind mounts use :z for SELinux/Fedora).
- backend/README.md walkthrough: adding a policy question is a one-file backend
  change, no FE change, no client regen. Updated CLAUDE.md + ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 20:05:53 +02:00
parent 4e9af05cc1
commit cf570a8132
62 changed files with 2618 additions and 394 deletions

102
backend/README.md Normal file
View File

@@ -0,0 +1,102 @@
# BIG-register BFF (ASP.NET Core)
The backend that hosts the **business rules** for the BIG-register portal. The
frontend renders the decisions this service computes; it does not recompute them
(BFF-lite + decision DTOs — see `../docs/architecture/0001-bff-lite-decision-dtos.md`).
No database, no real BRP/DUO: data is in-memory and seeded (`Data/SeedData.cs`),
but the endpoints, DTOs, status codes and error envelope are production-shaped.
## Run
### Everything (docker-compose, from repo root)
```bash
docker compose up
```
- App: <http://localhost:4200>
- Swagger UI: <http://localhost:5000/swagger>
### Backend only (local)
```bash
cd backend
dotnet run --project src/BigRegister.Api
# → http://localhost:5000/swagger
```
### Frontend against a local backend
```bash
npm start # ng serve, proxies /api → http://localhost:5000 (proxy.conf.json)
```
### Tests
```bash
cd backend && dotnet test # rule unit tests + endpoint integration tests
```
## API
| Method | Route | Purpose |
|---|---|---|
| GET | `/api/dashboard-view` | registration + person + computed herregistratie decision |
| GET | `/api/notes` | specialisms / aantekeningen |
| GET | `/api/brp/address` | BRP address lookup (`gevonden:false` = no address) |
| GET | `/api/duo/diplomas` | diplomas with derived profession + applicable policy questions, + manual fallback |
| GET | `/api/intake/policy` | scholing threshold (config value) |
| POST | `/api/registrations` | submit registration → reference, or 422 (manual diploma) |
| POST | `/api/herregistraties` | submit re-registration → reference, or 422 (0 hours) |
| POST | `/api/intakes` | submit intake → reference, or 422 (0 hours) |
Rejections use **ProblemDetails (RFC 7807)** with status **422**.
## Where the rules live (`src/BigRegister.Api/Domain/`)
- `Diplomas/DiplomaRules.cs` — profession derivation + which policy questions apply.
- `Registrations/HerregistratieRule.cs` — eligibility + reason + status invariant.
- `Intake/IntakePolicy.cs` — scholing threshold.
- `Submissions/SubmissionRules.cs` — submit rejections + reference generation.
## Typed client (NSwag)
The frontend calls this API through a generated TypeScript client. Regenerate it
from the contract after a **shape** change:
```bash
npm run gen:api # builds backend → swagger.json → src/app/shared/infrastructure/api-client.ts
```
## Maintainability: changing a policy is one backend change
**Goal:** require every *Verpleegkundige* diploma to confirm a Dutch skills
assessment. This is a new policy question on a diploma type.
Edit **one file**`Domain/Diplomas/DiplomaRules.cs`:
```diff
public static IReadOnlyList<PolicyQuestion> QuestionsFor(Diploma d)
{
var questions = new List<PolicyQuestion>();
if (d.Engelstalig)
questions.Add(NlTaalEngelstalig);
+ if (d.Opleiding == "verpleegkunde")
+ questions.Add(new PolicyQuestion(
+ "bekwaamheid",
+ "Heeft u in de afgelopen vijf jaar een bekwaamheidstoets afgelegd?",
+ QuestionType.JaNee));
return questions;
}
```
Rebuild the backend (`docker compose up` or `dotnet run`). The new question now
appears in the registration wizard for HBO-Verpleegkunde.
- **No frontend change.** The FE renders whatever questions the API returns.
- **No client regeneration.** The wire shape (`PolicyQuestionDto`) is unchanged —
only the data behind it. `npm run gen:api` is only needed when a DTO *shape* changes.
Add a unit test for the new rule in `tests/BigRegister.Tests/RuleTests.cs` and
you're done.