Files

277 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.