# Inventory — henza_pwa (reference user app) > Scope: `E:\gold bank\henza_pwa`. All `path:line` are relative to `henza_pwa/`. > Excluded from the file census: `node_modules`, `dist`, `3.0.0`, and two **vendored** libraries checked into `src/components/`: `src/components/DatePicker/DatePicker/**` (forked ant-design-vue `vc-picker` + Jalali locale) and `src/components/JalaliAntDatePicker/**` (forked ant-design-vue `_util`/components). Those are third-party code, not app code. > > **Critical framing finding:** this app is **NOT** a consumer "gold bank" (buy gram / sell gram / wallet / deposit / withdraw / transfer / KYC-level gating). It is a **B2B wholesale gold jewelry catalog + ordering app** for jewelers (`مشتری`/`کارشناس فروش`/`بنکدار`). There is **no buy-gold, sell-gold, wallet, deposit, withdraw, transfer, cart-payment, address-book, ticket/support, or KYC-level system** anywhere in the codebase (verified by exhaustive grep of every axios call — see §2.4). The closest analogues are documented per-section and flagged **ABSENT** where the requested feature does not exist. --- ## 1. Stack & versions Source: `package.json:1-58`. | Concern | Package | Version | Evidence | |---|---|---|---| | Framework | `vue` | `^3.2.29` | `package.json:34` | | Build | `vite` | `^3.2.5` | `package.json:52` | | Vue plugin | `@vitejs/plugin-vue` | `^2.0.0` | `package.json:42` | | JSX plugin | `@vitejs/plugin-vue-jsx` | `^3.0.0` | `package.json:43` | | UI library (primary) | `ant-design-vue` | `^3.2.20` | `package.json:17` | | UI library (mobile) | `vant` | `^4.0.3` | `package.json:33` | | Icons | `@ant-design/icons-vue` `^6.1.0`, `feather-icons` `^4.28.0`, `vue-feather` `^2.0.0-rc.1` | — | `package.json:10,21,35` | | Router | `vue-router` | `^4.0.12` | `package.json:36` | | Store (Pinia) | `pinia` | `^2.1.4` | `package.json:30` — **only used by one store** (`src/views/report/store/report.ts:9`). All other state is hand-rolled class singletons. | | HTTP client | `axios` | `^0.25.0` | `package.json:18` | | Date (Jalali) | `moment-jalaali` `^0.10.0`, `jalaliday` `^2.3.0`, `jalali-dayjs` `0.0.1`, `dayjs` `^1.11.6`, `date-fns` `^2.29.3` | — | `package.json:19,20,24,25,29` | | Date picker | `vue3-persian-datetime-picker` `^1.2.2` (+ vendored fork under `src/components/DatePicker/`) | — | `package.json:38` | | PWA plugin | `vite-plugin-pwa` | `^0.11.13` | `package.json:54` | | Service-worker toolkit | `workbox-precaching` | `^6.4.2` | `package.json:56` | | **Socket / live-price lib** | **NONE** | — | No `socket.io`, `ws`, `pusher`, `centrifuge`, `sse`, `eventsource` in `package.json`. Live price is **manual pull only** — see §10. | | Push notifications | `firebase` `^9.6.7`, `@firebase/app` `^0.7.17`, `@firebase/messaging` `^0.9.8` | — | `package.json:11,12,22` — FCM wiring exists but is **disabled** (`src/App.vue:104-106` `useFirebase()` commented out) | | Animation | `gsap` `^3.9.1` | — | `package.json:23` (used by bottom-nav morph, `src/components/ui/navigation.vue:83`) | | Lottie | `@lottiefiles/dotlottie-vue` `^0.4.15` | — | `package.json:14` (version-update dialog, `src/App.vue:24`) | | Carousel | `swiper` `^7.4.1` | — | `package.json:32` | | Lazy images | `vue3-lazyload` `^0.2.5-beta` | — | `package.json:37` (registered `src/main.ts:32-46`) | | Utils / composables | `lodash` `^4.17.21`, `@vueuse/core` `^9.13.0` | — | `package.json:28,16` | | CSS | `tailwindcss` `3.0.18`, `less` `^4.1.2`, `postcss-rtl` `^1.7.3`, `autoprefixer`, `@fullhuman/postcss-purgecss` | — | `package.json:13,26,31,44,50` | | TS | `typescript` `^4.4.4`, `vue-tsc` `^0.29.8` | — | `package.json:51,55` | | App version | `1.0.7` | — | `package.json:3` (compared against `localStorage.version` at `src/App.vue:57-68`) | --- ## 2. HTTP layer ### 2.1 Base URLs & config `src/service/` contains a **single** file (`src/service/hesab.ts`) with 5 functions. **Every other API call is inlined in views/stores/mixins.** Full census in §2.4. | Key | Value | Evidence | |---|---|---| | `$themeConfig.baseURL` | `https://henzagold.com/oapi/api/` | `themeConfig/themeConfig.ts:43` | | `$themeConfig.coreURL` | `https://henzagold.com/oapi/` | `themeConfig/themeConfig.ts:45` | | `baseURL` (named export, unused by axios) | `https://henzagold.com/oapi/api/` | `themeConfig/themeConfig.ts:40` | | `$themeConfig.baseRoute` | `/` | `themeConfig/themeConfig.ts:47` | | `$themeConfig.outDir` | `dist` | `themeConfig/themeConfig.ts:48` | | `$BasePath` | dev → `http://localhost:3030`, prod → `https://app.henzagold.com` | `themeConfig/themeConfig.ts:50` | | Commented-out local alternatives | `http://localhost/api/`, `http://localhost/oapi/` | `themeConfig/themeConfig.ts:44,46` | There is **no `.env` / `import.meta.env` usage anywhere** — all config is hard-coded in `themeConfig/themeConfig.ts`. `vite.config.ts:91-93` stubs `process.env` to `{}`. ### 2.2 Axios instances (`src/plugins/axios.ts`) Three instances, all created with the same static default `headers` object built **once at module load** (`src/plugins/axios.ts:6-10`): ``` Content-Type: application/json accept: application/json Authorization: Bearer ${getUserToken()} // snapshot at import time ``` | Export | baseURL | Request interceptor | Evidence | |---|---|---|---| | `default` (`ax`, imported everywhere as `@axios`) | `$themeConfig.baseURL` | Overwrites `config.headers.Authorization = 'Bearer ' + getUserToken()` on **every** request | `src/plugins/axios.ts:15-24` | | `noHeaderAxios` | `$themeConfig.baseURL` | Pass-through (`config => config`) — caller must set `Authorization` manually | `src/plugins/axios.ts:27-32` | | `cax` | `$themeConfig.coreURL` | Sets `Authorization = 'Bearer ' + localStorage.getItem('appToken')` (ignores child token) | `src/plugins/axios.ts:37-45` — **`cax` is never imported anywhere** (dead export) | `@axios` alias → `src/plugins/axios` (`vite.config.ts:61-64`). `@themeConfig` → `themeConfig/themeConfig` (`vite.config.ts:57-60`). `/@/` → `src/` (`vite.config.ts:65-68`). ### 2.3 Interceptors, token, 401, error toasts | Aspect | Reality | Evidence | |---|---|---| | **Request interceptor** | Only attaches `Authorization`. Error handler is a **no-op bug**: `error => { Promise.reject(error) }` — result not returned, so the promise resolves `undefined`. | `src/plugins/axios.ts:17-23`, same bug at `:29-31` and `:39-45` | | **Response interceptor** | **NONE.** No response interceptor is registered on any instance. | `src/plugins/axios.ts` (entire file, 46 lines) | | **Token source** | `getUserToken() = userChildToken() ?? userToken()` | `src/store/tokenManage.ts:8-10` | | `userToken()` | `localStorage.getItem('appToken') \|\| localStorage.appToken \|\| null` | `src/store/tokenManage.ts:4-6` | | `userChildToken()` | `localStorage.getItem('activeChildToken') \|\| localStorage.activeChildToken \|\| null` | `src/store/tokenManage.ts:1-3` | | **Impersonation** | If a sales-rep selects a customer, that customer's `api_token` is stored as `activeChildToken` and **takes priority** over the rep's own token for all requests. | `src/store/myDataStore.ts:159-169` | | **Refresh logic** | **ABSENT.** No refresh token, no retry queue, no `/refresh` endpoint anywhere. | verified by grep — no `refresh` API call exists | | **401 handling** | Handled in **exactly one place**, `myStore.init()`: on `e.response.status === 401` → remove `appToken`, `appCart`, `activeChildToken`; `toggleActiveUser(undefined)`; `router.push({name:'home'})`; then `throw e`. | `src/store/myDataStore.ts:134-145` | | **403 handling** | **ABSENT.** No `403` reference anywhere. | grep | | **Global error toast** | **ABSENT.** Every call site does its own `try/catch { console.log(e) }`. Toasts are per-view (`showNotify` / `NotifyVant` / `message.error`). | e.g. `src/views/home/index.vue:218-222`, `src/views/cart.vue:189-193`, `src/Mixins/getProducts.ts:35-37` | | **Per-call error toasts that DO exist** | `addUser` duplicate-mobile (`src/views/user/src/components/addUser.vue:182-189`), upload errors (`src/components/SingleImageUploader.vue:94-97,117-122,129-139`), login 404/other (`src/views/user/LoginRegister/components/Login.vue:147-154,177-183`), makeOrder (`src/views/user/src/components/makeOrder.vue:224,229`), editUser success (`src/views/user/src/components/editUser.vue:285-291`) | — | ### 2.4 Complete API function census Every network call in the app (excluding the two vendored libs). **58 distinct call sites, 44 distinct endpoints.** #### `src/service/hesab.ts` (the only "service" file — 5 functions) | # | Function | Method | URL | Params / body | Return type | Evidence | |---|---|---|---|---|---|---| | 1 | `hesabDetail(page=1, limit=10)` | POST | `/hesab_detail/table` | body `{limit, page}` | `TableOutput` | `src/service/hesab.ts:12-14` — **never called** (only imported at `src/views/report/index.vue:74`, call commented out at `:215`) | | 2 | `hesabDetailNew(page=1, limit=10)` | GET | `/fin_tahesab/table` | **params ignored** (signature takes them, URL uses none) | `TableOutput` | `src/service/hesab.ts:16-18` | | 3 | `accountingDetail(factor_code?)` | GET | `/fin_tahesab/detail/${factor_code}` | path param | `TableOutput` | `src/service/hesab.ts:20-22` | | 4 | `accountingDetailList(offset?)` | GET | `/fin_tahesab/my_detail/${offset}` | path param | — | `src/service/hesab.ts:23-25` | | 5 | `hesabMe()` | GET | `/tahesab/me` | — | `any` | `src/service/hesab.ts:26-28` | #### Auth & profile | # | Method | URL | Body / params | Called from | Evidence | |---|---|---|---|---|---| | 6 | POST | `/web/auth` | `{username}` (phone) | Login step 1 | `src/views/user/LoginRegister/components/Login.vue:137` | | 7 | POST | `/user-verfiy` *(sic — typo in endpoint)* | `{username, password /*=OTP*/, token_mobile}` | Login step 2 | `src/views/user/LoginRegister/components/Login.vue:165` | | 8 | POST | `/major/register` | `{fname, lname, mobile, store_name, phone, address, city}` | Register | `src/views/user/LoginRegister/components/Register.vue:332` | | 9 | POST | `/major/complete` | `{business_license_image_id, shop_image_id, ncode_image_id}` | Complete profile (doc upload) | `src/views/user/src/completeProfile.vue:193` | | 10 | POST | `/me-data` | `{}` | `myStore.init()` — **called twice**: once via `noHeaderAxios` with explicit parent token, once via `axios` with child token | `src/store/myDataStore.ts:120-126` (noHeaderAxios), `:130` | | 11 | POST | `/me-data` | `{}` | makeOrderStatus bootstrap | `src/views/user/src/components/makeOrderStatus.vue:180` | | 12 | POST | `/update-profile` | `{image_id, phone, address, fname, lname, mobile, birth_date, marriage_date}` | editUser | `src/views/user/src/components/editUser.vue:289` | | 13 | POST | `/cities/list` | `{}` → `{rows: City[]}` (flat list w/ `level`,`parent`) | Register city cascader | `src/views/user/LoginRegister/components/Register.vue:299` | #### Catalog / products | # | Method | URL | Body / params | Called from | Evidence | |---|---|---|---|---|---| | 14 | POST | `/front` | `{}` → `{products, more, cats, ciny}` | Home bootstrap | `src/views/home/index.vue:184` | | 15 | POST | `/front` | `{}` (for `ciny` fallback list) | `getCollection` in products mixin | `src/Mixins/getProducts.ts:46` | | 16 | POST | `/get-products` | full `Query` object (see §8) → `{rows, count, limit}` | product list / search / newest | `src/Mixins/getProducts.ts:28` | | 17 | POST | `/get-products` | `{products_id: number[]}` → `{rows}` | cart re-hydration | `src/store/cartStore.ts:41` | | 18 | GET | `/ciny/${cats[0]}` | path param = first category id | collection list for a category | `src/Mixins/getProducts.ts:42` | | 19 | GET | `/product/${id}` | path param (from route) | single product | `src/views/singleProduct.vue:283` | | 20 | GET | `/product/${props.id}` | path param (from prop, drawer mode) | single product in drawer | `src/views/singleProduct.vue:304` | | 21 | GET | `/config` | — → `{about, main_slider}` | base store init | `src/store/store.ts:63` | | 22 | GET | `/promotion` | — (only if logged in) | base store init | `src/store/store.ts:74` | | 23 | GET | `/ignore` | — (only if logged in) | base store init | `src/store/store.ts:86` | | 24 | GET | `/ignore` | — | `getCustomerListType()` — **dead**, never called, duplicates #23 | `src/store/store.ts:98` | | 25 | GET | `/price` | — → keyed object of price rows | base store `getPriceList()` | `src/store/store.ts:114` | | 26 | GET | `/price` | — → array or object | Dashboard `getListPrice()` (separate, newer path) | `src/views/user/src/components/Dashboard.vue:634` | #### Favorites | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 27 | POST | `/add-to-fav` | `{id}` — **toggle** (optimistic local toggle first) | favorite mixin | `src/Mixins/favorite.ts:8` | | 28 | POST | `/get-favorites` | `{page}` → `{rows}` | favorites list | `src/views/user/src/components/favorits.vue:52` | #### Orders (catalog orders) | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 29 | POST | `/set-order` | `{final_submit:false, from_parent:boolean, id:null, items:[{discount, id:null, note, percent_pay, product, promotion_id, promotion_item_id, qty, rash_weight, rial_pay:0, variant, wage_weight, weight}]}` → `{id}` | cart checkout | `src/views/cart.vue:161-185` | | 30 | POST | `/get-orders` | `{sts}` → `{rows}` (`sts = -1` for all) | orders list / tab change | `src/views/user/src/components/Orders.vue:290` | | 31 | POST | `/order/child` | `{page, search}` → `{rows}` | sales-rep: customers' orders | `src/views/user/src/components/ChildOrders.vue:332` | | 32 | PUT | `/order/child/${id}` | `{items:[...w, rash_weight], final_submit, id, from_parent:true}` | sales-rep: edit/finalize a customer order | `src/views/user/src/components/ChildOrders.vue:400` | #### Custom manufacture orders ("سفارش ساخت") | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 33 | POST | `/create-custom-order` | `{color, size, color_mina, color_stone, sum, file_id, description}` | makeOrder submit | `src/views/user/src/components/makeOrder.vue:221` | | 34 | GET | `/get-custom-orders` | — → `{rows}` | makeOrderStatus list | `src/views/user/src/components/makeOrderStatus.vue:187` | | 35 | POST | `/update-custom-order/${orderId}` | `{sts: 2}` | (commented-out generic updater) | `src/views/user/src/components/makeOrderStatus.vue:197` | | 36 | POST | `/update-custom-order/${orderId}` | `{sts: 2}` | `confirmOrder` | `src/views/user/src/components/makeOrderStatus.vue:212` | | 37 | POST | `/update-custom-order/${orderId}` | `{sts: 2}` | `completeOrder` — **same `sts:2` as confirm (likely bug)** | `src/views/user/src/components/makeOrderStatus.vue:234` | #### Financial documents ("اسناد" / findoc) | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 38 | POST | `/get-my-findoc` | `{}` → `{rows}` — **no pagination, no filters** | transactions list | `src/views/user/src/components/transactions.vue:203` | | 39 | POST | `/set-findoc` | type-dependent (see §6.9) | addTransaction (page) | `src/views/user/src/components/addTransaction.vue:196` | | 40 | POST | `/set-findoc` | type-dependent (identical) | addFindoc (drawer, duplicate) | `src/views/user/src/components/addFindoc.vue:204` | #### Customers (sales-rep only) | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 41 | POST | `/my-child` | `{}` → customer array | customerChoice list | `src/views/user/src/components/customerChoice.vue:184` | | 42 | POST | `/customer/toggle-active` | `{id}` | customerChoice activate/deactivate | `src/views/user/src/components/customerChoice.vue:199` | | 43 | POST | `/customer/toggle-active` | `{id}` | Dashboard `setActive` — **dead** (`getCustomers` is undefined in that scope) | `src/views/user/src/components/Dashboard.vue:593` | | 44 | POST | `/customer/create` | full `userFormModel` | addUser (create mode) | `src/views/user/src/components/addUser.vue:417` | | 45 | POST | `/customer/update` | `{...userFormModel, id: route.query.edit}` | addUser (edit mode) | `src/views/user/src/components/addUser.vue:406` | | 46 | GET | `https://henzagold.com/oapi/admin/major/list_type` | **absolute URL, bypasses baseURL, hits `/admin/`** | addUser: customer-type dropdown | `src/views/user/src/components/addUser.vue:457-460` | #### Notifications | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 47 | POST | `get-notify` *(no leading slash)* | `{page}` → `{rows}` | notification list (infinite scroll) | `src/views/home/src/components/NotificationList.vue:101` | | 48 | GET | `/notify/count` | — → number | unread badge | `src/views/home/src/components/NotificationList.vue:117` | | 49 | PUT | `notify/read/${id}` *(no leading slash)* | — | mark read | `src/views/home/src/components/NotificationList.vue:132` | > `NotificationList.vue` is **not mounted** — its usage in Home is commented out (`src/views/home/index.vue:5`). #### Files | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 50 | POST | `https://henzagold.com/oapi/api/file/create` | `FormData{file, has_thumb:'0', has_watermark:'0'}`, header `Content-Type: multipart/form-data` → `{id, image}` | SingleImageUploader | `src/components/SingleImageUploader.vue:107-109` | #### Analytics (fires on every route change) | # | Method | URL | Body | Called from | Evidence | |---|---|---|---|---|---| | 51 | POST | `/log/create/${key}` | `{}` or `{data:{product_id}}` when `to.name === 'singleProduct'`; `key = meta.title` + `-${params.id}` | router `beforeEach` | `src/router/customAnalytics.ts:26` | | 52 | GET | `/log/update/${lastRequestReturnedId}` | — (closes previous page-view) | router `beforeEach` | `src/router/customAnalytics.ts:17` | | 53 | POST | `/log/create/${routeName}` | — | `useCustomAnalyticsComponent` composable, `onBeforeMount` | `src/router/customAnalytics.ts:40` | | 54 | GET | `/log/update/${requestReturnedId}` | — | same composable, `onBeforeUnmount` | `src/router/customAnalytics.ts:53` | Excluded routes for auto-analytics: `['login', 'userPage::hasSubRoute']` (`src/router/customAnalytics.ts:6`). #### Non-axios external URLs (`window.open`) | Purpose | URL | Evidence | |---|---|---| | Order invoice PDF | `https://henzagold.com/oapi/api/report/order?order_id=${id}` (target `_bank`) | `src/views/user/src/components/Orders.vue:334` | | Findoc file | `https://henzagold.com/oapi/api/report/doc?doc_id=${id}` (target `_bank`) | `src/views/user/src/components/transactions.vue:225` | | Phone call | `tel:${mobile}` | `src/views/user/src/components/profile.vue:149-151` (line numbering within `