Files
s.esmaeili ea86636027 Initial commit: Hedena Gold monorepo.
First commit of the Hedena Gold product (separate from gold-bank): branding, package names, and deploy defaults aligned to the new brand; legacy Henza trees stay local-only via .gitignore.
2026-10-15 17:19:10 +03:30

17 KiB
Raw Permalink Blame History

00 — INVENTORY & MAPPING (Phase 0)

Read-only inventory of all four reference/target pairs, plus the mapping tables that drive Phase 1 and Phase 2.

Method: seven parallel read-only subagents, one per project, each citing path:line. Their raw output lives in the _inv-*.md files next to this one; this document is the consolidated map. Claims marked ✔ below were additionally re-verified by the main session by reading the cited lines directly.

Detail inventory Project Lines
_inv-ref-server.md henza_server (reference backend) 1313
_inv-tgt-api.md apps/api (target backend) 1179
_inv-ref-admin.md henza_admin (reference admin) 1705
_inv-tgt-admin.md apps/admin-panel (target admin) 1261
_inv-ref-pwa.md henza_pwa (reference user app) 1178
_inv-tgt-userapp.md apps/user-app (target v1) 612
_inv-tgt-userappv2.md apps/user-app-v2 (target v2) 700

0. The single most important finding — the domain is NOT gold banking

The kickoff brief asks to audit "gold price, gram/weight, fees, commissions, spread, wallet/balance, buy/sell orders, deposit/withdraw, transfers, KYC levels, daily limits, payment callbacks, live price sockets". Most of that does not exist in the reference, so it cannot be a parity gap.

henza_server + henza_pwa are a gold-jewellery B2B wholesale + B2C e-commerce system (Henza Gold): a catalogue of physical SKUs priced from a gold rate, a cart, orders, and a gram-denominated customer ledger.

Verified absences in the reference user app (_inv-ref-pwa.md §6.2):

Brief's flow Reality in henza_pwa
buy gold ABSENT — nearest is add-to-cart for a physical jewellery SKU
sell gold ABSENT
wallet ABSENT — a read-only balance strip on the dashboard
deposit ABSENT — nearest is submitting a findoc of type چک/حواله/آب‌شده
withdraw ABSENT
transfer ABSENT — findoc type 2 (حواله) only records a transfer note
KYC levels PARTIAL — two flat flags, no levels
addresses ABSENT — one free-text field on the user
tickets / support ABSENT — a static WhatsApp FAB (index.html:59-61)
daily/min/max limits only one exists: gram_level (ApiMobileController.php:528-758)
live price socket ABSENT — BROADCAST_DRIVER=null, zero ShouldBroadcast events ✔
payment callback route site/transaction/verify is not registered anywhere
cron Console/Kernel::schedule() is empty

Consequence for the plan: the parity matrices will cover what the reference actually has — catalogue, pricing formula, cart/orders, ledger ("اسناد"), customer management, ACL — and will not manufacture rows for gold-banking features. Where the target has something bank-like, it is an intentional addition, not a gap.


1. Stack mapping

Layer Reference Target Note
Backend Laravel/PHP, 46 controllers, 57 models NestJS 10 + Prisma 5, 25 modules, 29 models total rewrite; port behaviour only
Admin Vue 2 + Vuesax/Vuexy template, Vuex Vue 3.5 + ant-design-vue 4.2.6, Pinia total rewrite
User app Vue 3 + Vite + vant + ant-design-vue 3 Vue 3 + Vite, two design versions closest pair
Money types PHP floats throughout Prisma.Decimal, zero Float in schema ✔ target is stricter — keep it

2. Backend mapping — henza_server ⇄ apps/api

2.1 Scale

Metric henza_server apps/api
Routes / endpoints 412 (admin 249, api 70, customer 48, site 30, web 15) — corrected, see below 179
Controllers 46 25
Models 57 files (34 migrated tables + ~28 legacy Django-era tables) 29 Prisma models
Enums PHP static arrays 14 Prisma enums
DTO / validation zero FormRequests, all inline, several endpoints unvalidated 8 DTO classes; most admin bodies untyped Record<string, unknown>
Seeders 1, empty prisma/seed.ts — user types, 144 rids, admins

