From 8b84c8aeb3d89910c0b2151207b3b10b24798383 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Sun, 30 Aug 2026 22:51:46 +0530 Subject: [PATCH] Document the implemented region management endpoints Updates mainPlan.md's Region Management table, 10_region_management.md's status/open-questions/implementation-notes sections, and BACKEND_API_REFERENCE.md's Regions section to reflect the newly added target/budget/agency-map/tax-rule/hierarchy/dashboard endpoints, and flags that the EF Core migration for the new tables is still pending. Co-Authored-By: Claude Sonnet 5 --- Distribution/docs/10_region_management.md | 67 ++++++++++++------ Distribution/docs/mainPlan.md | 14 ++-- sharedDocs/BACKEND_API_REFERENCE.md | 82 +++++++++++++++++++++++ 3 files changed, 137 insertions(+), 26 deletions(-) diff --git a/Distribution/docs/10_region_management.md b/Distribution/docs/10_region_management.md index 5ed01e5..b586439 100644 --- a/Distribution/docs/10_region_management.md +++ b/Distribution/docs/10_region_management.md @@ -1,6 +1,6 @@ # 10 — Region Management (Beyond CRUD) -**Status**: Not Started +**Status**: Implemented (except `assignAreaToRegion`, blocked on the `Area` entity) **Depends On**: 00 — Existing Gap Fixes --- @@ -24,48 +24,77 @@ CRUD: targets, budgets, agency mapping, tax rules, hierarchy, and dashboarding. ### Regional Target / Budget -`RegionId`, `Period` (e.g. `2026-08`), `TargetValue`, `BudgetAmount`. +Implemented as two separate entities, `RegionalTarget` and `RegionalBudget` +(`Models/RegionalTarget.cs`, `Models/RegionalBudget.cs`), each with `FK_RegionId`, `Period` +(string, `yyyy-MM`), and `TargetValue`/`BudgetAmount` respectively. A unique index on +`(FK_RegionId, Period)` means each region has at most one target and one budget row per period; +setting the same period again updates it in place rather than inserting a duplicate. ### Agency-Region Map -`RegionId`, `AgencyId` — depends on whether "Regional Agency" becomes a distinct entity or stays a -`Role` (see Open Questions). +Implemented as `RegionAgencyMap` (`Models/RegionAgencyMap.cs`): `FK_RegionId`, `FK_AgencyUserId`, +unique on the pair. "Regional Agency" stayed a `Role`, not a distinct entity (see Open Questions, +resolved) — `FK_AgencyUserId` points at an existing `User` who holds the `RegionalAgency` role, +the same convention already used by `Claim.FK_AgencyUserId` (§18). ### Regional Tax Rule -`RegionId`, `RuleConfig` (JSON) — deferred to whenever tax/pricing rule overrides are prioritized. +Implemented as `RegionalTaxRule` (`Models/RegionalTaxRule.cs`): `FK_RegionId` (unique — one rule +per region), `RuleConfig` (raw JSON string). Nothing in pricing/tax calculation reads this yet; +it's stored for a future pricing-rule integration. --- ## API Endpoints -| Function reference | Endpoint intent | -|---|---| -| `assignAreaToRegion(areaId, regionId)` | Blocked — no `Area` entity exists (see [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)) | -| `setRegionalTarget(regionId, period, targetValue)` | `PUT /api/regions/{id}/targets/{period}` | -| `setRegionalBudget(regionId, period, budgetAmount)` | `PUT /api/regions/{id}/budgets/{period}` | -| `mapAgencyToRegion(agencyId, regionId)` | `POST /api/regions/{id}/agencies` | -| `setRegionalTaxRule(regionId, ruleConfig)` | `PUT /api/regions/{id}/tax-rule` | -| `getRegionHierarchy(regionId)` | `GET /api/regions/{id}/hierarchy` | -| `getRegionDashboard(regionId, period)` | `GET /api/regions/{id}/dashboard?period=...` | +| Function reference | Endpoint | Status | +|---|---|---| +| `assignAreaToRegion(areaId, regionId)` | — | Blocked — no `Area` entity exists (see [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)) | +| `setRegionalTarget(regionId, period, targetValue)` | `PUT /api/regions/{id}/targets/{period}` | Implemented | +| `setRegionalBudget(regionId, period, budgetAmount)` | `PUT /api/regions/{id}/budgets/{period}` | Implemented | +| `mapAgencyToRegion(agencyId, regionId)` | `POST /api/regions/{id}/agencies` | Implemented | +| `setRegionalTaxRule(regionId, ruleConfig)` | `PUT /api/regions/{id}/tax-rule` | Implemented | +| `getRegionHierarchy(regionId)` | `GET /api/regions/{id}/hierarchy` | Implemented (region → routes → store ids; no `Area` level) | +| `getRegionDashboard(regionId, period)` | `GET /api/regions/{id}/dashboard?period=...` | Implemented (real-time aggregation; see Open Questions, resolved) | + +All request/response DTOs live in `Features/Region/RegionDTOs.cs`; routing and access checks are +in `RegionController.cs` and `RegionService.cs`. --- ## Business Rules - Only `RegionalManager` and above (per role level in `Data/Seed/RoleAndPermissionSeed.cs`) can - set targets/budgets for their own region; only `RegionalAgency` can set them across regions. + set targets/budgets/tax-rule for their own region; only `RegionalAgency` can set them across + regions. Implemented via `RegionService.CanManageRegionAsync`: role level ≥ 60 (`RegionalAgency`) + is allowed unconditionally, role level ≥ 50 (`RegionalManager`) requires a matching + `UserRegionScope` row for that region, anything lower is rejected. The same gate is applied to + `mapAgencyToRegion`. +- `mapAgencyToRegion` additionally rejects the target user if their role level is below the + `RegionalAgency` threshold (60) — you can only map a user who actually holds (or outranks) that + role. --- ## Open Questions -- Is "Regional Agency" a business entity distinct from the `RegionalAgency` role (e.g. a contracted - third party with its own record), or is the role sufficient? -- Does `getRegionDashboard` need real-time aggregation, or a scheduled/cached rollup? +- ~~Is "Regional Agency" a business entity distinct from the `RegionalAgency` role...?~~ Resolved: + kept as a `Role` on `User`; no new agency entity, consistent with `Claim.FK_AgencyUserId`. +- ~~Does `getRegionDashboard` need real-time aggregation, or a scheduled/cached rollup?~~ Resolved: + real-time aggregation from `Order`/`Store`/`Route` for now; revisit if/when region-wide dashboard + queries become a performance concern at scale. --- ## Implementation Notes -> Fill this section in during planning discussion before writing any code. +- Backend: `Models/RegionalTarget.cs`, `Models/RegionalBudget.cs`, `Models/RegionAgencyMap.cs`, + `Models/RegionalTaxRule.cs`, `Data/AppDBContext.cs` (DbSets + unique indexes), + `Features/Region/{IRegionRepository,RegionRepository,RegionService,RegionController,RegionDTOs}.cs`. +- Frontend: `distribution_fe/types/api.ts` (response types), `distribution_fe/lib/api/master.ts` + (`regions.setTarget/setBudget/mapAgency/setTaxRule/hierarchy/dashboard`), and a new + `RegionManageSheet` dialog (`distribution_fe/app/(dashboard)/network/regions/_components/region-manage-sheet.tsx`) + wired into the regions table's row actions. +- Outstanding: an EF Core migration for the four new tables still needs to be generated and + applied (blocked locally by the running dev server locking the build output) — no schema exists + in the database yet even though the code is complete. diff --git a/Distribution/docs/mainPlan.md b/Distribution/docs/mainPlan.md index 24f5d6e..d1378a0 100644 --- a/Distribution/docs/mainPlan.md +++ b/Distribution/docs/mainPlan.md @@ -116,13 +116,13 @@ here — see `SRS_User_Management_Module.pdf`. | Function | What it does | Status | |---|---|---| | `createRegion(name, boundaries)` | Creates a new region record. | Partially implemented — `RegionController` supports create with `Name` only, no `boundaries` field. | -| `assignAreaToRegion(areaId, regionId)` | Links an existing area under a region. | Not implemented — no `Area` entity exists in the schema. | -| `setRegionalTarget(regionId, period, targetValue)` | Sets the sales target for a region for a given period. | Not implemented. | -| `setRegionalBudget(regionId, period, budgetAmount)` | Allocates budget at the regional level. | Not implemented. | -| `mapAgencyToRegion(agencyId, regionId)` | Assigns a Regional Agency's contract scope to a region. | Not implemented — `RegionalAgency` is currently a `Role`, not a distinct agency entity; scope is via `UserRegionScope`. | -| `setRegionalTaxRule(regionId, ruleConfig)` | Overrides tax/pricing rules for a specific region. | Not implemented. | -| `getRegionHierarchy(regionId)` | Returns all areas/territories nested under a region. | Not implemented — see [user_management_PLAN.md](../user_management_PLAN.md) territory hierarchy prerequisite. | -| `getRegionDashboard(regionId, period)` | Aggregated KPIs: sales, fill rate, agency performance for the region. | Not implemented. | +| `assignAreaToRegion(areaId, regionId)` | Links an existing area under a region. | Not implemented — no `Area` entity exists in the schema (see [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)). | +| `setRegionalTarget(regionId, period, targetValue)` | Sets the sales target for a region for a given period. | Implemented — `PUT /api/regions/{id}/targets/{period}`, backed by the `RegionalTarget` entity. Restricted to `RegionalManager`+ with region scope, or `RegionalAgency` across any region. | +| `setRegionalBudget(regionId, period, budgetAmount)` | Allocates budget at the regional level. | Implemented — `PUT /api/regions/{id}/budgets/{period}`, backed by the `RegionalBudget` entity. Same access rule as `setRegionalTarget`. | +| `mapAgencyToRegion(agencyId, regionId)` | Assigns a Regional Agency's contract scope to a region. | Implemented — `POST /api/regions/{id}/agencies`, backed by the `RegionAgencyMap` entity. `RegionalAgency` is still a `Role`, not a distinct agency entity — `agencyId` is the `Guid` of an existing `User` holding that role (consistent with `Claim.FK_AgencyUserId`, §18); the endpoint rejects a user who doesn't hold the role. | +| `setRegionalTaxRule(regionId, ruleConfig)` | Overrides tax/pricing rules for a specific region. | Implemented — `PUT /api/regions/{id}/tax-rule`, backed by the `RegionalTaxRule` entity (`RuleConfig` stored as a raw JSON string, not yet consumed by pricing/tax logic elsewhere). | +| `getRegionHierarchy(regionId)` | Returns all areas/territories nested under a region. | Partially implemented — `GET /api/regions/{id}/hierarchy` returns the region's routes and each route's store ids; no `Area` level since that entity doesn't exist yet. | +| `getRegionDashboard(regionId, period)` | Aggregated KPIs: sales, fill rate, agency performance for the region. | Partially implemented — `GET /api/regions/{id}/dashboard?period=...` returns real-time order count, actual sales, target, and budget for the period; no fill-rate or agency-performance metrics yet. | ### 3. Route Management diff --git a/sharedDocs/BACKEND_API_REFERENCE.md b/sharedDocs/BACKEND_API_REFERENCE.md index 31a58a4..350d3ee 100644 --- a/sharedDocs/BACKEND_API_REFERENCE.md +++ b/sharedDocs/BACKEND_API_REFERENCE.md @@ -506,6 +506,88 @@ 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. + +| 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 | +| `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:** +```json +// PUT /api/regions/{id}/targets/{period} +{ "targetValue": 500000 } +``` +```json +{ "regionId": "guid", "period": "2026-08", "targetValue": 500000 } +``` + +**Set budget request / response:** +```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}" } +``` + +**Hierarchy response:** +```json +{ + "regionId": "guid", + "regionName": "string", + "routes": [ + { "routeId": "guid", "routeName": "string", "storeIds": ["guid", "..."] } + ] +} +``` + +**Dashboard response:** +```json +{ + "regionId": "guid", + "period": "2026-08", + "targetValue": 500000, // null if no target set for the period + "budgetAmount": 200000, // null if no budget set for the period + "orderCount": 42, + "actualSales": 187500.00, + "lastOrderDate": "datetime | null" +} +``` + +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". + +No EF Core migration for `RegionalTarget`/`RegionalBudget`/`RegionAgencyMap`/`RegionalTaxRule` has +been applied yet — these endpoints will 500 against a database that hasn't run the pending +migration. + --- ### 6.6 Routes