277 lines
14 KiB
Markdown
277 lines
14 KiB
Markdown
# PROTECTED — intentional monorepo additions (additive-only)
|
||
|
||
**CONFIRMED by the user at the Phase 0 gate (2026-09-21).**
|
||
Rulings are recorded in `DECISIONS.md` (D0–D12).
|
||
|
||
Items here are intentional changes in `gold-banking-monorepo`. Per `CLAUDE.md` §4
|
||
they must **not** be removed, restyled, restructured, or have their behaviour
|
||
changed. You may only **ADD** what the reference has and they lack, in a way that
|
||
doesn't break them. Any fix that touches a protected item needs explicit approval
|
||
with a diff plan first.
|
||
|
||
Legend: **[brief]** = named as protected in the kickoff brief · **[found]** =
|
||
discovered in Phase 0, proposed for protection · **[?]** = needs your ruling.
|
||
|
||
---
|
||
|
||
## A. Admin panel — Dashboard · **[brief]**
|
||
|
||
**Location:** `apps/admin-panel/src/views/Dashboard.vue` — one file serving both
|
||
`/dashboard/ecommerce` and `/dashboard/analytics`. KPI chrome lives in
|
||
`components/statistics-cards/StatisticsCardLine.vue`.
|
||
**Gate:** rid `dashboard.view`; lacking it redirects to `my-data` (not a 403 page).
|
||
**Data:** a single `GET admin/dashboard/overview` on mount, then client-side sorts
|
||
(`reagent` desc by `grams_sell`; `lastOrder` desc by `w`).
|
||
|
||
Contents, in render order:
|
||
|
||
1. 4 KPI tiles — مشتریان جدید (نفر) · طلای فروشرفته (گرم) · تعداد سفارشات · نرخ هر گرم طلای ۱۸ عیار
|
||
2. 2 KPI tiles — سفارشات تحویلشده · مجموع بدهی طلای مشتریان (گرم)
|
||
3. Area chart «روند سفارشها» — 2 series (تک / عمده); x-labels rotated **-45°** with
|
||
`rotateAlways` + `hideOverlappingLabels` (a deliberate overlap fix — do not "tidy" it)
|
||
4. 2 donuts — وضعیت سفارشات (8-entry colour array) · تفکیک عمده/تک
|
||
5. Horizontal bar + an always-rendered agent table (کارشناس فروش / میزان فروش گرم / تعداد سفارش)
|
||
6. Vertical bar «محصولات پرفروش» with an `a-empty` fallback
|
||
7. Sales summary «میزان فروش» — 3 equal cells incl. a computed average
|
||
8. Table «سفارشات هفته جاری» — `pageSize` 10, `sts_str` as a coloured `a-tag`, `caj` via `jdate`
|
||
|
||
**Chrome (owner-approved 2026-10-14):** panel-unified look — plain layout background,
|
||
simple page header (no brand badge), bordered ant cards, muted period labels (not gold
|
||
pills), flat KPI tiles (soft navy icon well, no lift hover / side accent bar), equal
|
||
sales-summary cells. Chart series palette stays `#1c2d56 / #28c76f / #ff9f43 / #ea5455`.
|
||
**No filters, no date range, no export, no refresh** — that is the intended design, not a gap.
|
||
Spec: `docs/superpowers/specs/2026-10-14-admin-dashboard-visual-unify-design.md`.
|
||
|
||
> Why this matters for parity: the reference's two dashboards
|
||
> (`DashboardECommerce.vue`, `DashboardAnalytics.vue`) were found in the earlier
|
||
> port to be **mostly commented-out Vuexy demo markup**, with ApexCharts imported
|
||
> but never used in live markup (`docs/CHANGELOG-PORT-HISTORY.md`). So there is
|
||
> essentially nothing to port *into* this dashboard. Visual chrome may change only
|
||
> with explicit owner approval (this section).
|
||
|
||
---
|
||
|
||
## B. Admin panel — the extra option in product Variants · **[brief]**
|
||
|
||
**Location:** `apps/admin-panel/src/views/apps/product/AddProduct.vue:151-270,
|
||
315-361, 519-568` (Create/Edit Product).
|
||
**Gate:** the card is gated `product.list.variants`; add/edit/remove on
|
||
`.add` / `.edit` / `.remove`.
|
||
|
||
Variant table columns: تصویر | عنوان | وزن | اجرت درصدی | تعداد | فعال (tag) |
|
||
« پیش سفارش» (tag) | عملیات.
|
||
|
||
**The protected extra field is `تصویر تنوع` (variant image).** It is a **picker,
|
||
not an uploader**: a 64×64 flex grid over the product's already-saved gallery
|
||
(`data.gallery`, each `{id, url, role, sortOrder}`), whose first tile is a
|
||
`بدون تصویر` option and whose selected tile gets a blue `.is-on` border.
|
||
`pickVariantImage()` sets `image_id` (posted) plus `image_url` (thumbnail only,
|
||
stripped before POST).
|
||
|
||
It is populated **only in edit mode** — on create the gallery is empty and an
|
||
`a-alert` shows a context-dependent message (`ابتدا محصول را ذخیره کنید…` vs
|
||
`برای این محصول تصویری ثبت نشده است.`).
|
||
|
||
Variants are edited **purely in memory** (row key `__key = v-<ts>-<rand>`) and
|
||
saved with the product via `PUT product/update/{id}` / `POST product/create` under
|
||
a `variants[]` array — there is **no per-variant endpoint**. Only validation is
|
||
`عنوان تنوع الزامی است`; delete has **no confirm dialog**.
|
||
|
||
> Related, and NOT protected by itself: the same `imageId`/`image` contract on the
|
||
> **user-app** side. `user-app` consumes it; `user-app-v2` does **not** (gap V2-03).
|
||
> Closing that gap in v2 is additive and does not touch this admin code.
|
||
|
||
---
|
||
|
||
## C. User apps — new designs and new features · **[brief]**
|
||
|
||
Both `apps/user-app` and `apps/user-app-v2` keep their own visual design. The
|
||
following were identified in Phase 0 as design/feature additions with no
|
||
`henza_pwa` equivalent.
|
||
|
||
### C.1 Shared by both apps
|
||
|
||
| Item | Location |
|
||
|---|---|
|
||
| `/notifications` route + screen (self-documented "no legacy equivalent") | `views/user/NotificationsView.vue` |
|
||
| `ui/PageBar.vue` replacing the legacy `AppHeader` | `components/ui/` |
|
||
| Redesigned CSS bottom nav (5 tabs; legacy was a 4-tab GSAP notch) | `layout/` |
|
||
| `GallerySlider.vue` rewrite + teleported full-screen image preview | `components/product/` |
|
||
| Variant chips with per-variant images, stock badges, «نمایش همه» | `components/product/SingleProduct.vue` |
|
||
| The whole stock / pre-order rule (`qty`, `isPreOrder`, `capQty`, `maxQty`) | `stores/cartStore.ts`, `composables/useProduct.ts` — **v1 only; porting to v2 is gap MON-01** |
|
||
| vant pull-to-refresh on 5 screens | various views |
|
||
| IntersectionObserver incremental rendering (20-item window) | list views |
|
||
| Ledger-based «اسناد» screen | `views/user/TransactionsView.vue` |
|
||
| Order-detail drawer + 4-step tracker + `useOrderItemProduct` join | `views/user/OrdersView.vue` |
|
||
| 9-chip `OrderStatus` enum | `types/` |
|
||
| Dotlottie update dialog | app shell |
|
||
| New API contracts: `GET /home/slider`, `GoldPriceQuote`, `productCode`, `collections[]`, `gold-bank:*` session keys | `api/client.ts` |
|
||
|
||
### C.2 `user-app` (design v1) only
|
||
|
||
Home family: `components/home/RateStrip.vue`, `ShowcaseSlider.vue`,
|
||
`ShowcaseCard.vue`, `CategoryRail.vue`, `SectionHead.vue`, `AccountStrip.vue`;
|
||
admin banner via `GET /home/slider` + `AdminSlide`; the «برگزیده منتزر» and
|
||
«تازهها» home sections; trust-badge rows.
|
||
|
||
### C.3 `user-app-v2` (design v2) only
|
||
|
||
`components/home/HeroCarousel.vue`, `ProductCarousel.vue`, `CategoryGrid.vue`,
|
||
`ProductPanel.vue`; the IRANSans FaNum font family (12 files,
|
||
`src/assets/styles/fonts/iransans/`); redesigned HomeView structure; redesigned
|
||
DashboardView (hero card + 4 settings rows, replacing card + tile grid); the
|
||
staggered first-paint reveal system (module-scope `hasRevealed`) in
|
||
HomeView/CartView/DashboardView; the `compact` RateStrip pill variant;
|
||
SingleProduct's scroll-driven solid header + category deep-link pill; the
|
||
pill-shaped SearchInput.
|
||
|
||
> **Important boundary.** C.2 and C.3 are *design* differences and stay separate.
|
||
> The five items in `ISSUES.md` §V2-01..05 are *logic* differences and are **not**
|
||
> protected — `CLAUDE.md` §2 requires logic to be identical across both apps.
|
||
|
||
---
|
||
|
||
## D. Backend — stricter auth than the reference · **CONFIRMED (D1)**
|
||
|
||
**Protected.** The user ruled the target's stricter auth stands as an accepted
|
||
deviation. Additional obligation from D1: build a full
|
||
"reference admin operation ↔ target rid" matrix so no operation lacks a
|
||
permission, and explain the 172-vs-174 key difference.
|
||
|
||
⚠️ **Not yet satisfied:** 5 ledger-write routes are still `@Public()`
|
||
(`DECISIONS.md` §"@Public audit" group A). They contradict D1 and are awaiting
|
||
the user's go-ahead to be protected.
|
||
|
||
The reference leaves every admin route unauthenticated (`henza_server/routes/admin.php:12`
|
||
and `:272` — the `auth:api` group wrapper is commented out at both ends, verified).
|
||
`apps/api` instead applies a global `JwtAuthGuard` + `PermissionsGuard` with
|
||
144 server-enforced rids, and only 15 routes opt out via `@Public()`.
|
||
|
||
Strict parity would mean **removing** authentication from ~215 admin endpoints.
|
||
`CLAUDE.md` §3 already carves out exactly this case ("if matching the reference
|
||
would cause … a clear security hole in money flows → STOP and ask").
|
||
|
||
**Proposed:** protect the target's stricter auth as an accepted deviation; log the
|
||
reference behaviour as `SEC-01` and the residual `@Public()` routes as `SEC-02`.
|
||
|
||
---
|
||
|
||
## E. Backend — Decimal money discipline · **CONFIRMED**
|
||
|
||
The reference uses PHP floats throughout. `apps/api` has **zero `Float` columns**
|
||
in `prisma/schema.prisma`; ledger and order math run on `Prisma.Decimal`.
|
||
|
||
Reverting to floats for "parity" would introduce rounding error into money and
|
||
gram values. **Proposed:** protect the Decimal discipline. The one leak
|
||
(`GRAMS_PER_MESGHAL` as a JS float, `ledger.service.ts:189`) is logged as `MON-03`,
|
||
to be fixed in Phase 3 — not now.
|
||
|
||
Note this does **not** exempt the target from matching the reference's *arithmetic*,
|
||
including the `floor(price/1000)*1000` rounding in `ProductVariant.php:183-190`.
|
||
Same result, better representation.
|
||
|
||
---
|
||
|
||
## F. Prior deliberate non-ports · **CONFIRMED (D4)**
|
||
|
||
Per D4: broken/destructive reference behaviour is not replicated, only logged —
|
||
but **business behaviour, calculations, rounding and messages must match the
|
||
reference exactly**, and every deliberate non-replication carries a matrix note
|
||
«عمداً تکرار نشد — ISSUE #n».
|
||
|
||
Recorded in `docs/CHANGELOG-PORT-HISTORY.md` as intentionally **not** ported:
|
||
|
||
1. Unauthenticated file upload (`file/create` had no middleware in the reference)
|
||
2. SMS sent inside `toggleActive`
|
||
3. Six destructive data behaviours — deleting a confirmed document, editing a
|
||
confirmed amount, deleting a category that has dependents, creating a category
|
||
cycle, orphaning the audit trail — each replaced with a Persian 400 message
|
||
4. The reference's avatar-load crash (`ReagentCustomer.vue:280` dereferenced
|
||
`data.image.id` unguarded)
|
||
5. The branch-mode horizontal layout (`HorizontalNavMenu`, 4 files never ported;
|
||
`mainLayoutType` narrowed to `'vertical'`)
|
||
|
||
Items 1–4 are security/data-loss carve-outs consistent with `CLAUDE.md` §3 and are
|
||
**proposed for protection**. **Item 5 is different** — it is a missing feature, not
|
||
a safety carve-out, and Phase 1 should treat it as a real gap unless you say
|
||
otherwise.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## G. Target-only capabilities · **CONFIRMED (D2) — status ➕, do not touch**
|
||
|
||
Per D2, features that exist only in the target are listed with status **➕** and
|
||
left alone. Ask before touching any of them.
|
||
|
||
| Capability | Where | Note |
|
||
|---|---|---|
|
||
| Dual-currency ledger (`LedgerEntry`, gold grams + rial) | `apps/api/src/modules/accounting/` | replaces the reference's single mixed-unit `balance` column |
|
||
| Gram / rial debt split (`goldDebtGrams`, `fiatDebtRial`) | `prisma/schema.prisma` | reference mixed both in one field |
|
||
| `Prisma.Decimal` money types throughout | schema + services | see §E |
|
||
| Server-enforced rid permissions | `auth/acl.constants.ts` | see §D |
|
||
| `UploadedFile` model + authenticated upload | `admin-uploads/` | reference upload was anonymous |
|
||
| Status-label / transition helpers | `admin-orders.service.ts` | reference had two divergent enums |
|
||
|
||
This list grows as Phase 1 and Phase 2 identify more. It is **not** a to-do list —
|
||
it is a keep-out list.
|
||
|
||
---
|
||
|
||
## Summary — confirmed status
|
||
|
||
| Section | Item | Status |
|
||
|---|---|---|
|
||
| A | Admin Dashboard | ✅ protected (brief) |
|
||
| B | Product Variants image picker | ✅ protected (brief) |
|
||
| C | User-app designs + new features | ✅ protected (brief), **excluding** the 5 v1↔v2 logic gaps (D3) |
|
||
| D | Stricter admin auth in `apps/api` | ✅ protected (D1) — 5 ledger routes still open, pending approval |
|
||
| E | Decimal money types | ✅ protected |
|
||
| F.1–4 | Prior security / data-loss non-ports | ✅ protected (D4) |
|
||
| F.5 | Branch horizontal layout | ❌ **a real gap**, not protected |
|
||
| G | Target-only capabilities | ✅ ➕ leave untouched (D2) |
|
||
|
||
### Explicitly NOT protected (parity work, per `DECISIONS.md`)
|
||
|
||
- The `fullName` collapse — **being reversed** under D0
|
||
- `percent` read from the referring agent — **wrong**, being corrected under D9
|
||
- The 5 v1↔v2 logic gaps — D3
|
||
- Unverified OTP — D6
|
||
- The public customer list — D8
|
||
- `singleCustomer.vue`'s full-form send — D11
|
||
|
||
---
|
||
|
||
## H — Retail storefront (D13) — preserved, out of current scope
|
||
|
||
Owner's ruling of 2026-09-22. The reference also ran a **retail storefront**
|
||
(B2C single-customer shop). The **wallet**, **cashback** and
|
||
`ConfigStatic['percent']` belong to it.
|
||
|
||
**Keep-out rule — stronger than "protected":** these are neither fixed nor
|
||
touched. Nothing is deleted or disabled on either side; no parity work is done
|
||
on them; they appear in the matrices only with status **⏸️ حفظشده — خارج از
|
||
کار فعلی**, with no gap row and no work row.
|
||
|
||
| Surface | Reference | Endpoints |
|
||
|---|---|---|
|
||
| Customer API (prefix `cu`) | `routes/customer.php` | 48 |
|
||
| Public site API (prefix `site`) | `routes/site.php` | 30 |
|
||
| Wallet / cashback admin | `admin.php:186-191`, `Adm\WalletController` | 6 |
|
||
| Retail-customer admin | `admin.php:35,59,60,194-199` + `single` resource | 15 |
|
||
| Retail analytics | `admin.php:38` `get-analytics-tak` → `SingleCustomerOrders` | 1 |
|
||
| Product comments admin | `admin.php:150-152` + `comment` resource | 9 |
|
||
| **Total** | | **109** |
|
||
|
||
Whatever of this already exists in `gold-banking-monorepo` stays exactly as it
|
||
is — it is not restyled, not refactored, not removed.
|
||
|
||
**Shared-surface warning.** Product, variant, price, order and customer are
|
||
shared between the B2B business and the storefront. Before changing any of
|
||
them, check whether the change reaches the storefront and **ask the owner
|
||
first** (D13 rule 4). When in doubt whether something is storefront-only or
|
||
shared, **list it and ask — never guess** (D13 rule 5).
|
||
|
||
Full reasoning and the endpoint classification: `DECISIONS.md` § D13.
|