# 10 — Region Management (Beyond CRUD) **Status**: Implemented (except `assignAreaToRegion`, blocked on the `Area` entity) **Depends On**: 00 — Existing Gap Fixes --- ## Goal Region CRUD already exists (`Features/Region`). This step covers everything in [mainPlan.md §2](./mainPlan.md#2-region-management) that isn't plain CRUD: targets, budgets, agency mapping, tax rules, hierarchy, and dashboarding. --- ## Background / Context `Region` today has only a `Name`. Nothing points to it except `Route.FK_RegionId`. There is no `Area` level (see Open Question in [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)). --- ## Data Model Changes ### Regional Target / Budget 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 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 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 | 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)). Stand-in: `PUT /api/routes/{id}/region` assigns/reassigns an existing route directly to a region (see [11_route_sales_management.md](./11_route_sales_management.md)). | | `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/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...?~~ 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 - 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. - The EF Core migration `20260831050805_AddRegionManagement` has been generated and applied to the shared database — `RegionalTargets`, `RegionalBudgets`, `RegionAgencyMaps`, and `RegionalTaxRules` all exist.