331 lines
22 KiB
Markdown
331 lines
22 KiB
Markdown
# PROGRESS — Reference Parity Project
|
||||
|
|
|
|||
|
|
> Read this file FIRST at the start of every session and after every `/compact`.
|
|||
|
|
> Rules live in `CLAUDE.md` (workspace root). Method lives in the `reference-parity-audit` skill.
|
|||
|
|
> Neither is in this repository — see `docs/GO-LIVE-PLAN.md` §0.
|
|||
|
|
|
|||
|
|
## New workstreams (2026-10-04) — plans only, nothing executed
|
|||
|
|
|
|||
|
|
| Plan | What | Waiting on |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `docs/GO-LIVE-PLAN.md` | Stack upgrade, release isolation, data migration and cutover from the live henza system. **§0 explains how to continue on another machine.** | Owner go-ahead per step |
|
|||
|
|
| `docs/PWA-PORT-PLAN.md` | Third user app `apps/user-app-classic`: a pixel-exact port of `henza_pwa@b171fff` onto the new API. | Owner rulings on its tensions 1–5 and Q1–Q9 |
|
|||
|
|
| `docs/ACCOUNTING-INTEGRATION-PLAN.md` | Tahesab / Zarrin integration, switchable in the admin panel (D27). **Implemented 2026-10-05**, off until switched on; progress in its §10. | Zarrin trial on a real install (ACC-01); ACC-03 ruling |
|
|||
|
|
| `docs/DATA-MIGRATION-MAP.md` | Column-by-column map of the live henza MySQL schema (`oapi-struncture.sql`, 80 tables) onto Prisma, checked against real row counts via the read-only `docs/migration-precheck*.sql`. Branch `migration/legacy-data`. **2026-10-07:** additive prep migration applied (nullable `nationalCode`, `birthDate`, rejection trail + `REJECTED`, `CustomerFavorite`), bcrypt guard, REJECTED consumer audit. ⚠ On this machine a file in `prisma/migrations` is applied by the watch-mode dev server on the next `src/` edit — see its §10.5.5. | `precheck-2` (where the ledger amount lives) and `precheck-3` output; two `verify()` rulings |
|
|||
|
|
|
|||
|
|
The "Current state" table below is the parity project's and was last updated 2026-09-22.
|
|||
|
|
|
|||
|
|
## Current state
|
|||
|
|
|
|||
|
|
| Field | Value |
|
|||
|
|
|---|---|
|
|||
|
|
| **Phase** | **Phase 2 — apps parity — items #1 and #2 done** (Phase 1 closed, `83e5d28`) |
|
|||
|
|
| **Branch** | `main` (the parity branches were merged; see git log 2026-10-08..10-12) |
|
|||
|
|
| **Status** | Phase 1: all in-scope sections closed (§2 §4 §5 §6 §7 §8 §9 ✅ · §3 ⏸️ by D16). Phase 2: **#1 MON-08 ✅**, **#2 D3 ✅**, app-login error reporting (**AUTH-01/02/03**, **DOC-01**), **«ورود به حساب مشتری» restored** (**EXP-01/02/03**, D29 — supersedes D21's omission), and the apps' back-navigation + read-freshness audit (**NAV-01…04**, **READ-01…03**). **308/308** jest tests; both apps `vue-tsc` clean. |
|
|||
|
|
| **Next step** | Build **`02-APPS-MATRIX.md`** — the field-by-field apps matrix (D12), which Phase 2 has been running without. Then the remaining app rows it turns up. |
|
|||
|
|
| **Blocked on** | nothing. Awaiting an owner ruling when convenient: **PERM-02**. Deferred by the owner: **ANLY-01** (route analytics, lowest priority). |
|
|||
|
|
| **Last updated** | 2026-10-12 |
|
|||
|
|
|
|||
|
|
### Phase 1 close-out (2026-10-12)
|
|||
|
|
|
|||
|
|
The two items that were still genuinely unfinished:
|
|||
|
|
|
|||
|
|
| Item | Result |
|
|||
|
|
|---|---|
|
|||
|
|
| §2 — the D1 rid matrix (stood as *(pending)* all phase) | ✅ filled: 163 rid-guarded routes, 68 distinct rids, 176 tree keys, mapped per module. 66/68 grantable from the UI; the 2 that are not are **PERM-02**. |
|
|||
|
|
| Order-placement SMS | ✅ **SMS-03** — the matrix had logged one missing message; in fact all four were missing and `OrdersService` had no `SmsService`. Fixed with 6 tests. |
|
|||
|
|
|
|||
|
|
**What is NOT done, by decision rather than by omission:**
|
|||
|
|
|
|||
|
|
| | Why |
|
|||
|
|
|---|---|
|
|||
|
|
| §3 — product price display (PRICE-01) | ⏸️ retail storefront, out of scope by **D13/D16**. `variant-price.ts` + 11 tests kept but unused. |
|
|||
|
|
| Firebase push beside each SMS (PUSH-01) | ⏸️ deferred to the PWA port. |
|
|||
|
|
| «سفارش ویرایش شد» admin SMS | «عمداً تکرار نشد» (D4) — no target path edits a placed order. |
|
|||
|
|
| PERM-02 (2 ungrantable rids) | additive, changes the permission vocabulary → awaits the owner, as PERM-01 did. |
|
|||
|
|
| `02-APPS-MATRIX.md` | Phase 2's file; not created yet. |
|
|||
|
|
|
|||
|
|
⚠️ **Not verified in a browser.** Every ✅ above rests on reading both sides,
|
|||
|
|
unit tests and smoke scripts. The prior port's note still stands: no admin view
|
|||
|
|
has been visually checked in a browser. A full click-through is the honest
|
|||
|
|
remaining risk on Phase 1.
|
|||
|
|
|
|||
|
|
### Security hotfix — final result
|
|||
|
|
|
|||
|
|
| Route | Was | Now |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `GET accounting/customers/:id/balance` | public | admin, rid `wallet.list` |
|
|||
|
|
| `POST accounting/customers/:id/reconcile` | public **write** | admin, rid `wallet.charge` |
|
|||
|
|
| `POST accounting/repayments/gold` | public **write** | admin, rid `document.create` |
|
|||
|
|
| `POST accounting/repayments/rial` | public **write** | admin, rid `document.create` |
|
|||
|
|
| `POST accounting/entries/:id/confirm` | public **write** | admin, rid `document.list.confirm` |
|
|||
|
|
| `PATCH orders/:id/status` | public **write** | admin, rid `order.list.sts.submit` |
|
|||
|
|
| `GET customers` | every customer, 18 fields | **route deleted** |
|
|||
|
|
| `GET customers/:id` | public | **route deleted** → `GET customer/me` (token) |
|
|||
|
|
| `GET customers/:id/orders` | public | **route deleted** → `GET customer/me/orders` (token) |
|
|||
|
|
| `GET customers/:id/ledger-entries` | public | **route deleted** → `GET customer/me/ledger-entries` (token) |
|
|||
|
|
| — | — | **new** `POST customer/auth/request-otp` · `verify-otp` |
|
|||
|
|
|
|||
|
|
Still public by design: `catalog/products`, `pricing/latest`, `home/slider`,
|
|||
|
|
`uploads/:filename` (ruling T2), and `POST /orders` + `GET /orders/:id` — the
|
|||
|
|
last two are logged as SEC-02/B1-B2 and want a customer-token pass of their own.
|
|||
|
|
|
|||
|
|
**Tests**
|
|||
|
|
- `apps/api/scripts/auth-smoke.sh` (live server): **46 passed, 0 failed**.
|
|||
|
|
Run it with `API=http://localhost:3000/api CODE_LOG=/path/to/api.log`.
|
|||
|
|
- `pnpm test` in `apps/api` (jest, newly installed): **7 passed**.
|
|||
|
|
- Typecheck: api ✅ · user-app ✅ · user-app-v2 ✅. Both apps build ✅.
|
|||
|
|
|
|||
|
|
### ⚠️ Correction worth remembering
|
|||
|
|
|
|||
|
|
Stage 1 reported the customer-book leak as "mitigated" after narrowing the
|
|||
|
|
`GET /customers` field list. That was wrong: the response still returned every
|
|||
|
|
customer **id**, and the three `/customers/:id*` routes were still public, so the
|
|||
|
|
full data was still two requests away. The user caught it. Stage 2 deleted the
|
|||
|
|
whole id-bearing surface instead. **Narrowing a response is not closing a leak
|
|||
|
|
while the id and the id-addressed routes remain.**
|
|||
|
|
|
|||
|
|
## ⚠️ Read `DECISIONS.md` before doing anything
|
|||
|
|
|
|||
|
|
All user rulings live there (D0–D17). 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
|
|||
|
|
`reference-parity-audit` skill (Step C rule C0, Step E rule E0).
|
|||
|
|
- **D0** — split `fullName` into `firstName`/`lastName` via an additive migration;
|
|||
|
|
**show the migration and get approval before running it**.
|
|||
|
|
- **D4** — never port broken behaviour, but match calculations/rounding/messages
|
|||
|
|
exactly; annotate each non-replication «عمداً تکرار نشد — ISSUE #n».
|
|||
|
|
- **D2** — target-only features get status ➕ and are left alone.
|
|||
|
|
- **D13** — the **retail storefront** (48 `customer.php` + 30 `site.php` + 31
|
|||
|
|
storefront-management admin routes = **109 of the reference's 412 endpoints**)
|
|||
|
|
is **preserved but out of scope**: status ⏸️, no gap row, no work row, nothing
|
|||
|
|
deleted or disabled. **288 endpoints stay in scope** (218 admin + 70 B2B app).
|
|||
|
|
Note: the reference total is **412, not 369** — `admin.php` was undercounted
|
|||
|
|
in Phase 0; corrected in `00-INVENTORY.md` §2.1.
|
|||
|
|
Ask before changing shared surfaces (product, variant, price, order, customer).
|
|||
|
|
|
|||
|
|
## Phase checklist
|
|||
|
|
|
|||
|
|
- [x] P0.1 — All 3 reference folders readable (`henza_server`, `henza_admin`, `henza_pwa`)
|
|||
|
|
- [x] P0.2 — Branch `parity/phase-0` + `docs/parity/` created
|
|||
|
|
- [x] P0.3 — Inventory all 7 projects → `_inv-*.md` (8111 lines total) → consolidated in `00-INVENTORY.md`
|
|||
|
|
- [x] P0.4 — Mapping tables, missing list, extra list (`00-INVENTORY.md` §2–4)
|
|||
|
|
- [x] P0.5 — Draft `PROTECTED.md`
|
|||
|
|
- [x] **P0.6 — GATE passed** — `PROTECTED.md` confirmed, D0–D12 recorded
|
|||
|
|
- [x] P0.7 — Urgent traces delivered: `percent`, `@Public` list, login leak
|
|||
|
|
- [x] P0.8 — Skill updated with rules C0 (field-by-field) and E0 (round-trip test)
|
|||
|
|
- [x] **P1.0 — approved and DONE:** 5 ledger routes protected, `percent` column added, D0 migration applied, SMS provider implemented
|
|||
|
|
- [x] Phase 1 — Admin parity (`01-ADMIN-MATRIX.md`) — all in-scope sections closed 2026-10-12
|
|||
|
|
- [ ] **GATE** ← HERE — asked 2026-10-12: "Admin parity is complete and tested. Shall we move on to the apps (user-app & user-app-v2)?"
|
|||
|
|
- [ ] Phase 2 — Apps parity (`02-APPS-MATRIX.md`, both user apps)
|
|||
|
|
- [ ] Phase 3 — Work through `ISSUES.md` (only when user asks)
|
|||
|
|
|
|||
|
|
## Answered — see `DECISIONS.md` for the full text
|
|||
|
|
|
|||
|
|
| # | Ruling |
|
|||
|
|
|---|---|
|
|||
|
|
| D0 | Split `fullName` → `firstName`/`lastName`, additive migration, backfill, sync, report multi-token rows. Approval needed before running. |
|
|||
|
|
| D1 | Target's stricter auth stands. Build the "reference operation ↔ rid" matrix; explain 172 vs 174. |
|
|||
|
|
| D2 | Audit only what the reference has. Target-only features → ➕, untouched. |
|
|||
|
|
| D3 | Build the 5 missing screens in Phase 2, both apps, plus analytics, `isLoaded`/`sts_verify_step_two` guards, guest toast+home, and v2's cart ceiling. |
|
|||
|
|
| D4 | Don't port breakage; do match calculations/rounding/messages. Unit-test the price formula. |
|
|||
|
|
| D5 | Leave the stray folder; delete last, own commit. |
|
|||
|
|
| D6 | **OTP must verify** — parity gap, fix it (plus expiry + attempt limits, which exceed the reference). |
|
|||
|
|
| D7 | `@Public` ledger routes: list delivered, awaiting approval to protect. |
|
|||
|
|
| D8 | Login leak: **target**, full customer book incl. national IDs and debts. |
|
|||
|
|
| D9 | `percent`: trace delivered — **the target's implementation is wrong**. |
|
|||
|
|
| D10 | Sales expert: keep `AdminUser`; investigate the 5 missing fields; propose `SalesExpertProfile`. |
|
|||
|
|
| D11 | `singleCustomer.vue` → diff-send. |
|
|||
|
|
| D12 | **Field-by-field matrix + mandatory round-trip test.** Added to the skill. |
|
|||
|
|
| 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.** |
|
|||
|
|
| D17 | **SalesExpertProfile** (cityId, gramLevel, phone, nationalCode) + **firstName/lastName on all AdminUsers**. **`reagent_id` deliberately NOT ported** — in the reference it was a side effect of the shared customers form and filling it removed the expert from the selectable-agent list (queryReagent:420); the referrer logic lives on `Customer.referredByAgentId`. |
|
|||
|
|
| D29 | **«ورود به حساب مشتری» restored** (2026-10-12) — a sales expert can work inside any of their referred customers' accounts with the reference's **full** scope (every customer route, documents included). Mechanism deliberately not the reference's: no customer token is minted; `X-Act-As-Customer` rides the expert's own token and is authorised in `CustomerJwtStrategy.validateExpert`, re-checking `referredByAgentId` on **every** request. **Supersedes D21's "deliberately not ported".** |
|
|||
|
|
| D28 | **App login step 1 must say what went wrong** (2026-10-12): an unregistered number → 404, a failed SMS send → 503, an unusable sales-expert account → 403, each with its own Persian message. Restores the reference's `firstOrFail()`. **Trades away part of the anti-enumeration protection, deliberately** — the per-mobile/per-IP send throttles still bound it and `verifyOtp` stays single-message. See D28 for the full reasoning and why D20 is not violated. |
|
|||
|
|
|
|||
|
|
## Open — awaiting user approval
|
|||
|
|
|
|||
|
|
Nothing. The 5 ledger routes, the `percent` column and the D0 migration are all
|
|||
|
|
done and verified (see CHANGELOG 2026-09-22).
|
|||
|
|
|
|||
|
|
## Key context (survives context loss)
|
|||
|
|
|
|||
|
|
- **Domain correction:** the reference is a **gold-jewellery B2B/B2C e-commerce**
|
|||
|
|
system, not a gold bank. No buy/sell order book, no spread, no deposit/withdraw,
|
|||
|
|
no P2P transfer, no KYC levels, no daily limits (only `gram_level`), no payment
|
|||
|
|
callback route, no cron, no websockets. See `00-INVENTORY.md` §0.
|
|||
|
|
- **The gold price formula** (Phase 1 row #1) —
|
|||
|
|
`henza_server/app/Models/ProductVariant.php:183-190`: `+extra%` → `+7%` margin →
|
|||
|
|
`+9%` VAT **on the non-metal portion only** → `×grams` → `floor(/1000)*1000`.
|
|||
|
|
Three superseded commented-out variants above it apply VAT to the whole price —
|
|||
|
|
do **not** port those.
|
|||
|
|
- **A large prior port already happened** — see `docs/ADMIN-PORT-PLAN.md` and
|
|||
|
|
`docs/CHANGELOG-PORT-HISTORY.md` (80 views, 25 Nest modules, 29 Prisma models,
|
|||
|
|
154 admin routes, 144/144 rids). Prior port notes **no view was ever visually
|
|||
|
|
verified in a browser**.
|
|||
|
|
- **Stack changed across the port:** Laravel→NestJS/Prisma, Vue2/Vuesax→Vue3/antd.
|
|||
|
|
Port behaviour, not code style.
|
|||
|
|
- **Reference scale is inflated:** 541 admin `.vue` files but only ~69 reachable
|
|||
|
|
(rest are Vuexy template demos); 9 reference router files are dead (not imported);
|
|||
|
|
~28 legacy Django-era DB tables have no migrations.
|
|||
|
|
- **v1 ⇄ v2 logic gaps** (5, all v2 behind v1) are parity work, not design:
|
|||
|
|
`ISSUES.md` MON-01, V2-01..05.
|
|||
|
|
|
|||
|
|
## Inventory files (Phase 0 working files)
|
|||
|
|
|
|||
|
|
| File | Scope | Lines |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `_inv-ref-server.md` | henza_server | 1313 |
|
|||
|
|
| `_inv-tgt-api.md` | apps/api | 1179 |
|
|||
|
|
| `_inv-ref-admin.md` | henza_admin | 1705 |
|
|||
|
|
| `_inv-tgt-admin.md` | apps/admin-panel | 1261 |
|
|||
|
|
| `_inv-ref-pwa.md` | henza_pwa | 1178 |
|
|||
|
|
| `_inv-tgt-userapp.md` | apps/user-app | 612 |
|
|||
|
|
| `_inv-tgt-userappv2.md` | apps/user-app-v2 | 700 |
|
|||
|
|
|
|||
|
|
Consolidated into `00-INVENTORY.md`. Issues found: `ISSUES.md` (26 entries).
|
|||
|
|
|
|||
|
|
## Standing rules added 2026-09-22
|
|||
|
|
|
|||
|
|
1. **Migrations run only via the standard Prisma workflow** (`migrate dev` in
|
|||
|
|
development). **`prisma db execute` is not used for schema changes.**
|
|||
|
|
2. **Every data deletion is logged in `CHANGELOG.md`** — including throwaway
|
|||
|
|
rows created during a session.
|
|||
|
|
3. **D13** — the retail storefront is preserved but out of scope; see
|
|||
|
|
`DECISIONS.md` §D13 and `PROTECTED.md` §H.
|
|||
|
|
|
|||
|
|
## Phase 2 — item #1 (MON-08) — ✅ DONE 2026-10-12
|
|||
|
|
|
|||
|
|
All four steps of the ruling are in place; see `ISSUES.md` §"Phase 2 — item #1".
|
|||
|
|
Steps 1, 3 and 4 had landed with the wage-formula work; **step 2 — logging the
|
|||
|
|
client figures that get discarded — was added 2026-10-12**, along with the
|
|||
|
|
comparison test the ruling asked for («تست مقایسهای»), which step 3 had not
|
|||
|
|
actually had: the suite only covered a tampered request, not an honest one.
|
|||
|
|
`orders.service.spec.ts` is now 16 tests.
|
|||
|
|
|
|||
|
|
An operator ordering from the panel still keeps the numbers they typed — only an
|
|||
|
|
app order is re-priced by the server.
|
|||
|
|
|
|||
|
|
## Phase 2 — item #2 (D3) — ✅ DONE 2026-10-12
|
|||
|
|
|
|||
|
|
D3 was ten items, not one. **Seven were already built** — all five screens
|
|||
|
|
(`/report`, `/report/detail`, `/ChildOrder`, `/customerChoice`,
|
|||
|
|
`/complete_profile`) exist in **both** apps, as do the `sts_verify_step_two`
|
|||
|
|
guard and v2's cart stock ceiling. Checked in the code, not read off a
|
|||
|
|
checkmark.
|
|||
|
|
|
|||
|
|
| Outstanding item | Result |
|
|||
|
|
|---|---|
|
|||
|
|
| guest toast «ابتدا وارد شوید» | ✅ both apps (**APP-03**). The destination stays the target's `/login?redirect=…`, better than legacy's `home` — owner's call, logged as «عمداً تکرار نشد — D3/الف». |
|
|||
|
|
| `isLoaded` router guard | ⏹️ **not ported** (**APP-04**) — guards a bootstrap request the target does not have; would only bounce a shared product link to `home` (D4). Owner agreed. |
|
|||
|
|
| route analytics | ⏸️ **deferred, lowest priority** (**ANLY-01**) — design parked in `docs/PHASE2-D3-PLAN.md`, with the two reference defects not to copy and the two retention/privacy questions still open. |
|
|||
|
|
|
|||
|
|
⚠️ **Phase 2 is running without its matrix.** `02-APPS-MATRIX.md` still does not
|
|||
|
|
exist, so "what else do the apps miss" rests on the earlier survey rather than a
|
|||
|
|
field-by-field comparison (D12). That is the next item.
|
|||
|
|
|
|||
|
|
The original requirement, for the record:
|
|||
|
|
|
|||
|
|
Before anything else in Phase 2: the server must derive `wagePercent`,
|
|||
|
|
`discountPercent` and `flatRialCharge` from the DB and **ignore** the client's
|
|||
|
|
values, because `henza_pwa` computes them rather than asking the user
|
|||
|
|
(`cart.vue:170-179`, `Mixins/product/index.ts:69-75`). A comparison test must
|
|||
|
|
show the total is unchanged for an ordinary customer. Depends on
|
|||
|
|
`makingChargeOffsetPercent` existing. Full reasoning: `DECISIONS.md`
|
|||
|
|
§"Report — MON-08".
|
|||
|
|
|
|||
|
|
## Done 2026-09-22 (was: waiting on the owner)
|
|||
|
|
|
|||
|
|
| # | Item | What is ready |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 1 | Run the **D0** and **percent** migrations | ✅ **Applied** with `migrate deploy` (`migrate dev` is interactive-only and this shell is not a TTY). Drift checked read-only first — the only difference is the pre-existing `_goldbank_migrations` table, which was **NOT** dropped (DEAD-07). Service layer, all three forms, trigger test and the full round-trip test done. |
|
|||
|
|
| 2 | Implement the **SMS provider** (Trez) | ✅ `SmsModule`/`SmsService` live, OTP wired, text byte-identical, boot check fails in production on a missing key, `.env.example` has names only. The 12 B2B templates land per flow in their own phase. **SEC-12: the owner is rotating the credentials and setting them in `.env`.** |
|
|||
|
|
|
|||
|
|
|
|||
|
|
## ⚠️ Standing rules added 2026-09-22 (D14)
|
|||
|
|
|
|||
|
|
1. **`migrate dev` in development, `migrate deploy` in production and nowhere
|
|||
|
|
else.** Never `migrate reset`, never edit `_prisma_migrations` by hand. If
|
|||
|
|
drift is reported and a reset is offered: **stop and report**, do not accept.
|
|||
|
|
`prisma db execute` is no longer used for schema changes.
|
|||
|
|
2. **Every data deletion is logged in `CHANGELOG.md`**, including throwaway rows
|
|||
|
|
a session creates itself.
|
|||
|
|
|
|||
|
|
## ⚠️ `customer_sync_full_name` is a DATABASE TRIGGER
|
|||
|
|
|
|||
|
|
Prisma knows nothing about it, so writing `Customer` can store something
|
|||
|
|
different from what you wrote:
|
|||
|
|
|
|||
|
|
| Write | Result |
|
|||
|
|
|---|---|
|
|||
|
|
| the parts only | `fullName` is rebuilt |
|
|||
|
|
| `fullName` only | the parts are re-derived (first token / the rest) |
|
|||
|
|
| **both at once** | **THE PARTS WIN**; the submitted `fullName` is discarded |
|
|||
|
|
|
|||
|
|
Re-read after writing if you need the stored value. See `DECISIONS.md` §D14 and
|
|||
|
|
the doc comment on the `Customer` model in `schema.prisma`.
|
|||
|
|
|
|||
|
|
## ⚠️ Two migration ledgers, on purpose (D15)
|
|||
|
|
|
|||
|
|
| Ledger | Written by | Used |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `_prisma_migrations` | the Prisma CLI | development |
|
|||
|
|
| `_goldbank_migrations` | `apps/api/src/prisma/migrate.ts` | the deployed container, at boot |
|
|||
|
|
|
|||
|
|
The Docker deploy image is copy-only and ships **no Prisma CLI**, so the app
|
|||
|
|
applies migrations itself at startup. **Every new migration must be safe under
|
|||
|
|
both paths** — keep the SQL idempotent (`IF NOT EXISTS`, `CREATE OR REPLACE`).
|
|||
|
|
|
|||
|
|
## ⚠️ Persian test input comes from a FILE (D15)
|
|||
|
|
|
|||
|
|
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.
|