diff --git a/docs/parity/01-ADMIN-MATRIX.md b/docs/parity/01-ADMIN-MATRIX.md index 7f5819a..0080c90 100644 --- a/docs/parity/01-ADMIN-MATRIX.md +++ b/docs/parity/01-ADMIN-MATRIX.md @@ -169,9 +169,21 @@ To be filled, in the fix order given in the Phase 1 summary: --- -## §3 — Price formula and product pricing — **the top critical gap** +## §3 — Price formula and product pricing — ⏸️ STOPPED BY D16 -### PRICE-01 — the target computes no product price at all ❌ CRITICAL +> **Owner ruling, 2026-09-22.** Computing and showing `price` / +> `price_with_discount` is a **retail-storefront** concern; the B2B app works in +> **making charge (اجرت)** only. PRICE-01 is therefore ⏸️ under D13 and is NOT +> worked on. `variant-price.ts` and its 11 tests are **kept but unused**; +> nothing needed reverting because the function was never wired in. +> +> **Making charge stays fully in scope** — `percentage_pay`, +> `makingChargeOffsetPercent`, and wage display on variants and order lines. +> +> The findings below are retained as evidence for whenever the storefront is +> taken up. + +### PRICE-01 — the target computes no product price at all — ⏸️ out of scope | Dimension | Reference | Target | Status | |---|---|---|---| @@ -183,15 +195,15 @@ To be filled, in the fix order given in the Phase 1 summary: | Displayed in the product list | `henza_admin/src/views/apps/product/ProductList.vue:283,287,301` — `{{ item.price }}` in three places | no price column | ❌ missing | | Extra percent source | `ConfigStatic['product_price_extra_percent']`, default 7 (`ProductVariant.php:50`) | the ported function defaults to 7 but nothing reads the config row | ❌ missing | -**Impact.** Every admin screen that shows a product price shows nothing. This is -the single largest behavioural gap in the admin panel and it is money-facing, so -it is **fix #1**. +**Impact (historical).** Every admin screen that would show a product price shows +nothing. Under D16 this is storefront behaviour, so it is not a Phase 1 gap. **What is already done:** the formula itself is ported and proven — the live version at `:183-190`, not the three superseded variants above it that apply the 9% to the whole price (a test asserts the difference: 39,986,000 vs 42,686,000). -**What remains:** read `product_price_extra_percent` from settings, fetch the +**What remains (only if the storefront is ever taken up):** read +`product_price_extra_percent` from settings, fetch the current gold price, call `computeVariantPrice`, and surface `price` / `price_with_discount` in `variantRow()` and the product list column. diff --git a/docs/parity/CHANGELOG.md b/docs/parity/CHANGELOG.md index 63185e0..f7d8848 100644 --- a/docs/parity/CHANGELOG.md +++ b/docs/parity/CHANGELOG.md @@ -398,3 +398,51 @@ reliably, 0 single-token, 0 empty, **1 needs review** — `customer-dev-test` first name «محمد علی» survives intact, which is the defect the owner originally reported. - Typecheck: api ✅ · admin-panel ✅. + +--- + +## 2026-09-22 (later still) — D16 priority change, SEC-02 re-verified + +### PRICE-01 stopped (D16) + +Price display is retail-storefront work, so it falls under D13 and is now ⏸️. + +- `apps/api/src/modules/pricing/variant-price.ts` and its 11 tests are **kept** + and still run; they are simply not used. +- **Nothing needed reverting.** `computeVariantPrice` was never called from + anywhere outside its own file and spec — verified by grep over `apps/api/src` + and `apps/admin-panel/src`. No code had been wired to display a price. +- Making-charge work (`percentage_pay`, `makingChargeOffsetPercent`, wage display + on variants and order lines) **stays in scope**. +- `ISSUES.md` PRICE-01 re-marked ⏸️ rather than deleted, so the evidence survives. + +### SEC-02 re-verified live + +Re-tested end to end rather than quoting the earlier run: + +| Check | Result | +|---|---| +| `POST /orders` with no token | 401 | +| Body `customerId: "customer-2"` + `customer-dev-test` token | landed on **customer-dev-test**; warn-logged | +| `GET /orders/:id` own | 200 | +| `GET /orders/:id` another customer's | **404** (not 403) | +| `GET /orders/:id` unknown | 404, identical | +| `GET /orders/admin/:id` with a customer token | 401 | + +**9/9 passed**, plus **32/32** in `auth-smoke.sh`. + +### Data deletions (standing rule 2) + +| What | How many | Why | +|---|---|---| +| 1 `Order` created by the SEC-02 test, with its `OrderItem`, `OrderStatusEvent` and `LedgerEntry` rows | 1 order + its children | Test fixture, removed by the test itself. | + +### A testing note worth keeping + +The dev OTP line briefly appeared to be missing after the SMS change. It was not +a regression: a **stale API process from an earlier test still held port 3077**, +so the new instance died with `EADDRINUSE` and the log being tailed belonged to +the old process. Kill the listener (`netstat -ano | grep :3077`) before +concluding anything from a log. `smsConfigured()` returns false with no +credentials, so the dev log fires exactly as intended — confirmed once the port +was free. diff --git a/docs/parity/DECISIONS.md b/docs/parity/DECISIONS.md index 03de954..77847d6 100644 --- a/docs/parity/DECISIONS.md +++ b/docs/parity/DECISIONS.md @@ -878,3 +878,83 @@ three rows from both attempts were deleted and logged. - Console output of Persian is not evidence: the Windows console renders it as `?` even when the data is correct. Verify by comparing values or codepoints in Node, as `scripts/test-name-trigger.cjs` does. + +--- + +# D16 — Price display is storefront; new work order (2026-09-22) + +## 1. PRICE-01 is stopped — price belongs to the retail storefront + +**Owner's ruling.** Computing and displaying `price` / `price_with_discount` is a +**retail storefront (تک‌فروشی)** concern. The B2B app works in **making charge +(اجرت)** only. So PRICE-01 falls under D13 and is marked **⏸️ preserved — out of +current scope**. + +| Item | Ruling | +|---|---| +| `apps/api/src/modules/pricing/variant-price.ts` | **Kept, not deleted.** Simply not used. | +| `variant-price.spec.ts` (11 hand-checked tests) | **Kept** and still runs in CI. | +| Any code wiring price into the admin panel | **Revert.** — *None existed: `computeVariantPrice` was never called from anywhere outside its own file and spec, so there was nothing to revert. Verified by grep over `apps/api/src` and `apps/admin-panel/src`.* | +| Making-charge fields — `percentage_pay`, `makingChargeOffsetPercent`, wage display | **Still fully in scope.** | + +PRICE-01 in `ISSUES.md` is re-marked ⏸️ rather than deleted, so the analysis and +its evidence stay available if the storefront is ever taken up. + +**Why this matters beyond one row:** the reference's `ProductVariant::$appends` +carries `price`, `color_str` and `price_with_discount` together +(`ProductVariant.php:115-119`). Only the price pair is storefront; anything else +appended there is still judged on its own merits. + +## 2. The gate stands: admin first, then the apps + +### Phase 1 — admin, in this order + +**(a) Persons (اشخاص)** +1. The **5 missing sales-expert fields** (`ncode`, `phone`, `city_id`, + `gram_level`, `reagent_id`): **report first** — where the reference uses each + one for `type=1` — then add according to what that shows. Standing proposal: + a `SalesExpertProfile` related to `AdminUser` (D10). Fields with no use are + **listed for the owner to decide**, not added speculatively. +2. `singleCustomer` migrates to **diff-send** (D11). +3. Field-by-field audit of every form and grid in Persons — retail customer, + wholesale customer, sales expert — per D12, each with its round-trip test. + +**(b) Products, categories, variants, and order detail in the admin** — deep +field-by-field: every form field, column, filter and button (D12). +- The **variant image picker stays untouched** (PROTECTED, `PROTECTED.md` §B); + only genuine omissions are added. +- The **making charge of every variant and every order line must display and + persist exactly as the reference does.** + +**(c) The rest of the admin, in order:** financial documents → users and +permissions → admin SMS templates → promotions / notifications / cities / +branches / workshops → reports. + +**(d)** Full admin test pass, the Phase 1 report, then the gate question +**«ادمین کامل و تست شد. بریم سراغ اپ‌ها؟»** — and **stop and wait**. + +### Phase 2 — apps (only after the owner approves) + +**(a)** The 5 v2-behind-v1 logic gaps, most important the **cart stock ceiling +(`capQty`/`maxQty`)**, matching v1 and the reference exactly — v2's design fully +preserved, with a test proving v1 and v2 behave identically. + +**(b)** The missing screens (`report`, `report/detail`, `ChildOrder`, +`customerChoice`, `complete_profile`) in **both** apps, each in its own design, +plus whatever backend they need. + +**(c)** Field-by-field audit of products, categories, variants, cart and order in +the apps. + +**(d)** The remaining Phase 2 items: MON-08, removing `customerId` from the order +DTO, route analytics, the `isLoaded` / `sts_verify_step_two` guards, guest +behaviour, app SMS templates, and the remaining screens. + +## 3. Working method per section + +**Implement the gaps first, then field-by-field test that same section.** Before +fixing any section, **show the owner a short summary of the gaps found** and wait. + +This supersedes the "audit everything, then fix everything" reading of D12: the +field-by-field requirement is unchanged, but it is applied **per section**, with +a checkpoint before each one. diff --git a/docs/parity/ISSUES.md b/docs/parity/ISSUES.md index f1f97a0..121a6fd 100644 --- a/docs/parity/ISSUES.md +++ b/docs/parity/ISSUES.md @@ -123,6 +123,6 @@ Severity: `Critical` (money loss / security / data loss) · `High` · `Medium` | ID | Severity | Where | Description | Evidence | Status | |---|---|---|---|---|---| -| PRICE-01 | **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. | Open — **fix #1 of Phase 1**. | +| 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. | diff --git a/docs/parity/PROGRESS.md b/docs/parity/PROGRESS.md index d822dd9..739bca4 100644 --- a/docs/parity/PROGRESS.md +++ b/docs/parity/PROGRESS.md @@ -10,7 +10,7 @@ | **Phase** | **Phase 1 — admin parity, matrix in progress** | | **Branch** | `parity/phase-1-admin` (cut from `parity/security-hotfix`) | | **Status** | Both stages done and verified live: **46/46** smoke assertions, **7/7** jest unit tests, typecheck + build clean on all three projects. | -| **Next step** | Owner is reviewing the matrix summary; §3 PRICE-01 is fix #1 once the order is confirmed | +| **Next step** | Phase 1 (a) Persons — report where the reference uses the 5 sales-expert fields for `type=1`, then implement per D16 | | **Blocked on** | nothing | | **Last updated** | 2026-09-22 | @@ -51,7 +51,7 @@ while the id and the id-addressed routes remain.** ## ⚠️ Read `DECISIONS.md` before doing anything -All user rulings live there (D0–D15). Highlights that change how work is done: +All user rulings live there (D0–D16). Highlights that change how work is done: - **D12 — the matrix is FIELD-BY-FIELD, not page-by-page**, and a 4-step round-trip test per entity is mandatory. Both rules were added to the @@ -105,6 +105,7 @@ All user rulings live there (D0–D15). Highlights that change how work is done: | D13 | **Retail storefront (wallet, cashback, `ConfigStatic['percent']`) preserved but OUT OF SCOPE.** Nothing deleted or disabled; no parity work; matrices list it ⏸️ with no gap/work row; ask before touching shared surfaces. | | D14 | **`migrate dev` in development, `migrate deploy` in production and nowhere else.** Never `migrate reset`; never edit `_prisma_migrations`. On drift + a reset offer: **stop and report**. | | D15 | **`_goldbank_migrations` is KEPT** — it is the tracking table of the app's own migration runner (`src/prisma/migrate.ts`), needed because the Docker image has no Prisma CLI. Declared `@@ignore`d; drift is zero. **Persian test input must come from a FILE, never a shell argument.** | +| D16 | **PRICE-01 STOPPED** — price display is retail-storefront (⏸️ D13); the B2B app works in making charge only. `variant-price.ts` + tests kept but unused. **Making charge stays in scope.** New work order: Persons → Products/variants/order-detail → the rest of the admin → gate → apps. **Per section: implement gaps, then field-by-field test, and show a gap summary BEFORE fixing.** | ## Open — awaiting user approval @@ -215,3 +216,46 @@ The Windows shell replaces Persian characters with `?`. A test that passes its data inline can "pass" while storing `????`. Write fixtures in the test file or a UTF-8 JSON file and send bodies with `curl --data-binary @file`. Console output of Persian is never evidence — compare values in Node. + +## ⚠️ Phase 1 work order (D16 — supersedes the earlier ordering) + +**(a) Persons (اشخاص)** ← current +1. The 5 missing sales-expert fields (`ncode`, `phone`, `city_id`, `gram_level`, + `reagent_id`): **report first** where the reference uses each for `type=1`, + then add accordingly (proposal: `SalesExpertProfile` related to `AdminUser`, + D10). Unused fields are listed for the owner, not added speculatively. +2. `singleCustomer` → diff-send (D11). +3. Field-by-field audit of every Persons form and grid (retail, wholesale, + sales expert) per D12, each with its round-trip test. + +**(b) Products, categories, variants, admin order detail** — deep field-by-field. +The variant image picker is PROTECTED (§B) — additive only. Making charge on +every variant and order line must display and persist exactly as the reference. + +**(c) The rest, in order:** financial documents → users & permissions → admin SMS +templates → promotions / notifications / cities / branches / workshops → reports. + +**(d)** Full admin test pass → Phase 1 report → gate question → **STOP and wait**. + +### Method per section (D16) + +Implement the gaps first, then field-by-field test that same section. **Show the +owner a short gap summary BEFORE fixing each section.** + +## ✅ SEC-02 — verified complete (re-tested 2026-09-22) + +| Check | Result | +|---|---| +| `POST /orders` without a token | 401 (was `@Public`) | +| Body `customerId: "customer-2"` with a `customer-dev-test` token | order landed on **customer-dev-test**; spoof blocked and warn-logged | +| `GET /orders/:id` own order | 200 | +| `GET /orders/:id` another customer's order | **404**, not 403 — ids stay unenumerable | +| `GET /orders/:id` unknown id | 404, identical response | +| `GET /orders/admin/:id` with a customer token | 401 | + +**9/9 passed** live, plus **32/32** in `auth-smoke.sh`. The server derives the +customer from the token only (`orders.controller.ts:26-29`, +`orders.service.ts` `createOrder(dto, customerId)`). + +Client-supplied `wagePercent` / `discountPercent` / `flatRialCharge` remain — +that is **MON-08**, scheduled as Phase 2 item #1 per the owner's ruling.