Consolidate region management APIs

This commit is contained in:
2026-09-01 15:34:58 +05:30
parent 82a32de966
commit 7cf272e311
16 changed files with 880 additions and 509 deletions
+39 -42
View File
@@ -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.)
---