Consolidate region management APIs
This commit is contained in:
@@ -506,54 +506,44 @@ Array of:
|
||||
}
|
||||
```
|
||||
|
||||
**Beyond CRUD** — targets, budgets, agency mapping, tax rules, hierarchy, and dashboard. These are
|
||||
not gated by the `regions.*` permission codes above; access is checked by role level in
|
||||
`RegionService`: `RegionalManager` (level 50) can act only on a region they have scope over (via
|
||||
`UserRegionScope`), `RegionalAgency` (level 60) can act on any region.
|
||||
**Beyond CRUD** — manage (targets/budgets/agency mapping/tax rules, consolidated), hierarchy, and
|
||||
dashboard. Not gated by the `regions.*` permission codes above.
|
||||
|
||||
> ⚠️ **Authorization temporarily disabled**: `ManageAsync` currently allows any authenticated
|
||||
> caller — the previous role-level gate (`RegionalManager`, level 50, scoped via
|
||||
> `UserRegionScope`; `RegionalAgency`, level 60, any region) was deliberately stubbed out
|
||||
> (`CanManageRegionAsync` always returns `true`) pending a decision on the role model for this
|
||||
> endpoint. Re-enabling it is tracked as a TODO in `RegionService.cs`.
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `PUT` | `/api/regions/{id}/targets/{period}` | Set the sales target for a region for a period (`period` = `yyyy-MM`) |
|
||||
| `PUT` | `/api/regions/{id}/budgets/{period}` | Set the budget for a region for a period |
|
||||
| `POST` | `/api/regions/{id}/agencies` | Map an existing user (holding the `RegionalAgency` role) to this region |
|
||||
| `PUT` | `/api/regions/{id}/tax-rule` | Set/replace the region's tax rule config |
|
||||
| `PUT` | `/api/regions/{id}/manage` | Set target/budget/agency-map/tax-rule in one call — replaces the old separate targets/budgets/agencies/tax-rule endpoints |
|
||||
| `GET` | `/api/regions/{id}/hierarchy` | Region's routes and each route's store ids |
|
||||
| `GET` | `/api/regions/{id}/dashboard?period=yyyy-MM` | Real-time order count/sales/target/budget for the period |
|
||||
|
||||
**Set target request / response:**
|
||||
**Manage request** — every field is optional; only the sub-actions whose fields are present get
|
||||
applied, so a caller can update just one thing or several in a single request. `period` is
|
||||
required alongside `targetValue` and/or `budgetAmount` (both are silently skipped without it):
|
||||
```json
|
||||
// PUT /api/regions/{id}/targets/{period}
|
||||
{ "targetValue": 500000 }
|
||||
```
|
||||
```json
|
||||
{ "regionId": "guid", "period": "2026-08", "targetValue": 500000 }
|
||||
// PUT /api/regions/{id}/manage
|
||||
{
|
||||
"period": "2026-08", // required for targetValue/budgetAmount
|
||||
"targetValue": 500000, // optional
|
||||
"budgetAmount": 200000, // optional
|
||||
"agencyUserId": "guid", // optional — must be a User with the RegionalAgency role
|
||||
"ruleConfig": "{\"rate\": 0.15}" // optional — arbitrary JSON stored as a string, not yet consumed by pricing
|
||||
}
|
||||
```
|
||||
|
||||
**Set budget request / response:**
|
||||
**Manage response** — each field is `null` if that sub-action wasn't requested:
|
||||
```json
|
||||
// PUT /api/regions/{id}/budgets/{period}
|
||||
{ "budgetAmount": 200000 }
|
||||
```
|
||||
```json
|
||||
{ "regionId": "guid", "period": "2026-08", "budgetAmount": 200000 }
|
||||
```
|
||||
|
||||
**Map agency request / response:**
|
||||
```json
|
||||
// POST /api/regions/{id}/agencies
|
||||
{ "agencyUserId": "guid" } // must be a User with the RegionalAgency role
|
||||
```
|
||||
```json
|
||||
{ "regionId": "guid", "agencyUserId": "guid" }
|
||||
```
|
||||
|
||||
**Set tax rule request / response:**
|
||||
```json
|
||||
// PUT /api/regions/{id}/tax-rule
|
||||
{ "ruleConfig": "{\"rate\": 0.15}" } // arbitrary JSON stored as a string; not yet consumed by pricing
|
||||
```
|
||||
```json
|
||||
{ "regionId": "guid", "ruleConfig": "{\"rate\": 0.15}" }
|
||||
{
|
||||
"regionId": "guid",
|
||||
"target": { "regionId": "guid", "period": "2026-08", "targetValue": 500000 } | null,
|
||||
"budget": { "regionId": "guid", "period": "2026-08", "budgetAmount": 200000 } | null,
|
||||
"agencyMap": { "regionId": "guid", "agencyUserId": "guid" } | null,
|
||||
"taxRule": { "regionId": "guid", "ruleConfig": "{\"rate\": 0.15}" } | null
|
||||
}
|
||||
```
|
||||
|
||||
**Hierarchy response:**
|
||||
@@ -576,13 +566,20 @@ not gated by the `regions.*` permission codes above; access is checked by role l
|
||||
"budgetAmount": 200000, // null if no budget set for the period
|
||||
"orderCount": 42,
|
||||
"actualSales": 187500.00,
|
||||
"lastOrderDate": "datetime | null"
|
||||
"lastOrderDate": "datetime | null",
|
||||
"taxRuleConfig": "{\"rate\": 0.15}" | null, // current tax rule, independent of period
|
||||
"agencyUserIds": ["guid", "..."] // all agency users currently mapped to this region
|
||||
}
|
||||
```
|
||||
`taxRuleConfig` and `agencyUserIds` are not period-scoped — they reflect current state regardless
|
||||
of the `period` query param, added so the frontend's region-manage dialog has something to render
|
||||
after calling `manage` (previously the dashboard only echoed back target/budget, leaving tax-rule
|
||||
and agency-mapping changes invisible in the UI after saving).
|
||||
|
||||
All four of these endpoints return `404` if the region doesn't exist, the caller lacks the
|
||||
required region scope, or (for agency mapping) the target user doesn't hold the `RegionalAgency`
|
||||
role — the response body carries no distinction between "not found" and "forbidden".
|
||||
All three of these endpoints return `404` if the region doesn't exist, or (for `manage`, when
|
||||
`agencyUserId` is supplied) the target user doesn't hold the `RegionalAgency` role — the response
|
||||
body carries no distinction between "not found" and "invalid agency user". (The former
|
||||
"forbidden due to region scope" case doesn't currently apply — see the authorization note above.)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user