parity(docs): D16 — stop PRICE-01 as storefront, reorder Phase 1, verify SEC-02
Owner ruling: computing and displaying price / price_with_discount is retail storefront work, so it falls under D13 and is marked out of scope. The B2B app works in making charge only. variant-price.ts and its 11 hand-checked tests are kept and still run, just unused. Nothing needed reverting: computeVariantPrice was never called from outside its own file and spec, verified by grep over apps/api/src and apps/admin-panel/src, so no price had been wired into any screen. PRICE-01 is re-marked in ISSUES.md rather than deleted so the evidence survives if the storefront is ever taken up. Making-charge work stays fully in scope: percentage_pay, makingChargeOffsetPercent, and wage display on variants and order lines. New Phase 1 order: Persons -> products/categories/variants/order detail -> financial documents -> users and permissions -> admin SMS -> promotions, notifications, cities, branches, workshops -> reports -> gate. Per section: implement the gaps, then field-by-field test that section, and show a gap summary before fixing. SEC-02 re-verified live rather than quoted: 9/9 assertions, plus 32/32 in auth-smoke.sh. POST /orders is 401 without a token; a body customerId of "customer-2" sent with a customer-dev-test token still lands the order on customer-dev-test and is warn-logged; a foreign order id returns 404 rather than 403 so ids stay unenumerable; the admin read rejects a customer token. Client-supplied wagePercent/discountPercent/flatRialCharge remain as MON-08, already scheduled as Phase 2 item #1. Also records why the dev OTP log briefly looked broken: a stale API process still held port 3077, so the new instance died with EADDRINUSE and the tailed log belonged to the old process. Not a regression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
64e76a8616
commit
e3af31948e
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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. |
|
||||
|
||||
+46
-2
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user