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:
co-authored by
Claude Opus 5.5
parent
fbe7b98ada
commit
fbe4f38e06
@@ -125,7 +125,7 @@ Severity: `Critical` (money loss / security / data loss) · `High` · `Medium`
|
||||
|---|---|---|---|---|---|
|
||||
| PRICE-01 | ⏸️ out of scope (was Critical) | TGT `apps/api/src/modules/admin-products/admin-products.service.ts:188-208`; `apps/admin-panel/src/views/apps/product/ProductList.vue` | **No product price is computed or shown anywhere in the admin panel.** The reference appends `price` and `price_with_discount` to every variant (`ProductVariant.php:115-119`), sets the live gold rate before listing (`AdminController.php:97-98`), and renders `{{ item.price }}` in three places in its product list (`ProductList.vue:283,287,301`). The target's `variantRow()` returns neither field, nothing fetches a gold price for the admin list, and `computeVariantPrice()` — ported and unit-tested today — is called from nowhere. | `grep computeVariantPrice src/ --include=*.ts` matches only its own file and spec. | ⏸️ **Stopped by D16** — price display is retail-storefront, out of scope. `variant-price.ts` and its 11 tests are KEPT but unused. Nothing needed reverting: the function was never called. Making-charge fields stay in scope. |
|
||||
| PRICE-02 | Info | REF `app/Models/ProductVariant.php:163` vs TGT `admin-products.service.ts:206-208` | `rial_pay` (a flat rial charge per variant) appears only in the **superseded, commented-out** formula, not the live one at `:183-190`. The target returns null rather than faking it, which matches the live reference. Recorded so nobody "restores" it from the dead code. | Read both. | ✅ No action — matches. |
|
||||
| PERM-01 | Medium | TGT `prisma/seed.ts`; `apps/admin-panel/src/views/user/permissionConst.ts` | `dashboard.omde` and `dashboard.tak` are seeded into the `permission` table but are **absent from the permission UI tree**, so they can be held by a row yet never seen, granted or revoked from the admin screen — and nothing reads them any more (the protected Dashboard uses `dashboard.view`). The DB holds **178** distinct rids against the UI's **174**. | `SELECT rid FROM permission GROUP BY rid` → 178, including all three dashboard rids. | Open — the fix removes permission rows, which is a data deletion and needs the owner's approval first. |
|
||||
| PERM-01 | Medium | TGT `prisma/seed.ts`; `apps/admin-panel/src/views/user/permissionConst.ts` | `dashboard.omde` and `dashboard.tak` are seeded into the `permission` table but are **absent from the permission UI tree**, so they can be held by a row yet never seen, granted or revoked from the admin screen — and nothing reads them any more (the protected Dashboard uses `dashboard.view`). The DB holds **178** distinct rids against the UI's **174**. | `SELECT rid FROM permission GROUP BY rid` → 178, including all three dashboard rids. | ✅ **FIXED 2026-10-08** (owner approved the deletion). Migration `20261008160000_dashboard_permission_merge` gives `dashboard.view` to every type holding `dashboard` / `dashboard.omde` / `dashboard.tak`, then deletes those rows. **Also found:** the import dropped these three as unknown rids, so legacy types 102, 104, 105, 108 and 135 lost the dashboard — `people.ts` RENAMED_RIDS now maps them to `dashboard.view`. The other 45 legacy rids the target lacks are tree parents or rids no legacy code reads. |
|
||||
|
||||
## Delivery-mode items (D19/D20, 2026-09-22)
|
||||
|
||||
@@ -134,7 +134,7 @@ Severity: `Critical` (money loss / security / data loss) · `High` · `Medium`
|
||||
| **REL-01** | **Critical — before the real release** | TGT deployment env | **`OTP_MASTER_CODE` must be removed from the environment, and the SMS credentials configured.** While the variable is set, ANY active customer can sign in with that one fixed code. It is off by default (unset = disabled) and the API prints a boot banner while it is armed, but nothing stops it reaching production except this step. | `customer-auth.service.ts` `masterOtpCode()`; `main.ts` boot banner; D20 | Unset `OTP_MASTER_CODE`; set `SMS_TREZ_USERNAME` / `SMS_TREZ_PASSWORD` / `SMS_TREZ_SENDER_LINE`; restart and confirm the banner is gone. |
|
||||
| **REL-02** | High | TGT `apps/api/src/modules/sms/sms.service.ts` | D20 removed the rule that a missing SMS credential aborts the boot in production. The server now starts with SMS disabled, so **customers cannot receive a login code** and the failure is only a log line. Deliberate, to keep the delivery unblocked. | D14 (superseded) vs D20 | After the delivery, decide whether to restore a boot check or add a health endpoint that reports SMS readiness. **Eased by D26 (2026-10-05):** the default `relay` driver needs no credentials, so SMS works on a fresh deployment; a gateway refusal is still only a log line. |
|
||||
| **PERS-01** | Medium | REF `AdminController.php:348` vs TGT `admin-customers.service.ts` `update()` | The reference **discards the submitted mobile on edit** (`$input['mobile'] = $customer->mobile;`), making it immutable once set; the target lets an operator change it. D19 ruled the lock NOT ported, so the target keeps the editable behaviour. Changing a mobile changes the customer's login identity. | `AdminController.php:348`; D19 answer B | If the lock is wanted later, apply it in `update()` and hide/disable the field on the edit form. |
|
||||
| **PERS-02** | Medium | REF `cities` table vs TGT `City` model | The reference's cities are a **two-level hierarchy** (`parent` + `level`, provinces at level 0) rendered as an `a-tree-select` with provinces **not selectable** (`citiesModel.js:38`). The target flattened this to a nullable `province` **string** and a flat `a-select` on all three Persons forms. Functionally the city is still captured; the province grouping and its tree UI are absent. | `2021_09_18_171700_city_table.php:18-19`; `henza_admin/src/mixins/citiesModel.js:20-48`; `schema.prisma` `model City` | Structural change; decide with the owner during the cities screen (Phase 1c). |
|
||||
| **PERS-02** | Medium | REF `cities` table vs TGT `City` model | The reference's cities are a **two-level hierarchy** (`parent` + `level`, provinces at level 0) rendered as an `a-tree-select` with provinces **not selectable** (`citiesModel.js:38`). The target flattened this to a nullable `province` **string** and a flat `a-select` on all three Persons forms. Functionally the city is still captured; the province grouping and its tree UI are absent. | `2021_09_18_171700_city_table.php:18-19`; `henza_admin/src/mixins/citiesModel.js:20-48`; `schema.prisma` `model City` | ✅ **FIXED 2026-10-08** (owner: as legacy). `City.parentId` (self relation, Restrict) replaces `province`; migration `20261008170000_city_tree` turns each province label into a parent row («استان …») before dropping the column. Admin pickers are legacy's tree (`CityTreeSelect.vue`: top level with children not selectable); city form/grid have «والد». **Also restored:** legacy `_getP` — a city without its own percent takes its ancestor's (`getCityPercent`); the omde-customer form's city→percent autofill. Import copies legacy parents. Delete refuses a city with children; a parent loop is refused. |
|
||||
| **PERS-03** | Low — **column added 2026-10-07 (`Customer.birthDate`, 541 legacy dates imported); form field still missing** | REF `bulkCustomer.vue:111`, `singleCustomer.vue:29` vs TGT | The reference collects a **Jalali birth date** on both the wholesale (`birth_date`) and retail (`birth_day`) customer forms via `DateInput`. The target's `Customer` model has no column for it and both forms omit the field. | `henza_admin/.../bulkCustomer.vue:111`; `schema.prisma` `model Customer` (no birth field) | Additive: add a nullable `birthDate` column and the picker, if the owner wants it. |
|
||||
| **PERF-01** | Low | TGT `admin-customers.service.ts` `listReagents()` | The per-agent gold-sold total runs **one `order.aggregate` per row** inside `Promise.all` — an N+1 on every page of the sales-expert grid. Harmless at the current agent count. | `listReagents()` `rows.map(async ...)` | Replace with a single `groupBy` over the referring agent. |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user