The 412 → 179 drop is expected, not automatically a gap: the reference counts Route::resource expansions, ~28 dead legacy tables, a site.php storefront and a web.php that the monorepo replaces with its own apps. Which of the in-scope routes are genuinely unmapped is Phase 1 / Phase 2 matrix work, per route group.

⚠️ Count correction (2026-09-22). The 369 reported throughout Phase 0 is wrong; the real total is 412. Recounting the live routes (excluding the 6 commented-out ones, expanding Route::resource ×7 and Route::match(['get','post']) ×2) gives 412. The error was in admin.php: §3.6 of _inv-ref-server.md recorded 117 explicit routes where the file actually has 151, so admin is 249, not 215. Verified with grep -E "^\s*Route::(get|post|…)" | grep -v "^\s*//" per file.

File Explicit match ×2 resource ×7 Total
admin.php 151 0 14 → 98 249
api.php 61 1 → 2 1 → 7 70
customer.php 18 1 → 2 4 → 28 48
site.php 30 0 0 30
web.php 13 1 → 2 0 15
Total 412

Scope split (D13, 2026-09-22). The retail storefront is preserved but out of scope, so the 412 divide as follows:

Group Endpoints Scope
Admin panel — B2B 218 ✅ Phase 1
B2B mobile app (api.php) 70 ✅ Phase 2
Retail storefront — customer API (customer.php) 48 ⏸️ preserved, out of scope
Retail storefront — public site API (site.php) 30 ⏸️ preserved, out of scope
Retail storefront — admin management (wallet 6, single 15, comment 9, get-analytics-tak 1) 31 ⏸️ preserved, out of scope
web.php dev/ops closures, price cron, redirects 15 ⏸️ out of scope (dead/dev)
Total 412 288 in scope · 124 out

109 of the 412 are retail storefront; 15 more are web.php dev/ops. Full classification with evidence: DECISIONS.md § D13.

2.2 Core business logic — where each rule lives

Rule Reference Target
Gold price formula ✔ app/Models/ProductVariant.php:183-190 apps/api/src/modules/... (Phase 1 to map)
Price source app/Models/WebServiceGold.php — getPriceGold18Ayar() divides by 10 (rial→toman) pricing module
Order pipeline app/Http/Controllers/Controller.php — createSingleCustomerOrders, afterCreateOrder, cashback at sts 6 orders.service.ts, admin-orders.service.ts
Wholesale order + the only limit ApiMobileController.php:528-758 (gram_level) orders.service.ts
Retail checkout Customer/OrderController.php:68-168 orders.service.ts
Ledger / wallet packages/rooyesh/wallet/ — hash chain, pepper "yhoenszeaf" accounting/ledger.service.ts (dual-currency, Prisma.Decimal)
Cashback Adm/AdminController.php:985-1013 (chargeWalletAfterOrder, in grams) admin-customers.service.ts
Order statuses two divergent enums: Order.php:66 (0–6) and SingleCustomerOrders.php:67 (1–7) OrderStatus + PreOrderStatus
SMS / OTP app/Models/Sms.php absent — DB persistence only

The gold price formula, verified ✔

henza_server/app/Models/ProductVariant.php:183-190:

$price = goldPrice + (goldPrice * ((percentage_pay + product_price_extra_percent) / 100));
$price += $price * 0.07;                 // 7% margin
$temp   = $price - goldPrice;            // non-metal portion only
$temp  += $temp * 0.09;                  // 9% VAT on that portion only
$price  = goldPrice + $temp;
$price *= grams;
return floor($price / 1000) * 1000;      // round DOWN to the nearest 1000

Three superseded variants sit commented out above it — they apply VAT to the whole price, which is wrong and must not be ported. getPriceWithDiscountAttribute (:255-280) is the same formula with 0.07 - discount_percent/100 and a trailing price -= price * (discount_price/100).

This formula, its ordering, and the floor(/1000)*1000 are row #1 of the Phase 1 matrix — it is the money rule the whole catalogue depends on.

