feat(cities): cities are a tree again, as legacy's; old dashboard permissions merged (PERS-02, PERM-01)

Cities (owner: as legacy)
- City.parentId (self relation) replaces the flat `province` label; the
  migration turns each label into a parent row («استان …») first, so nothing
  is lost.
- Admin pickers are legacy's tree (CityTreeSelect): the top level, the
  country, is a heading. The city form and grid get «والد».
- Restored legacy _getP: a city without its own percent takes its
  ancestor's. Restored the omde-customer form's city → percent autofill.
- Import copies legacy parents; delete refuses a city with children; a
  parent loop is refused.

Permissions (owner approved the deletion)
- dashboard / dashboard.omde / dashboard.tak rows become dashboard.view,
  then are deleted.
- Import maps them the same way: legacy types 102, 104, 105, 108 and 135
  had lost the dashboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-08 16:31:12 +03:30
co-authored by Claude Opus 5.5
parent fbe7b98ada
commit fbe4f38e06
17 changed files with 467 additions and 126 deletions
+8 -1
View File
@@ -118,10 +118,15 @@ enum LedgerSourceType {
// Identity
// ─────────────────────────────────────────────────────────────────────────
/// Legacy `cities` is a tree (country > province > city > district) and so is
/// this: a city hangs under its `parent`. In the admin pickers a top-level row
/// (the country) is a heading, not selectable (henza_admin citiesModel.js:38).
model City {
id String @id @default(cuid())
name String
province String?
parentId String?
parent City? @relation("CityTree", fields: [parentId], references: [id], onDelete: Restrict)
children City[] @relation("CityTree")
/// Legacy cities.percent / percent_aux — per-city wage surcharges the city
/// screen edits through GET cities/get-percent/{id} and get-percent-aux/{id}.
percent Decimal? @db.Decimal(6, 3)
@@ -131,6 +136,8 @@ model City {
branches Branch[]
preOrders PreOrder[]
salesExpertProfiles SalesExpertProfile[]
@@index([parentId])
}
model AdminUser {