DKNet.Templates

Purchase Orders — Endpoints

Part of Purchase Orders.

Base path: /v1/purchase-orders. Every route is a literal MapPost/MapGet/MapPut/MapDelete call in PurchaseOrderV1Endpoint — none of it goes through the generic CRUD route-registration helper (see Automated Products for that style).

🔌 Endpoints

Verb Path Purpose Auth
POST / Create a purchase order authenticated caller with a resolvable acting user and ownership key
GET / List purchase orders, paged, optionally filtered by customer name authenticated caller; sees only its own orders
GET /{id} Get one purchase order by id authenticated caller; its own orders only
PUT /{id} Change an order’s amount authenticated caller; its own orders only
POST /{id}/cancel Cancel an order authenticated caller; its own orders only
DELETE /{id} Delete an order authenticated caller; its own orders only

Whether an anonymous call is rejected before it reaches any of the above depends on FeatureManagement:RequireAuthorization — off in Development, on elsewhere; see API Request Pipeline. Every write route resolves the acting user from the caller’s ClaimTypes.Name claim via [FromClaim] and AddContextualRequestPopulation; a payload byUser field is never read for this.

POST /

Creates a purchase order for a customer and amount.

GET /

Lists purchase orders, paged and optionally filtered to one customer. Returns only the caller’s own orders.

GET /{id}

Gets one purchase order by id.

PUT /{id}

Changes an order’s amount. No other field can be changed through this route.

POST /{id}/cancel

Cancels an order. Refuses a second cancel rather than silently repeating it.

DELETE /{id}

Deletes an order outright — there is no soft delete.

⚠️ Errors & limits

Two different error body shapes appear on this feature, depending on which layer refuses the request:

Paging: pageIndex defaults to 1, pageSize to 20, capped at 100; both are validated, not clamped — an out-of-range value is a 400, not a silently adjusted page. The list response is a bare array with no total count or “has more” flag; a caller that needs to know when to stop paging has to keep requesting pages until one comes back shorter than pageSize.

Filtering: customerName is an exact, case-sensitive match (SpecGetPurchaseOrder), not a “contains” search.

Ordering: the list query applies no explicit sort — row order is whatever the database returns, not guaranteed stable from one page to the next.

Consistency: every route reads and writes the same PostgreSQL database directly; there is no cache and nothing eventually consistent here. The one asynchronous step is the domain event delivery to PurchaseOrderCreatedEventHandler, which only logs and cannot be observed through the HTTP API.

Retry safety: POST / is the only route that needs — and gets — idempotency protection. PUT /{id} is naturally safe to retry (re-applying the same amount is harmless); POST /{id}/cancel and DELETE /{id} are safe to retry because a second call answers 409/404 instead of repeating the effect.

Rate limiting, CORS, authentication and versioning are handled once for the whole application — see API Request Pipeline.

This feature deliberately does not: soft-delete (delete is a hard DELETE FROM), publish its event to an external broker (see Integration), or expose a partial update (PUT only ever changes amount).