2.3 Auth & permissions

Aspect Reference Target
Admin route protection NONE ✔ — auth:api commented at routes/admin.php:12 and :272, so all ~215 admin routes are public global JwtAuthGuard + PermissionsGuard; 15 routes opt out with @Public() ✔
Permission model Permission(rid, ut) rows returned at login (AdminController:74), enforced client-side only — no Gates/Policies/Spatie acl.constants.ts — 144 rids + expandWithParents(), enforced server-side
Permission catalogue source not derivable from the server; lives in the admin at views/user/permissionConst.js (172 declared keys) acl.constants.ts (144 rids) + permissionConst.ts (179 keys, 174 unique)
Parent expansion store/users/index.js:3-21 checkParent() — order.list.sts.remove implies order.list.sts, order.list, order expandWithParents() — same semantics
Token refresh machinery exists but is unreachable (main.js:22-23 commented) none

The target is deliberately stricter than the reference on auth. That is a prior, documented decision (docs/CHANGELOG-PORT-HISTORY.md), not a parity gap — and reverting it would reopen a critical hole. Logged as SEC-01/SEC-02. This needs your confirmation (see Q1).


3. Admin mapping — henza_admin ⇄ apps/admin-panel

Metric henza_admin apps/admin-panel
.vue under views/ 541 — but only ~69 reachable from the live router; the rest are Vuexy template demos 91 .vue + 19 .ts table models = 110 files
Routes admin build + branch build (two route trees) 80 admin-mode + 15 branch-mode records
Permission keys 172 in permissionConst.js 179 (174 unique) in permissionConst.ts
Rids enforced by meta.role ~58 ~58
Button-level $hasPermission catalogued in _inv-ref-admin.md §5.3 73 distinct rids via hasPermission()
HTTP src/axios.js, no response interceptor, no working refresh src/api/client.ts + endpoints.ts legacy→REST map (~100 exact + 34 regex), 401 → clear token + redirect
Table system src/components/x-table/ (4 forked copies) one XTable, POST {search, filters, page, limit, order, select, titles, report_title} → {rows, query}
Digit/price utils none exist (_inv-ref-admin.md Appendix B #17) none, except inside DateInput.vue

Route-tree note: src/router/{apps,category,customer,dashboard,full,manage,order,product,temp}.js exist in the reference but are not imported — only admin.js and branch.js are (src/router.js:6,8). Nine dead route files. They must not be mistaken for missing pages in Phase 1.

3.1 Mapping status

A route-by-route and action-by-action mapping is Phase 1's deliverable (01-ADMIN-MATRIX.md). What Phase 0 establishes is the denominator: ~69 live reference views against 91 target views, with the reference's 172-key permission tree against the target's 174-unique-key tree.


4. User-app mapping — henza_pwa ⇄ user-app + user-app-v2

4.1 Routes

Reference route name user-app / user-app-v2 Status
/home home /home → HomeView.vue ✅
/collection/:id? collection same ✅
/search/:id? search /search — no :id? param ⚠️ different
/product/:id singleProduct components/product/SingleProduct.vue ✅
/newest newestProduct same ✅
/user userPage::hasSubRoute DashboardView.vue ✅
/orders ordersList same ✅
/transactions transactionsList same ✅
/profile profileData same ✅
/favorites favoritesList same ✅
/cart cart same ✅
/login login LoginRegisterView.vue ✅
/report report — ❌ missing
/report/detail reportDetail — ❌ missing
/ChildOrder ChildOrder (sales-rep child orders) — ❌ missing
/customerChoice customerChoice (impersonation) — ❌ missing
/complete_profile completeProfile (document upload / KYC) — ❌ missing
— — /notifications ➕ extra in target

Verified by reading henza_pwa/src/router/index.ts:98-215 against apps/user-app/src/router/index.ts:18-45 ✔. The two target routers are byte-identical (diff returns nothing) ✔ — good for the v1/v2 parity rule.

4.2 Guard differences ✔

Behaviour henza_pwa/src/router/index.ts target
unauth hitting a gated route showNotify('ابتدا وارد شوید') then redirect to home (:250-254) redirect to /login with ?redirect=, no toast
gated route list ordersList, transactionsList, profileData, favoritesList, completeProfile meta.requiresAuth on 4 (no completeProfile — the route doesn't exist)
logged-in hitting /login → home (:256) → home ✅
!isLoaded && to.name !== 'home' → home (:258) absent
sts_verify_step_two ∈ {1,2} blocks completeProfile :260 absent
route analytics useCustomAnalytics() → POST /log/create/{key}, GET /log/update/{id} (customAnalytics.ts:25,32) absent entirely

4.3 The v1 ⇄ v2 rule

CLAUDE.md §2: the only allowed difference is visual design. Five logic differences found, all with v2 behind v1 — these are parity gaps inside the target, independent of the reference:

# Gap in user-app-v2 Evidence
1 No cart stock ceiling — no capQty/maxQty; a user can order more units than exist v1 stores/cartStore.ts:62,65, components/product/CartItem.vue:82-83; zero hits in v2 ✔
2 No getHomeSlider() / GET /home/slider admin banner diff of the two api/client.ts ✔
3 No per-variant images — v1 has ProductImageSummary{id,url,sortOrder,role} + imageId/image; v2's images is Array<{url}> same diff ✔
4 No orderable-first variant defaulting v1 composables/useProduct.ts:26
5 No variant-chip preview cap / per-chip stock badges v1 components/product/SingleProduct.vue:388-392

Logged as MON-01 and V2-01..05 in ISSUES.md; they become Phase 2 matrix rows.

4.4 Live price — reference vs target ✔

Neither side has a socket. But the reference has a two-path bug worth knowing before porting: baseStore.getPriceList() (store.ts:107-156) and Dashboard.getListPrice() (Dashboard.vue:632-660) both do a one-shot GET /price; the visible grid comes from the second, while the manual refresh button re-runs the first — so the refresh button does not update the list the user is looking at. Also prices.date/prices.time assignments are commented out (store.ts:145-147), so the dashboard's date/time header is permanently blank.

Target: one-shot GET /pricing/latest?karatPerMille=750 on mount + pull-to-refresh, in HomeView and DashboardView only.


5. What Phase 0 did NOT determine (goes to Phase 1 / Phase 2)

  • Route-by-route mapping of the reference's 412 backend routes (288 in scope after D13) onto the target's 179.
  • Field-by-field comparison of request/response shapes per endpoint.
  • Per-view form/validation/table comparison for the ~69 live admin views.
  • Whether the target's gold-price implementation matches ProductVariant.php:183-190 exactly, including the floor(/1000)*1000. This is Phase 1 row #1.
  • Whether the 174 target permission keys are a superset/subset of the reference's 172.

6. Open questions — need your answer before Phase 1

See the gate message; recorded here so they survive a context reset.

  • Q1 — Auth on admin routes. The reference leaves all ~215 admin routes unauthenticated (routes/admin.php:12 commented). The target enforces JWT + per-rid permissions. Strict parity would mean removing auth. Confirm the target's stricter behaviour stands as an accepted deviation.
  • Q2 — Scope of the "gold banking" checklist. Confirm we audit the domain the reference actually implements (§0) rather than manufacturing rows for buy/sell/deposit/withdraw/KYC-levels/sockets that exist nowhere.
  • Q3 — The 5 missing user-app routes (/report, /report/detail, /ChildOrder, /customerChoice, /complete_profile). These are real reference features absent from both target apps. Building them means ~5 screens × 2 designs plus backend. In scope for Phase 2, or deferred?
  • Q4 — Reference bugs. 20 defects found in henza_pwa and 18 in henza_admin (listed in the _inv-* appendices). CLAUDE.md §3 says replicate + log. Confirm we do not replicate the data-destroying / clearly-broken ones (e.g. the refresh button that updates an invisible list), and log them instead.
  • Q5 — Stray folder gold-banking-monorepo/user-app-v2/ (repo root, outside apps/): only package.json + an outdated vite.config.ts, ignored by pnpm-workspace. Delete, or leave?