# Admin · Geography — Business Rules

Numbered, implementable rules the demo encodes (canonical), with our status.

## Rules from the demo (`portals/admin/Geography.tsx`, `types.ts:559`, `store/seed.ts:418`)

1. **A city has an `active` boolean.** (`types.ts:563`; seed shows Dammam `active:false`.)
   → Ours: **MISSING** — no `active`/`status` column on `city` (`common/models/base/City.php` rules `:43-50`). Requires migration + model rule + form control.

2. **An admin can toggle a city active/inactive.** (Toggle, `Geography.tsx:39-46`.)
   → Ours: **MISSING** — no toggle, no endpoint, no field.

3. **Toggling is acknowledged with a toast** `"<city> enabled/disabled"`. (line 42-45.)
   → Ours: **MISSING** (flash messages exist for CRUD save, but no toggle action).

4. **A city's shop count = number of shops whose `city` equals the city name.** (line 26.)
   → Ours: derivable via `shop.city` FK → City (`Shop.php:468`) but **NOT surfaced**. Note: our join is by **id** (`shop.city = city.id`), demo joins by **name**. Implement count via FK.

5. **"shop" is singular when count == 1, else "shops".** (line 36.)
   → Ours: N/A (count not shown). Implementable in PHP if surfaced.

6. **A city owns a list of districts; the count is shown.** (`districts: string[]`, line 36.)
   → Ours: districts are a **separate table** with `district.city_id` FK (`base/District.php:21`). Count = `District::find()->where(['city_id'=>id])->count()`. **Not surfaced** on city UI.

7. **Districts are displayed as chips/badges under their city.** (lines 48-54.)
   → Ours: **MISSING** on city screens (districts only live on their own list page).

8. **Geography is admin-only.** (Demo lives under `portals/admin/`.)
   → Ours: Government guards guests + `checkPermmissions('government')` (`GovernmentController.php:21-22`). **City/District controllers do NOT add this guard** — they rely only on `BackendController` defaults; verify the base enforces auth/permission. Potential parity/security gap to confirm.

## Rules ours adds beyond the demo (document so they are not "lost" in a reskin)
9. **City/District/Government are fully CRUD-managed** (create/update/delete with `loadAll/saveAll`). Demo has none of this.
10. **District carries SEO fields**: `slug`, `meta_description` (max 160), `direction`, `region` (`base/District.php:48-51`). City carries `slug`, `meta_description`, `country_id`, `sort`.
11. **Cities are sortable** (`sort` column; `GovernmentController` has a `change-sort` SortAction; City has `sort` attr). Demo has no ordering concept.
12. **A "Government/region" tier exists above City** (`country_code`, customers relation). The demo has **no** region tier — flag as NEW-on-our-side, decide whether to expose or hide.

## Open decisions for parity work
- Add `city.active` (and possibly `district.active`) columns + toggle endpoint + UI to reach demo parity (rules 1-3).
- Decide whether to build the consolidated card dashboard (rules 4-7) or keep tabular CRUD.
- Confirm permission guard on City/District matches Government (rule 8).
