Files
tehran_taj/docs/parity/_inv-tgt-admin.md
T
s.esmaeiliandClaude Opus 5 71192ba2ba parity(docs): Phase 0 inventory, mapping and draft PROTECTED list
Read-only inventory of all 7 projects (3 reference + 4 target) produced by
parallel subagents, every claim cited path:line. No source code changed.

- 00-INVENTORY.md  consolidated mapping: stack, backend, admin, user apps
- PROTECTED.md     draft protected list (awaiting user confirmation)
- ISSUES.md        26 logged issues (security, money/stock, validation, dead code)
- CHANGELOG.md     change log scaffold
- PROGRESS.md      resumable state + Q1-Q5 blocking Phase 1
- _inv-*.md        7 raw per-project inventories (8111 lines)

Key finding: the reference is a gold-jewellery B2B/B2C e-commerce system, not a
gold bank. Buy/sell, deposit/withdraw, transfer, KYC levels, payment callbacks,
cron and websockets do not exist in it, so they cannot be parity gaps.

[ref: henza_server, henza_admin, henza_pwa]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 18:09:46 +03:30

117 KiB
Raw Blame History

Inventory — apps/admin-panel (target admin)

All paths relative to apps/admin-panel/. Read from source; node_modules, dist, branchDist ignored. Root = E:\gold bank\gold-banking-monorepo\apps\admin-panel.


1. Stack & versions

Source: package.json:1-37.

Concern Package Version Line
Framework vue ^3.5.0 package.json:23
UI library ant-design-vue ^4.2.6 package.json:17
UI icons @ant-design/icons-vue ^7.0.1 package.json:14
Router vue-router ^4.4.0 package.json:24
Store pinia ^2.2.0 package.json:22
HTTP client axios ^1.7.7 package.json:19
Date library moment-jalaali ^0.10.0 package.json:21
Charts apexcharts ^7.4.0 + vue3-apexcharts ^1.11.1 — package.json:18,25
Utility lodash ^4.17.21, @vueuse/core ^9.13.0 — package.json:16,20
Shared types @gold-bank/shared-types (workspace:*) — package.json:15
Build vite ^5.4.0, @vitejs/plugin-vue ^5.1.0, vue-tsc ^2.1.0, typescript ^5.5.0, less ^4.1.2 — package.json:28-35

Scripts (package.json:5-12): dev (vite), dev:branch (vite --mode branch), build (vue-tsc -b && vite build), build:branch (--mode branch), preview, lint.

Bootstrap src/main.ts:15-22: createApp(App) → createPinia() → Antd → router → VueApexCharts → mount #app. Styles ./assets/styles/main.less (src/main.ts:13). Vue-2 legacy plugins (Vuesax, VeeValidate, vue2-hammer, vue-clipboard2, ACL, i18n) intentionally dropped (src/main.ts:1-5).

index.html:2 — <html lang="fa" dir="rtl">, title منتزر — پنل مدیریت (index.html:7), preloader #loading-bg removed by router.afterEach (index.html:12, src/router/index.ts:63-68).

src/env.d.ts:12-17 declares the global apexchart component for vue-tsc.


2. HTTP layer

2.1 Client — src/api/client.ts

Aspect Value Line
Base URL import.meta.env.VITE_API_BASE_URL ?? '/api/' src/api/client.ts:11
Default headers Content-Type: application/json, Accept: application/json src/api/client.ts:13-16
Boot token reads localStorage[tokenName] and seeds Authorization: Bearer <t> src/api/client.ts:18-21
Instance axios.create({ baseURL, headers }) src/api/client.ts:23-26

Request interceptor (src/api/client.ts:34-58):

  1. Rewrites config.url through mapEndpoint() (:36-38).
  2. Re-reads token each request; sets Authorization: Bearer <token> when present, otherwise deletes the header (legacy sent literal "Bearer null" — fixed) (:39-44).
  3. If config.data instanceof FormData, deletes Content-Type so the browser sets the multipart boundary (:52-54) — required for file/create uploads.

Response interceptor (src/api/client.ts:68-82): NOT in legacy. On HTTP 401, if not already on /auth/login and not already redirecting, removes the token and does window.location.assign(${import.meta.env.BASE_URL}auth/login). Guarded by a module redirecting flag so it fires once. There is no refresh-token flow — no /refresh call, no retry queue.

Error handling / toasts: none centrally. Every view does its own try/catch + message.error(e?.response?.data?.message ?? '<farsi fallback>'). Shared delete errors go through Modal.error in src/composables/useDeleteHandler.ts:43-46.

Token storage key is mode-dependent: tokenName = isBranch() ? 'branch_token' : 'token', userName = isBranch() ? 'branch_user' : 'user' (src/utils/index.ts:18-19).

2.2 Endpoint mapping layer — src/api/endpoints.ts

Legacy path → new REST path rewrite, applied inside the request interceptor. normalize() strips leading slashes (:20-22); mapEndpoint() checks EXACT first, then PATTERNS in order, else passes through; absolute http(s):// URLs bypass entirely (:251-270). isMapped() is a dev diagnostic (:273-276).

EXACT map (src/api/endpoints.ts:29-168)

Legacy path New path Line
file/create admin/files/create :33
admin-login admin/auth/login :36
users/show admin/auth/me :37
single-order admin/orders/list :41
single/order-sts/edit admin/orders/status :42
major/order/table admin/orders/wholesale/list :43
order-tamin admin/orders/tamin/list :44
order-sts/edit admin/orders/status :48
delete-order admin/orders/delete :49
get-custom-orders admin/pre-orders/list :52
customer admin/customers-omde/list :56
banker admin/customers-omde/banker/list :57
shopkeeper admin/customers-omde/shopkeeper/list :58
major/verify/table admin/customers-omde/non-verified/list :59
major/archive/table admin/customers-omde/archived/list :60
reagent admin/customers-omde/reagent/list :61
customer/toggle-active admin/customers-omde/toggle-active :62
customer/toggle-sts admin/customers-omde/toggle-sts :63
major/verify admin/customers-omde/verify :64
single-customer admin/customers-omde/single/list :67
single admin/customers-omde/single (sets kind=RETAIL) :75
customer/create admin/customers-omde/create :76
query/reagent admin/customers-omde/query/reagent :77
wallet/charge admin/customers-omde/wallet/charge :78
comment/table admin/comments/list :79
comment/sts admin/comments/sts :80
comment admin/comments :81
order admin/orders/create :84
order/type admin/orders/type-options :85
add/variant admin/products/variants/create :86
product/variant admin/products/by-ids :87
products admin/products/list :90
product/create admin/products/create :91
get/variants admin/products/variants/list :92
change/percent/count admin/products/change-percent/count :93
change/all admin/products/change-percent/apply :94
cash/table admin/documents/list :97
cash admin/documents :98
query/customer admin/documents/query/customer :99
wallet/table admin/documents/wallet/list :100
wallet/config admin/documents/wallet/config :101
wallet/birthday/config admin/documents/wallet/birthday/config :102
product/variant/promotion admin/promotions :105
major/promotion admin/promotions/list :106
autopromotion admin/promotions/auto :107
ignore admin/promotions/exclusions :108
users admin/users :113
users/list admin/users/list :114
const/__list__ut admin/users/types :115
const admin/users/types :116
roles/create admin/users/roles :117
branch/product admin/invoices :120
branch/product/table admin/invoices/list :121
branch/list admin/invoices/branch-list :122
branch admin/branches :125
branch/table admin/branches/list :126
workshop admin/workshops :127
user/list admin/branches/user-list :128
cities admin/misc/cities :131
sms admin/misc/sms :132
sms/table admin/misc/sms/list :133
notify admin/misc/notify :134
notify/table admin/misc/notify/list :135
notify/customer admin/misc/notify/customer :136
ddf admin/misc/newsletter/list :138
fashion/table admin/misc/newsletter/list :139
log/list/customer admin/misc/log/customers :140
dashboard/link admin/misc/dashboard-links :141
get-analytics admin/dashboard/omde :144
get-analytics-tak admin/dashboard/tak :145
dashboard/overview admin/dashboard/overview :146
brand/create admin/misc/brands :147
ciny/create admin/categories/collections :149
cities/list admin/cities/list :152
report/order-item admin/dashboard/report/order-item :155
report/product admin/dashboard/report/product :156
workshop/table admin/workshops/list :157
brand admin/products/brands :158
category admin/categories/list :165
category/ admin/categories :166
ciny admin/categories/collections/list :167

PATTERN map (src/api/endpoints.ts:173-248) — first match wins

Pattern Rewrite Line
cities/get-percent-aux/(.+) admin/misc/cities/percent-aux/$1 :185
cities/get-percent/(.+) admin/misc/cities/percent/$1 :186
cities/(.+) admin/misc/cities/$1 :187
const/(.+) admin/misc/const/$1 :190
brand/(.+) admin/misc/brands/$1 :193
log/ago/(.+) admin/misc/log/customer/$1 :196
notify/read/(.+) admin/misc/notify/read/$1 :199
notify/(.+) admin/misc/notify/$1 :200
single/(.+) admin/customers-omde/$1 :203
custom-order/(.+) admin/pre-orders/$1 :206
get-custom-order/(.+) admin/pre-orders/$1 :207
major/archive/(.+) admin/customers-omde/archive/$1 :209
major/dashboard/(.+) admin/customers-omde/dashboard/$1 :210
comment/(.+) admin/comments/$1 :213
customer/(.+) admin/customers-omde/$1 :215
delete/product/(.+) admin/products/delete/$1 :218
product/show/(.+) admin/products/$1 :219
product/update/(.+) admin/products/$1 :220
product/(.+)/variant admin/products/$1/variants :221
category/(.+) admin/categories/$1 :223
ciny/(.+) admin/categories/collections/$1 :224
cash/confirem/(.+) admin/documents/$1/confirm (client keeps legacy misspelling) :227
cash/(.+) admin/documents/$1 :228
product/variant/promotion/(.+) admin/promotions/$1 :231
major/promotion/(.+) admin/promotions/$1 :232
ignore/(.+) admin/promotions/exclusions/$1 :233
roles/(\d+)/(.+) admin/users/types/$1/roles/$2 :236
roles/(.+) admin/users/types/$1/roles :237
users/(.+) admin/users/$1 :238
branch/product/(.+) admin/invoices/$1 :243
branch/active/(.+) admin/branches/active/$1 :244
workshop/active/(.+) admin/workshops/active/$1 :245
branch/(.+) admin/branches/$1 :246
workshop/(.+) admin/workshops/$1 :247

2.3 Every API call site (method + URL as written; resolve via §2.2)

File:line Method URL (as written)
src/components/upload/ImageUploadInput.vue:97 POST file/create (FormData)
src/components/x-table/XTable.vue:288 POST props.model.url (per table model, §7/§6)
src/composables/useDeleteHandler.ts:34,36 POST / DELETE caller-supplied url
src/router/index.ts:87 GET users/show
src/stores/auth.ts:58 POST admin-login
src/stores/cart.ts:102 POST product/variant {products_id}
src/stores/users.ts:94 POST cities/list
src/views/Dashboard.vue:301 GET dashboard/overview
src/views/apps/category/Add.vue:215 PUT category/{id}
src/views/apps/category/Add.vue:217 POST category/
src/views/apps/category/Add.vue:235 GET admin/categories/tree
src/views/apps/category/Add.vue:243 GET admin/categories/{id}
src/views/apps/customer/ReagentCustomer.vue:154 PUT users/{id}
src/views/apps/customer/ReagentCustomer.vue:156 POST users
src/views/apps/customer/ReagentCustomer.vue:171 GET admin/users/{id}
src/views/apps/customer/bulkCustomer.vue:400 PUT customer/{id}
src/views/apps/customer/bulkCustomer.vue:402 POST customer/create
src/views/apps/customer/bulkCustomer.vue:422 GET query/reagent
src/views/apps/customer/bulkCustomer.vue:428 POST cities/list
src/views/apps/customer/bulkCustomer.vue:435 GET customer/{id}
src/views/apps/customer/singleCustomer.vue:97 PUT single/{id}
src/views/apps/customer/singleCustomer.vue:99 POST single
src/views/apps/customer/singleCustomer.vue:112 POST cities/list
src/views/apps/customer/singleCustomer.vue:120 GET customer/{id}
src/views/apps/product/AddProduct.vue:437 PUT product/update/{id}
src/views/apps/product/AddProduct.vue:439 POST product/create
src/views/apps/product/AddProduct.vue:453 GET brand
src/views/apps/product/AddProduct.vue:454 POST category
src/views/apps/product/AddProduct.vue:455 GET admin/products/collections
src/views/apps/product/AddProduct.vue:472 GET product/show/{id}
src/views/apps/product/ChangePercent.vue:87 POST change/percent/count
src/views/apps/product/ChangePercent.vue:104 POST change/all
src/views/apps/product/ChangePercent.vue:121 GET/POST brand, category
src/views/apps/product/ProductList.vue:131 GET admin/products/{id}
src/views/apps/product/ProductList.vue:146 POST delete/product/{id} (via delete handler)
src/views/branch/AddBranches.vue:139 PUT branch/{id}
src/views/branch/AddBranches.vue:141 POST branch
src/views/branch/AddBranches.vue:162 GET user/list
src/views/branch/AddBranches.vue:169 GET branch/{id}
src/views/branch/FacilityList.vue:104 GET {activePath}/{id} (branch/active | workshop/active)
src/views/branch/buyList.vue:69 GET branch/product/{id}
src/views/branch/dashboard.vue:75 GET get-analytics-tak
src/views/branch/dashboard.vue:76 POST products {page:1,limit:5}
src/views/branch/order/create.vue:213 POST order
src/views/branch/order/create.vue:233 POST single-customer {page:1,limit:500}
src/views/city/cityList.vue:92 GET cities/get-percent/{id}
src/views/city/cityList.vue:93 GET cities/get-percent-aux/{id}
src/views/city/cityList.vue:106 PUT cities/{id}
src/views/city/createCity.vue:85 POST cities
src/views/customer/Comments/Comments.vue:198 POST comment/sts {id, sts}
src/views/customer/Comments/Comments.vue:215 DELETE comment/{id}
src/views/customer/Comments/Comments.vue:225 POST comment
src/views/customer/CustomerDetail.vue:174 GET customer/{id}
src/views/customer/CustomerDetail.vue:175 GET major/dashboard/{id}
src/views/customer/CustomerDetail.vue:176 GET admin/customers-omde/{id}/orders
src/views/customer/CustomerDetail.vue:177 GET admin/customers-omde/{id}/documents
src/views/customer/CustomerDetail.vue:191 GET log/ago/{id}
src/views/customer/omde/OmdeCustomerList.vue:352 POST major/verify
src/views/customer/omde/OmdeCustomerList.vue:391 POST customer/toggle-active
src/views/customer/omde/OmdeCustomerList.vue:403 POST customer/toggle-sts
src/views/customer/omde/OmdeCustomerList.vue:413 GET major/archive/{id}
src/views/customer/reagentList/AgentDetail.vue:108 GET admin/customers-omde/reagent/{id}
src/views/customer/reagentList/ReagentList.vue:109 PUT users/{id} {is_active}
src/views/customer/single-customer/SingleCustomer.vue:99 POST wallet/charge
src/views/finical/addFinDoc.vue:305 PUT cash/{id}
src/views/finical/addFinDoc.vue:307 POST cash
src/views/finical/addFinDoc.vue:325 GET query/customer
src/views/finical/addFinDoc.vue:333 GET cash/{id}
src/views/finical/list.vue:111 POST cash/confirem/{id}
src/views/invoice/AddInvoice.vue:231 PUT branch/product/{id}
src/views/invoice/AddInvoice.vue:233 POST branch/product
src/views/invoice/AddInvoice.vue:253 GET branch/list
src/views/invoice/AddInvoice.vue:261 GET branch/product/{id}
src/views/invoice/ListInvoice.vue:113 GET branch/product/{id}
src/views/manage/Const.vue:217 GET brand
src/views/manage/Const.vue:224 POST brand/create
src/views/manage/Const.vue:241 PUT brand/{id}
src/views/manage/Const.vue:275 PUT const/__list__tags {val}
src/views/manage/Const.vue:290 PUT const/slider {val}
src/views/manage/Const.vue:299 POST ciny {page:1,limit:200}
src/views/manage/Const.vue:307 PUT ciny/{id}
src/views/manage/Const.vue:309 POST ciny/create
src/views/manage/Const.vue:333 DELETE ciny/{id}
src/views/manage/Const.vue:346-347 GET const/__list__tags, const/slider
src/views/manage/report.vue:24 GET dashboard/link
src/views/manage/wallet.vue:101 POST wallet/config
src/views/manage/wallet.vue:122 POST wallet/birthday/config
src/views/manage/wallet.vue:137-138 GET wallet/config, wallet/birthday/config
src/views/notify/create.vue:47 POST notify
src/views/notify/user.vue:73 GET notify/{id}
src/views/notify/user.vue:81 GET notify/read/{id}
src/views/offersIgnore/offersIgnore-add-edit.vue:166 POST ignore
src/views/offersIgnore/offersIgnore-add-edit.vue:184 DELETE ignore/{type}
src/views/offersIgnore/offersIgnore-add-edit.vue:198 GET ignore
src/views/order/List.vue:197 GET admin/orders/{id}
src/views/order/List.vue:203,215 POST single/order-sts/edit
src/views/order/MakeOrder.vue:168 POST workshop/table {page:1,limit:100}
src/views/order/MakeOrder.vue:177 GET admin/pre-orders/{id}
src/views/order/MakeOrder.vue:193 PUT custom-order/{id}
src/views/order/OmdeList.vue:171 GET admin/orders/{id}
src/views/order/OmdeList.vue:187,198 POST order-sts/edit
src/views/order/TaminList.vue:153 GET admin/orders/{id}
src/views/order/TaminList.vue:169,184 POST order-sts/edit
src/views/order/create.vue:327 POST add/variant
src/views/order/create.vue:373 POST order
src/views/order/create.vue:394 GET order/type
src/views/order/create.vue:395 POST single-customer {page:1,limit:500}
src/views/promotion/PromotionForm.vue:335 PUT product/variant/promotion/{id}
src/views/promotion/PromotionForm.vue:337 POST product/variant/promotion
src/views/promotion/PromotionForm.vue:356-358 POST/GET products, category, brand (limit 500)
src/views/promotion/PromotionForm.vue:369 GET query/customer
src/views/promotion/PromotionForm.vue:378 GET admin/promotions/{id}
src/views/promotion/autopromotion.vue:55 POST autopromotion
src/views/promotion/autopromotion.vue:69 GET autopromotion
src/views/promotion/promotion_list.vue:129 DELETE major/promotion/{id}
src/views/reg.vue:92 POST customer/create
src/views/report/order_item.vue:76 GET report/order-item {params: filters}
src/views/report/product.vue:60 GET report/product {params: filters}
src/views/sms/create.vue:94 POST sms
src/views/statics/index.vue:71 GET log/ago/{customer}
src/views/statics/index.vue:82 GET log/list/customer
src/views/user/ListUser.vue:90 GET const/__list__ut
src/views/user/Permission.vue:118 GET const/__list__ut
src/views/user/Permission.vue:136 GET roles/{id}
src/views/user/Permission.vue:160 POST const (create user type)
src/views/user/Permission.vue:167 POST roles/create {ut, roles}
src/views/user/UserCreate.vue:220 PUT users/{id}
src/views/user/UserCreate.vue:222 POST users
src/views/user/UserCreate.vue:241-242 GET const/__list__ut, admin/users/role-options
src/views/user/UserCreate.vue:252 GET admin/users/{id}
src/views/user/myData.vue:37 GET admin/users/me
src/views/workshop/AddWorkshop.vue:151 PUT workshop/{id}
src/views/workshop/AddWorkshop.vue:153 POST workshop
src/views/workshop/AddWorkshop.vue:172 GET workshop/{id}

3. Router — every route

3.1 Structure — src/router/index.ts

createWebHistory(import.meta.env.BASE_URL) (:10); scrollBehavior returns {left:0,top:0} (:12-14). Two top-level shells (:17-53):

  • path: '' → layouts/main/Main.vue, children = isBranch() ? branchRoute : adminRoute (:18-21)
  • path: '' → layouts/full-page/FullPage.vue, children = fullPage*Route + 4 error pages (:23-53)
  • catch-all /:pathMatch(.*)* → redirect /pages/error-404 (:55-58)

3.2 Admin routes (src/router/admin.ts) — main layout (layouts/main/Main.vue)

meta.layout does not exist as a key; layout is determined by the parent shell. Meta keys actually used: rule, role, parent, no_scroll, pageTitle, breadcrumb.

path name component file meta (rule / role / parent / no_scroll / pageTitle) line
/ dashboard — (redirect /dashboard/ecommerce) — :27-30
/dashboard/analytics dashboard-analytics views/Dashboard.vue editor / dashboard.view :36-43
/my-data my-data views/user/myData.vue editor / (role commented out) :45-52
/dashboard/ecommerce dashboard-ecommerce views/Dashboard.vue admin / dashboard.view :54-61
/config/manage/const — views/manage/Const.vue editor / config.statics / manage / true / مدیریت ثابت ها :63-82
/config/manage/wallet — views/manage/wallet.vue editor / config.wallet / manage / true / مدیریت کیف پول :84-103
/config/manage/report — views/manage/report.vue editor / config.report / manage / true / گزارش :105-124
/apps/product/ product — (redirect /apps/product/list) — :127-130
/product/change/percent — views/apps/product/ChangePercent.vue editor / product.percent / product / true :132-140
/apps/product/add — views/apps/product/AddProduct.vue editor / product.create / product / true :142-150
/apps/product/edit/:id/:variant_id? product-edit views/apps/product/EditProduct.vue editor / product.list.edit / product / true :152-161
/apps/product/list product-list views/apps/product/ProductList.vue editor / product.list / product / true :163-172
/category category — (redirect /category/list) — :175-178
/category/add/:id? category-add views/apps/category/Add.vue editor / collection.create / category / true :180-189
/category/edit/:id category-edit views/apps/category/Edit.vue editor / collection.list.edit / category / true :191-200
/category/list category-list views/apps/category/List.vue editor / collection.list / category / true :202-211
/add/findoc — views/finical/addFinDoc.vue editor / document.create :213-221
/edit/findoc/:id — views/finical/addFinDoc.vue editor / document.list.edit :222-230
/order order — (redirect /order/list) — :232-236
/order/list — views/order/List.vue editor / order.list / order / true :238-246
/order/omde-list — views/order/OmdeList.vue editor / order.omde / order / true :248-256
/order/make — views/order/MakeOrder.vue editor / order.makeOrder / order / true :258-266
/order/create — views/order/create.vue editor / order.create / order / true :268-276
/order/tamin-list — views/order/TaminList.vue editor / order.tamin / order / true :278-286
/major/promotion/list — views/promotion/promotion_list.vue editor / offers.list / true :288-296
/major/promotion/add — views/promotion/promotion_add_edit.vue editor / offers.create / true :298-306
/major/promotion/edit/:id — views/promotion/promotion_add_edit.vue editor / offers.list.edit / true :308-316
/major/promotion/tak/add — views/promotion/promotion_tak_add_edit.vue editor / offers.create.tak / true :318-326
/major/promotion/tak/edit/:id — views/promotion/promotion_tak_add_edit.vue editor / offers.list.tak.edit / true :328-336
/major/promotion/autopromotion — views/promotion/autopromotion.vue editor / offers.autopromotion / true :338-345
/major/offersIgnore — views/offersIgnore/offersIgnore-add-edit.vue editor / offersIgnore :347-354
/comments comments views/customer/Comments/Comments.vue editor / comment / true / دیدگاه ها :356-377
/sms/create create_sms views/sms/create.vue editor / sms.create / true / ارسال پیامک :379-398
/notify notify views/notify/user.vue editor / notify.me / true / لیست نوتیفیکیشن های ذریافتی :400-419
/notify/create create_notify views/notify/create.vue editor / notify.create / true / ارسال نوتیفیکیشن :421-440
/wallet wallet views/wallet/wallet.vue editor / wallet / true / لیست کیف پول های شارژ شده :442-461
/reagent/edit/:id reagent-edit views/apps/customer/EditReagent.vue editor / persons.list.sellers.edit / product / true :463-473
/reagent/add reagent-add views/apps/customer/EditReagent.vue editor / persons.create / product / true :475-485
/reagent/view/:id reagent-view views/customer/reagentList/AgentDetail.vue editor / persons.list.sellers.view / product / true :487-496
/bulkCustomer/edit/:id bulkCustomer-edit views/apps/customer/EditBulkCustomer.vue editor / persons.list.client.omde.edit / product / true :498-507
/bulkCustomer/view/:id bulkCustomer-view views/customer/omde/ViewOmdeCustomer.vue editor / persons.list.client.omde.view / product / true :509-518
/singleCustomer/edit/:id singleCustomer-edit views/apps/customer/EditSingleCustomer.vue editor / persons.list.client.single.edit / product / true :520-529
/singleCustomer/view/:id singleCustomer-view views/customer/single-customer/ViewSingleCustomer.vue editor / persons.list.client.single.view / product / true :531-540
/customer/omde-list omde-list-customer views/customer/omde/OmdeList.vue editor / persons.list.client.omde / مشتریان عمده :542-560
/customer/omde-non-verified-list omde-non-verified-list views/customer/omde/OmdeNonVerifiedList.vue editor / persons.list.client.omde / مشتریان در انتظار :562-580
/customer/banker-list banker-list views/customer/omde/BankerList.vue editor / persons.list.client.omde / لیست بنکداران :582-600
/customer/shopkeeper-list shopkeeper-list views/customer/omde/ShopkeeperList.vue editor / persons.list.client.omde / لیست مغازه داران حضوری :602-620
/customer/omde-archived-list omde-archived-list views/customer/omde/OmdeArchivedList.vue editor / persons.list.client.omde / مشتریان بایگانی :622-640
/customer/reagent-list raegentList views/customer/reagentList/ReagentList.vue editor / persons.list.sellers / کارشناسان فروش :642-660
/customer/add add-customer views/apps/customer/AddCustomer.vue editor / persons.create / افزودن مشتری :662-681
/customer/list list-customer views/customer/single-customer/SingleCustomer.vue editor / persons.list.client.single / لیست مشتریان :683-698
/report/order_item report_order_item views/report/order_item.vue editor / report.order.item :700-712
/report/product report_product views/report/product.vue editor / report.product :714-721
/finical/list finical_list views/finical/list.vue editor / document.list :723-736
/user/list — views/user/ListUser.vue editor / user.list / user / true / لیست مدیران :738-757
/user/create — views/user/UserCreate.vue editor / user.create / user / true / افزودن کاربر :759-778
/user/edit/:id — views/user/UserEdit.vue editor / user.list.edit / user / true / ویرایش نوع کاربر :780-799
/permission — views/user/Permission.vue editor / user.permission / permission / true / دسترسی ها :802-821
/city/create — views/city/createCity.vue editor / cities.create / city / true / افزودن شهر :823-842
/city/list — views/city/cityList.vue editor / cities.list / city / true / لیست شهر ها :844-863
/statics statics views/statics/index.vue editor / statics / true / آمار :866-885
/newsletters newsletters views/newsletters/newsletters.vue editor / role commented out / true / خبرنامه :887-906
/branch/add add-branch views/branch/AddBranches.vue editor / branch.create / افزودن شعب :908-927
/branch/edit/:id edit-branch views/branch/EditBranches.vue editor / branch.create / ویرایش شعب :929-937
/branch/list list-branch views/branch/ListBranches.vue editor / branch.list / لیست شعب :939-958
/workshop/add add-workshop views/workshop/AddWorkshop.vue editor / workshop.create / افزودن کارگاه :961-980
/workshop/edit/:id edit-workshop views/workshop/EditWorkshop.vue editor / workshop.create / ویرایش کارگاه :982-990
/workshop/list list-workshop views/workshop/ListWorkshop.vue editor / workshop.list / لیست کارگاه ها :992-1011
/invoice/add add-invoice views/invoice/AddInvoice.vue editor / invoice.create / افزودن فاکتور :1014-1033
/invoice/edit/:id edit-invoice views/invoice/EditInvoice.vue editor / invoice.create / ویرایش فاکتورها :1035-1043
/invoice/list list-invoice views/invoice/ListInvoice.vue editor / invoice.list / لیست فاکتورها :1045-1064

Admin full-page (src/router/admin.ts:1068-1085, shell layouts/full-page/FullPage.vue):

path name component meta line
/auth/login page-login views/auth/Login.vue rule editor :1070-1076
/reg reg views/reg.vue rule editor :1078-1084

Shared full-page pages (src/router/index.ts:28-51):

path name component meta line
/pages/error-404 page-error-404 views/pages/Error404.vue rule editor :28-33
/pages/error-500 page-error-500 views/pages/Error500.vue rule editor :34-39
/pages/not-authorized page-not-authorized views/pages/NotAuthorized.vue rule editor :40-45
/pages/maintenance page-maintenance views/pages/Maintenance.vue rule editor :46-51

3.3 Branch routes (src/router/branch.ts, active only when MODE === 'branch')

path name component file meta line
/ main — (redirect /dashboard) — :15-18
/dashboard dashboard views/branch/dashboard.vue admin / dashboard.view :20-29
/products product-list views/apps/product/ProductList.vue editor / product.list / product / true :31-40
/order order — (redirect /order/list) — :42-45
/order/list — views/order/List.vue editor / order.list / order / true :47-55
/buy/order/list — views/branch/buyList.vue editor / order.list / order / true :57-65
/buy/order/list (duplicate path) — views/order/List.vue editor / buy.order.list / true :67-74
/order/create — views/branch/order/create.vue editor / order.create / order / true :76-84
/singleCustomer/edit/:id singleCustomer-edit views/apps/customer/EditSingleCustomer.vue editor / persons.list.client.single.edit / product / true :86-95
/singleCustomer/view/:id singleCustomer-view views/customer/single-customer/ViewSingleCustomer.vue editor / persons.list.client.single.view / product / true :97-106
/singleCustomer/edit/:id (duplicate) singleCustomer-edit views/apps/customer/EditSingleCustomer.vue editor / persons.list.client.single.edit / product / true :108-117
/customer/add add-customer views/branch/addCustomer.vue editor / persons.create :119-126
/customer/list list-customer views/customer/single-customer/SingleCustomer.vue editor / persons.list.client.single :128-135
/report/order_item report_order_item views/report/order_item.vue editor / report.order.item :137-149
/report/product report_product views/report/product.vue editor / report.product :151-158
/finical/list (commented out) finical_list views/finical/list.vue editor / document.list :159-172 (inactive)

Branch full-page (src/router/branch.ts:175-184): /auth/login → page-login → views/branch/login.vue (the views/auth/Login.vue import is commented out at :178), meta rule editor.

Counts: admin main-layout routes 73 (incl. 4 redirect-only), admin full-page 2, shared error pages 4, catch-all 1 → 80 admin-mode route records. Branch mode: 15 active main-layout records + 1 full-page.

3.4 Guards

router.afterEach (src/router/index.ts:63-68) hides #loading-bg.

getPermission() (:82-100): if usersStore.roles already set, returns it; else GET users/show → users.getRoles(data.permission), strips permission and ut, persists api_token under tokenName and the user blob under userName. Errors are swallowed with console.log.

router.beforeEach (:111-129):

  1. noneAuthUrl = ['/auth/login','/auth/forgot-password','/auth/register','/reg'] (:71) → allow.
  2. No token in localStorage[tokenName] → redirect {path:'/auth/login'} (:128).
  3. With a token: load roles; if to.meta.role is set and !roles.includes(role) → if the route name is dashboard-ecommerce or dashboard-analytics, redirect to {name:'my-data'}, else {name:'page-not-authorized'} (:119-124).

meta.rule (editor/admin) is inert — never read by any guard (legacy ACL plugin was not ported).


4. Menu / navigation

Defined in src/layouts/navMenuItems.ts (641 lines). Exports adminMenu (:44), branchMenu (:551), getMenu() selector (:637-639, isBranch() ? branchMenu : adminMenu). Node interface NavMenuNode { url, name, slug, icon, i18n, tag, tagColor, permissionId, header, items, submenu } (:30-42).

Rendered by src/layouts/components/VerticalNavMenu.vue (uses a-menu-item-group for header nodes, :39) and recursively by src/layouts/components/NavMenuNode.vue (a-sub-menu when node.submenu, :8). Icons resolved from legacy Feather names via src/components/icons.ts resolveIcon().

Visibility rule (VerticalNavMenu.vue:81-82, NavMenuNode.vue:36-37): a node with no permissionId is always visible; otherwise it requires users.hasPermission(permissionId).

4.1 Admin menu items

Label (i18n) url icon permissionId line
پیشخوان /dashboard/ecommerce HomeIcon dashboard.view :49-55
[header] کاربری (Apps) — PackageIcon — :58-60
اشخاص null UserIcon persons :63-67
├ اضافه کردن شخص /customer/add — persons.create :73-77
├ لیست کارشناسان فروش /customer/reagent-list — persons.list.sellers :80-84
├ لیست مشتریان عمده /customer/omde-list — persons.list.client.omde :87-91
├ لیست مشتریان در انتظار /customer/omde-non-verified-list — persons.list.client.omde :94-98
├ لیست مشتریان بایگانی /customer/omde-archived-list — persons.list.client.omde :101-105
├ لیست مشتریان تک /customer/list — persons.list.client.single :108-112
├ لیست بنکداران /customer/banker-list — persons.list.client.omde (was commented out in legacy; filled in) :115-124
└ لیست مغازه داران حضوری /customer/shopkeeper-list — persons.list.client.omde (same fix) :130-136
شعب و کارگاه null GridIcon — :144-148
├ شعب null GridIcon branch :150-154
│ └ لیست شعب /branch/list — branch.list :159-163
├ کارگاه null GridIcon workshop :168-172
│ └ لیست کارگاه ها /workshop/list — workshop.list :177-181
محصولات null ShoppingBagIcon product :189-193
├ افزودن محصول /apps/product/add — product.create :196-200
├ محصولات /apps/product/list — product.list :203-207
└ تغییر درصد محصولات /product/change/percent — product.percent :210-214
سفارش ها null BoxIcon order :219-223
├ ثبت سفارش تک /order/create — order.create :226-230
├ لیست سفارش های تک /order/list — order.list :233-237
├ سفارش های ساختنی /order/make — order.makeOrder :240-244
├ لیست سفارش های عمده /order/omde-list — order.omde :247-251
└ لیست تامین /order/tamin-list — order.tamin :254-258
مدیران null UserIcon user :263-267
├ دسترسی ها /permission — user.permission :270-274
└ لیست مدیران /user/list — user.list :279-283
تخفیفات null GiftIcon offers :288-292
├ تخفیف اتوماتیک /major/promotion/autopromotion — offers.autopromotion :298-302
├ لیست تخفیفات /major/promotion/list — offers.list :307-311
└ استثناعات تخفیف /major/offersIgnore SlashIcon offersIgnore :315-320
گزارشات و اسناد مالی null FileTextIcon — :329-333
├ اسناد مالی null DollarSignIcon document :335-339
│ └ لیست اسناد /finical/list — document.list :342-346
├ فاکتور انتقال null BookIcon invoice :354-358
│ └ لیست فاکتورها /invoice/list — invoice.list :363-367
├ گزارشات null FileTextIcon report :372-376
│ ├ گزارش تجمیعی سفارش و اسناد /report/order_item — report.order.item :379-383
│ └ گزارش محصولات /report/product — report.product :386-390
پیامک null MailIcon sms :397-401
└ ارسال پیامک /sms/create — sms.create :404-408
آمار /statics BarChartIcon statics :413-417
خبرنامه /newsletters FileTextIcon newsletters (corrected from legacy's dead newsletters.newsletters) :420-429
[header] مدیریت (UI) — LayersIcon — :434-436
پیکر بندی null SettingsIcon config :439-443
├ مدیریت ثابت ها /config/manage/const — config.statics :446-450
├ گزارش /config/manage/report — config.report :453-457
└ مدیریت کیف پول /config/manage/wallet — config.wallet :460-464
دسته بندی ها null ListIcon collection :469-473
└ لیست دسته بندی ها /category/list — collection.list :478-482
کیف پول null CreditCardIcon wallet :487-491
└ لیست کیف پول های شارژ شده /wallet — wallet.list :494-498
شهر ها null PercentIcon cities :503-508
└ لیست شهر ها /city/list — cities.list :513-517
دیدگاه ها /comments MessageSquareIcon comment :522-527
نوتیفیکیشن null BellIcon notify :530-534
└ ارسال نوتیفیکیشن /notify/create — notify.create :537-541

4.2 Branch menu (src/layouts/navMenuItems.ts:551-634)

Label url icon permissionId line
پیشخوان /dashboard HomeIcon dashboard :552-559
اشخاص null UserIcon persons :560-566
├ اضافه کردن مشتری /customer/add UserPlusIcon persons.create :567-573
└ لیست مشتریان /customer/list UsersIcon persons.list.client.single :575-581
محصولات /products ShoppingBagIcon product.list :584-591
فاکتور خرید /buy/order/list ShoppingBagIcon product.list :592-599
سفارش ها null BoxIcon order :609-614
├ ثبت سفارش /order/create InboxIcon order.create :616-622
└ لیست سفارش /order/list ListIcon order.list :624-630

Note: /wallet menu item is gated on wallet.list while the route /wallet is gated on wallet (src/router/admin.ts:458) — a rid mismatch preserved from legacy.


5. Permissions

5.1 Mechanism

  • Store src/stores/users.ts. State: roles: string[] | undefined (undefined = "not fetched"), cityFilters, userInfo (:249-256).
  • Parent expansion checkParent() (:220-229) using getParents() (:196-204): granting persons.list.client.omde implicitly grants persons.list.client, persons.list, persons. It only walks up, never down — a user with wallet still fails a wallet.list check (documented legacy behaviour, :216-218).
  • Getter hasPermission(permissionId) (:263-269): returns roles.includes(id) or undefined when roles unloaded (falsy in v-if). This is the port of the legacy global Vue.prototype.$hasPermission.
  • Action getRoles(roles) commits checkParent(roles) (:293-295); deleteRoles() sets undefined (:296-298).
  • Sources of roles: login response data.roles.map(x => x.rid) (src/stores/auth.ts:152), or GET users/show → data.permission (src/router/index.ts:88).
  • Route guard: to.meta.role checked in router.beforeEach (src/router/index.ts:119-124).
  • Menu guard: node.permissionId (VerticalNavMenu.vue:81-82, NavMenuNode.vue:36-37).
  • Template guard: each view defines const hasPermission = (rid: string) => Boolean(users.hasPermission(rid)) and uses v-if. E.g. src/views/apps/product/AddProduct.vue:286, src/views/apps/product/ProductList.vue:113.
  • Table-model guard: some columns are added only if users.roles.includes(...) — src/views/order/orderListModel.ts:24, src/views/order/OmdeListModel.ts:32, src/views/order/TaminListModel.ts:21, src/views/order/MakeOrderModel.ts:29, src/views/customer/Comments/CommentListModel.ts:21.
  • Component guard: src/components/common/ListPageHeader.vue:146 renders the add button only when !rid || hasPermission(rid).
  • No directives (no v-permission) and no composable — only the store getter.

5.2 Permission vocabulary — src/views/user/permissionConst.ts (treeData, :23)

179 key entries, 174 unique (5 duplicated: persons.list.client.single.view at :131/:139, product.list.view at :191/:199, order.list.view at :252/:275, order.tamin.view at :386/:409, cities.list.view at :640/:648).

key label line
dashboard.view پیشخوان :29
persons اشخاص :33
persons.create اضافه کردن شخص :37
persons.create.seller افزودن کارشناس فروش :41
persons.create.client افزودن مشتری عمده :45
persons.create.single افزودن مشتری تک :49
persons.list.sellers لیست کارشناسان فروش :55
persons.list.sellers.edit ویرایش اطلاعات کارشناس فروش :59
persons.list.sellers.view.orders مشاهده سفارشات کارشناس فروش :63
persons.list.sellers.view.documents مشاهده اسناد مالی کارشناس فروش :67
persons.list.sellers.unable.order توقف سفارش کارشناس فروش :71
persons.list.sellers.able.order فعال سازی سفارش کارشناس فروش :75
persons.list.sellers.able فعال سازی حساب کاربری کارشناس فروش :79
persons.list.sellers.unable غیر فعال سازی حساب کاربری کارشناس فروش :83
persons.list.client.omde لیست مشتریان عمده :89
persons.list.client.omde.view مشاهده اطلاعات مشتریان عمده :93
persons.list.client.omde.edit ویرایش اطلاعات مشتری عمده :97
persons.list.client.omde.view.orders مشاهده سفارشات مشتری عمده :101
persons.list.client.omde.view.documents مشاهده اسناد مالی مشتری عمده :105
persons.list.client.omde.unable.order توقف سفارش مشتری عمده :109
persons.list.client.omde.able.order فعال سازی سفارش مشتری عمده :113
persons.list.client.omde.able فعال سازی مشتری عمده :117
persons.list.client.omde.unable غیر فعال سازی مشتری عمده :121
persons.list.client.single لیست مشتریان تک :127
persons.list.client.single.view مشاهده اطلاعات مشتریان تک :131, :139
persons.list.client.single.edit ویرایش اطلاعات مشتری تک :135
branch شعب :147
branch.create افزودن شعب :152
branch.list لیست شعب :156
workshop کارگاه :162
workshop.create افزودن کارگاه :167
workshop.list لیست کارگاه ها :171
product محصولات :177
product.create افزودن محصول :182
product.list لیست محصولات :186
product.list.view مشاهده لیست محصولات / مشاهده اطلاعات جامع محصول :191, :199
product.list.edit ویرایش اطلاعات محصول :195
product.list.variants تنوع های محصول :203
product.list.variants.view مشاهده تنوع های محصول :208
product.list.variants.edit ویرایش تنوع محصول :212
product.list.variants.remove حذف تنوع محصول :216
product.list.variants.add افزودن تنوع محصول :220
product.list.remove حذف محصول :226
product.list.group.edit تغییر دسته ای :230
product.percent تغییر درصد محصولات :236
order سفارشات :242
order.list لیست سفارشات :247
order.list.view مشاهده لیست سفارشات / مشاهده سفارش :252, :275
order.list.sts وضعیت سفارش :256
order.list.sts.view مشاهده وضعیت سفارش :261
order.list.sts.remove حذف وضعیت :265
order.list.sts.submit ثبت وضعیت :269
order.list.view.edit ویرایش سفارش :279
order.list.pdf دریافت pdf :283
order.omde سفارشات عمده :289
order.omde.state وضعیت سفارش :294
order.omde.state.view مشاهده وضعیت سفارش :299
order.omde.state.submit ثبت وضعیت سفارش :303
order.omde.state.remove حذف وضعیت سفارش :307
order.omde.view مشاهده سفارش :313
order.omde.view.edit ویرایش سفارش :317
order.omde.view.remove حذف آیتم از سفارش :321
order.omde.remove حذف سفارش :325
order.omde.get دریافت pdf سفارش :329
order.makeOrder سفارشات ساختنی :335
order.makeOrder.state وضعیت سفارش :340
order.makeOrder.state.view مشاهده وضعیت سفارش :345
order.makeOrder.state.submit ثبت وضعیت سفارش :349
order.makeOrder.state.remove حذف وضعیت سفارش :353
order.makeOrder.view مشاهده سفارش :359
order.makeOrder.view.edit ویرایش سفارش :363
order.makeOrder.view.remove حذف آیتم از سفارش :367
order.makeOrder.remove حذف سفارش :371
order.makeOrder.get دریافت pdf سفارش :375
order.tamin لیست تامین :381
order.tamin.view مشاهده لیست تامین / مشاهده سفارش :386, :409
order.tamin.state وضعیت سفارش :390
order.tamin.state.view مشاهده وضعیت سفارش :395
order.tamin.state.submit ثبت وضعیت سفارش :399
order.tamin.state.remove حذف وضعیت سفارش :403
order.tamin.view.edit ویرایش سفارش :413
order.create ثبت سفارش تک :419
user مدیران :425
user.create افزودن مدیر :430
user.list لیست مدیران :434
user.list.view مشاهده لیست مدیران :439
user.list.edit ویرایش مدیر :443
user.list.remove حذف مدیر :447
user.list.create افزودن مدیر :451
user.permission دسترسی ها :457
user.permission.edit ویرایش دسترسی :462
user.permission.create افزودن دسترسی :466
collection دسته بندی ها :474
collection.create افزودن دسته بندی :479
collection.list لیست دسته بندی ها :483
collection.list.view مشاهده لیست دسته بندی ها :488
collection.list.edit ویرایش دسته :492
collection.list.remove حذف دسته :496
document اسناد مالی :504
document.list لیست اسناد مالی :509
document.list.view مشاهده لیست اسناد مالی :514
document.list.edit ویرایش سند مالی :518
document.list.remove حذف سند مالی :522
document.list.deny رد کردن سند مالی :526
document.list.confirm تأیید کردن سند مالی :530
document.list.get دریافت pdf سند مالی :534
document.create وارد کردن سند مالی مشتری :540
offersIgnore استثناعات تخفیف :546
offers تخفیفات :550
offers.list لیست تخفیفات :555
offers.list.view مشاهده لیست تخفیفات :560
offers.list.edit ویرایش تخفیف عمده :564
offers.list.tak.edit ویرایش تخفیف تک :568
offers.list.remove حذف تخفیف عمده :572
offers.list.tak.remove حذف تخفیف تک :576
offers.create تعریف تخفیف عمده :582
offers.create.tak تعریف تخفیف تک :586
offers.autopromotion تخفیف اتوماتیک :590
invoice فاکتور انتقال :596
invoice.create افزودن فاکتور :601
invoice.list لیست فاکتورها :605
report گزارشات :611
report.order.item گزارش تجمیعی سفارشات و اسناد مالی :616
report.product گزارش محصولات :620
cities شهرها :626
cities.create افزودن شهر :631
cities.list لیست شهرها :635
cities.list.view مشاهده لیست شهرها / مشاهده شهر :640, :648
cities.list.remove حذف شهر :644
cities.list.view.edit ویرایش شهر :652
comment دیدگاه ها :660
comment.view.all مشاهده لیست دیدگاه ها :664
comment.state تغییر وضعیت دیدگاه :668
comment.view مشاهده دیدگاه :672
comment.view.edit ویرایش دیدگاه :676
comment.remove حذف دیدگاه :680
sms پیامک :686
sms.create ارسال پیامک :691
sms.create.single ارسال برای یک شماره تلفن :696
sms.create.all ارسال برای همه کاربران :700
sms.create.reagent ارسال برای کارشناسان فروش :704
sms.create.omde ارسال برای مشتریان عمده :708
sms.create.multiple ارسال برای چند شماره تلفن :712
sms.list لیست پیامک های ارسال شده :718
wallet کیف پول :725
wallet.list لیست کیف پول های شارژ شده :730
wallet.charge شارژ کیف پول :734
wallet.percent تغییر درصد شارژ کیف پول :738
notify نوتیفیکیشن :744
notify.create ارسال نوتیفیکیشن :749
notify.create.multiple.reagent ارسال برای کارشناسان فروش :754
notify.create.single.customer ارسال برای یک مشتری :758
notify.create.multiple.omde ارسال برای مشتریان عمده :762
notify.create.multiple.customer ارسال برای همه مشتریان :766
notify.create.single.admin ارسال برای یک مدیر :770
notify.create.multiple.admin ارسال برای همه مدیران :774
notify.me لیست نوتیفیکیشن های دریافتی :780
config پیکربندی :786
config.statics مدیریت ثابت ها :790
config.statics.brand برند ها :795
config.statics.brand.create افزودن برند :800
config.statics.brand.edit ویرایش برند :804
config.statics.brand.remove حذف برند :808
config.statics.tags برچسب ها :814
config.statics.tags.create افزودن برچسب :819
config.statics.tags.edit ویرایش برچسب :823
config.statics.tags.remove حذف برچسب :827
config.wallet مدیریت کیف پول :835
config.report گزارش :839
statics آمار :845
newsletters.newsletters خبرنامه :849
custom سفارشی :853
custom.config.statics.slider اسلایدر موبایل :857
custom.config.statics.tags.banner.ids برچسب بنر اول سایت :861

Known gaps (src/views/user/permissionConst.ts:6-15): 4 rids are enforced but absent from the tree and cannot be granted from the UI — buy.order.list, newsletters, persons.create.customer.tak, persons.list.sellers.view. ~36 tree keys are never checked anywhere.

5.3 rids checked via hasPermission() in templates (73 distinct)

cities.list.remove, cities.list.view.edit, collection.list.edit, collection.list.remove, comment.remove, comment.state, comment.view, comment.view.edit, config.statics, config.statics.brand.create, config.statics.brand.edit, config.statics.brand.remove, config.statics.tags.create, config.statics.tags.remove, config.wallet, custom.config.statics.slider, document.list.confirm, document.list.deny, document.list.edit, document.list.remove, offers.list.edit, offers.list.remove, offers.list.tak.edit, offers.list.tak.remove, order.create, order.list.pdf, order.list.sts, order.list.sts.remove, order.list.sts.submit, order.list.view, order.list.view.edit, order.makeOrder.state, order.makeOrder.view, order.makeOrder.view.edit, order.omde.remove, order.omde.state, order.omde.state.remove, order.omde.state.submit, order.omde.view, order.tamin.state, order.tamin.state.remove, order.tamin.state.submit, order.tamin.view, persons.create, persons.create.client, persons.create.seller, persons.list.client.omde.able, persons.list.client.omde.able.order, persons.list.client.omde.edit, persons.list.client.omde.view, persons.list.client.omde.view.documents, persons.list.client.omde.view.orders, persons.list.sellers.able, persons.list.sellers.edit, persons.list.sellers.unable, persons.list.sellers.view, product.list.edit, product.list.remove, product.list.variants, product.list.variants.add, product.list.variants.edit, product.list.variants.remove, product.list.view, sms.create.all, sms.create.multiple, sms.create.omde, sms.create.reagent, sms.create.single, sms.list, user.list.edit, user.list.remove, user.permission, user.permission.create, user.permission.edit, wallet.charge, wallet.percent.


6. Views — full recursive inventory of src/views

6.1 Every file (110 files; 91 .vue, 19 .ts)

file LOC purpose
src/views/Dashboard.vue 327 PROTECTED. Merged wholesale+retail dashboard; see §10a.
src/views/apps/category/Add.vue 261 Category create/edit form (shared by Edit.vue).
src/views/apps/category/CategoryTbl.ts 21 XTbl model for the category grid (admin/categories/list).
src/views/apps/category/Edit.vue 13 Thin wrapper over Add.vue for edit mode.
src/views/apps/category/List.vue 87 Category grid + row actions + delete.
src/views/apps/customer/AddCustomer.vue 47 Tabbed shell picking retail/wholesale/reagent customer forms.
src/views/apps/customer/EditBulkCustomer.vue 13 Wrapper → bulkCustomer.vue in edit mode.
src/views/apps/customer/EditReagent.vue 12 Wrapper → ReagentCustomer.vue.
src/views/apps/customer/EditSingleCustomer.vue 13 Wrapper → singleCustomer.vue in edit mode.
src/views/apps/customer/ReagentCustomer.vue 205 Sales-agent (کارشناس فروش) create/edit form.
src/views/apps/customer/bulkCustomer.vue 472 Wholesale customer create/edit form.
src/views/apps/customer/singleCustomer.vue 136 Retail customer create/edit form.
src/views/apps/product/AddProduct.vue 579 PROTECTED (variants). Product create/edit; see §10b.
src/views/apps/product/ChangePercent.vue 136 Bulk percentage-wage change across brand/category.
src/views/apps/product/EditProduct.vue 17 Wrapper → AddProduct.vue with route :id.
src/views/apps/product/ProductList.vue 168 Product grid, row actions, detail modal, delete.
src/views/apps/product/productListModel.ts 31 XTbl model for admin/products/list.
src/views/auth/Login.vue 162 Admin login (mobile + password).
src/views/branch/AddBranches.vue 184 Branch create/edit form.
src/views/branch/EditBranches.vue 10 Wrapper → AddBranches.vue.
src/views/branch/FacilityList.vue 112 Shared branch/workshop list implementation with active toggle.
src/views/branch/ListBranch.ts 20 XTbl model admin/branches/list.
src/views/branch/ListBranches.vue 25 Wrapper → FacilityList.vue for branches.
src/views/branch/addCustomer.vue 14 Branch-mode customer add wrapper.
src/views/branch/buyList.vue 80 Branch "فاکتور خرید" allocations list.
src/views/branch/dashboard.vue 93 Branch-mode dashboard (retail analytics + top 5 products).
src/views/branch/login.vue 105 Branch-mode login.
src/views/branch/order/create.vue 253 Branch order builder.
src/views/branch/orderListModel.ts 20 XTbl model admin/invoices/list (branch buy list).
src/views/city/CityListModel.ts 18 XTbl model admin/misc/cities/list.
src/views/city/cityList.vue 127 City list with inline percent editing + delete.
src/views/city/createCity.vue 102 City create form.
src/views/customer/Comments/CommentListModel.ts 30 XTbl model admin/comments/list.
src/views/customer/Comments/Comments.vue 274 Product-comment moderation (approve/reject/reply/delete).
src/views/customer/CustomerDetail.vue 211 Shared customer detail (omde + single) with orders/docs/log tabs.
src/views/customer/omde/BankerList.vue 13 Wrapper → OmdeCustomerList source banker.
src/views/customer/omde/OmdeArchivedList.vue 13 Wrapper → source archived.
src/views/customer/omde/OmdeCustomerList.vue 448 Shared implementation of the 5 wholesale-customer lists.
src/views/customer/omde/OmdeList.vue 18 Wrapper → source omde.
src/views/customer/omde/OmdeNonVerifiedList.vue 13 Wrapper → source nonVerified.
src/views/customer/omde/ShopkeeperList.vue 13 Wrapper → source shopkeeper.
src/views/customer/omde/ViewOmdeCustomer.vue 18 Wrapper → CustomerDetail.vue (omde mode).
src/views/customer/omde/omdeListModel.ts 71 Factory building all 5 omde list models.
src/views/customer/reagentList/AgentDetail.vue 127 Sales-agent detail view.
src/views/customer/reagentList/ReagentList.vue 128 Sales-agent list + active toggle.
src/views/customer/reagentList/ReagentListModel.ts 48 XTbl model admin/customers-omde/reagent/list.
src/views/customer/single-customer/SingleCustomer.vue 131 Retail customer list + wallet charge modal.
src/views/customer/single-customer/SingleCustomerListModel.ts 17 XTbl model admin/customers-omde/single/list.
src/views/customer/single-customer/ViewSingleCustomer.vue 19 Wrapper → CustomerDetail.vue (single mode).
src/views/finical/addFinDoc.vue 358 Financial-document create/edit (5 document types).
src/views/finical/list.vue 135 Financial-document list + confirm/deny + delete.
src/views/finical/list_model.ts 39 XTbl model admin/documents/list.
src/views/invoice/AddInvoice.ts 20 Product-picker table model for the invoice builder.
src/views/invoice/AddInvoice.vue 288 Branch-allocation (فاکتور) builder.
src/views/invoice/EditInvoice.vue 15 Wrapper → AddInvoice.vue.
src/views/invoice/ListInvoice.ts 19 XTbl model admin/invoices/list.
src/views/invoice/ListInvoice.vue 130 Invoice list + detail modal + delete.
src/views/manage/Const.vue 384 Settings: brands / tags / mobile slider / collections tabs.
src/views/manage/report.vue 43 Dashboard-link shortcut board.
src/views/manage/wallet.vue 157 Wallet config (expiry/percent) + birthday config.
src/views/newsletters/newsletter.ts 17 XTbl model admin/misc/newsletter/list.
src/views/newsletters/newsletters.vue 27 Newsletter subscriber list.
src/views/notify/create.vue 60 Send-notification form.
src/views/notify/notifyTbl.ts 16 XTbl model admin/misc/notify/list (noFilters()).
src/views/notify/user.vue 94 Received notifications + mark-read.
src/views/offersIgnore/offersIgnore-add-edit.vue 225 Promotion exclusion (استثناعات) manager.
src/views/order/List.vue 263 Retail order list + status edit + PDF (stubbed).
src/views/order/MakeOrder.vue 224 Made-to-order (ساختنی) pre-order list + workshop assign.
src/views/order/MakeOrderModel.ts 38 XTbl model admin/pre-orders/list.
src/views/order/OmdeList.vue 238 Wholesale order list + status edit + delete.
src/views/order/OmdeListModel.ts 57 XTbl model admin/orders/wholesale/list.
src/views/order/TaminList.vue 209 Supply (تامین) order list + status edit.
src/views/order/TaminListModel.ts 38 XTbl model admin/orders/tamin/list.
src/views/order/create.vue 425 Admin order builder (cart + custom variant weight).
src/views/order/orderListModel.ts 34 XTbl model admin/orders/list.
src/views/pages/Error404.vue 20 404 page.
src/views/pages/Error500.vue 20 500 page.
src/views/pages/Maintenance.vue 20 Maintenance page.
src/views/pages/NotAuthorized.vue 20 403 page.
src/views/promotion/PromotionForm.vue 408 Shared omde/tak promotion create-edit form.
src/views/promotion/autopromotion.vue 79 Automatic-discount percent config.
src/views/promotion/promotionListModel.ts 18 XTbl model admin/promotions/list.
src/views/promotion/promotion_add_edit.vue 26 Wrapper → PromotionForm (omde mode).
src/views/promotion/promotion_list.vue 145 Promotion list + edit routing + delete.
src/views/promotion/promotion_tak_add_edit.vue 26 Wrapper → PromotionForm (tak mode).
src/views/reg.vue 121 Public self-registration form (no auth guard).
src/views/report/order_item.vue 100 Aggregated order+document report with date filters.
src/views/report/product.vue 84 Per-product sales report.
src/views/sms/create.vue 115 SMS composer + sent log.
src/views/sms/smsTbl.ts 17 XTbl model admin/misc/sms/list.
src/views/statics/index.vue 91 Customer activity log viewer.
src/views/user/ListUser.vue 98 Admin user list + delete.
src/views/user/Permission.vue 215 Permission tree editor per user type.
src/views/user/UserCreate.vue 279 Admin user create/edit.
src/views/user/UserEdit.vue 13 Wrapper → UserCreate.vue.
src/views/user/myData.vue 45 Current-user profile ("my data").
src/views/user/permissionConst.ts 866 The 179-entry permission tree (treeData).
src/views/user/userTbl.ts 18 XTbl model admin/users/list.
src/views/wallet/wallet.vue 44 Charged-wallet list.
src/views/wallet/walletTbl.ts 16 XTbl model admin/documents/wallet/list (noFilters()).
src/views/workshop/AddWorkshop.vue 189 Workshop create/edit.
src/views/workshop/EditWorkshop.vue 10 Wrapper → AddWorkshop.vue.
src/views/workshop/ListWorkshop.ts 14 XTbl model admin/workshops/list.
src/views/workshop/ListWorkshop.vue 25 Wrapper → FacilityList.vue for workshops.

6.2 Significant views — detail

src/views/auth/Login.vue

Fields: موبایل (a-input, :23-28), رمز عبور (a-input-password, :36-41, submits on Enter). Validation (ported VeeValidate required|min:8 / required|min:5, on blur — :79-92): empty → وارد کردن موبایل الزامی است / وارد کردن رمز عبور الزامی است; too short → مقدار ورودی صحیح نیست. Submit disabled until validateForm (:95-97). Action: auth.login({phone, password}) → POST admin-login; on success router.push('/dashboard/ecommerce') (:105-108); on failure message.error('خطا در ورود') (:111). Legacy's token_mobile (Firebase push) intentionally omitted (:103-104).

src/views/apps/product/ProductList.vue

Table model buildProductListModel() → POST admin/products/list. Columns (productListModel.ts:10-26): id "کد" (filterNumber), ProductImage "تصویر محصول" (slot image, noSort), title "نام محصول" (filterText), brand.title "برند" (filterText), category.collection.title "دسته بندی" (filterText), product_code "کد محصول" (filterText), show_type "نوع نمایش" (filterSelect {1:'سایت',2:'گوشی',3:'سایت و گوشی'}, slot show_type), action "عملیات" (slot op, noSort). Selection enabled (has-selection, :15) with a count banner تعداد N محصول انتخاب شده است. (:9). Row actions (:31-56, all hidden in branch mode via v-if="!is_branch"): ویرایش (product.list.edit → /apps/product/edit/{id}), اطلاعات جامع (product.list.view → GET admin/products/{id} → modal), حذف (product.list.remove, a-popconfirm بله/خیر, title آیا مطمئن به حذف محصول مورد نظر هستید ؟). Delete: POST delete/product/{id} via useDeleteHandler (:138-147), success محصول حذف شد (message.info), fallback error خطا در حذف محصول. Detail modal (:60-90, width 70%): descriptions نام محصول / کد محصول / برند / دسته بندی / نوع نمایش / تعداد تنوع, plus a variant sub-table (عنوان، وزن، اجرت درصدی، تعداد — :123-128). No export.

src/views/apps/product/ChangePercent.vue

Bulk percentage-wage change. Loads GET brand + POST category (:121). POST change/percent/count previews affected count (:87); POST change/all applies (:104). Validation: empty percent → message.error('لطفا اجرت درصدی را وارد کنید') (:99).

src/views/apps/category/Add.vue (+ Edit.vue)

Loads the category tree GET admin/categories/tree (:235) and, in edit mode, GET admin/categories/{id} (:243). Validation (:205-206): !form.type → لطفا نوع نمایش دسته بندی را انتخاب کنید; !form.title → لطفا مقدار را وارد کنید. Submit: PUT category/{id} (edit, :215) / POST category/ (create, :217); success اطلاعات با موفقیت به ثبت رسید. (:219), error message.error(server ?? 'خطا در ثبت اطلاعات.') (:227).

src/views/apps/category/List.vue

Table admin/categories/list (CategoryTbl.ts:9). Columns: id شماره (filterNumber), image تصویر (slot, noSort), type_str نوع نمایش, title عنوان دسته (filterNumber — legacy quirk preserved, :14), parent.title پدر دسته (filterNumber), opt عملیات (slot). Row actions gated on collection.list.edit / collection.list.remove. Delete via useDeleteHandler, success دسته حذف شد (:69-71).

src/views/apps/customer/bulkCustomer.vue (wholesale customer form)

Loads GET query/reagent (agent list, :422) and POST cities/list (:428); edit mode GET customer/{id} (:435). Validation (:377-379): fname, lname, mobile each → لطفا مقدار را وارد کنید. Submit PUT customer/{id} / POST customer/create (:400-402); success اطلاعات با موفقیت به ثبت رسید. (:404); error prefers a mobile-specific server message (:414).

src/views/apps/customer/singleCustomer.vue (retail customer form)

POST cities/list (:112), edit GET customer/{id} (:120). Submit PUT single/{id} / POST single (:97-99) — the single endpoint sets kind=RETAIL server-side (see endpoints.ts:68-75).

src/views/apps/customer/ReagentCustomer.vue (sales agent form)

Edit GET admin/users/{id} (:171); submit PUT users/{id} / POST users (:154-156).

src/views/order/List.vue (retail orders)

Table admin/orders/list (orderListModel.ts:12). Columns: id شماره (filterNumber); customer.fname نام مشتری (filterKey customer.fullName, filterText); customer.lname نام خانوادگی (same filterKey); total_price جمع کل سفارش (slot, filterText); items تعداد آیتم ها (slot items_len, noSort); caj تاریخ ثبت (filterKey createdAt, filterDate, noSort); uaj تاریخ ویرایش (hidden, filterKey updatedAt, filterDate); user ثبت کننده (slot, noSort); conditional on order.tamin.state (:24) → sts_str وضعیت (slot sts) + branch.name نام شعبه (filterText); opt عملیات (slot). Detail: GET admin/orders/{id} (:197). Status edits POST single/order-sts/edit (:203, :215) — success ویرایش موفق آمیز, error خطا در ویرایش. PDF action is stubbed: message.warning('خروجی PDF هنوز در بک‌اند جدید پیاده‌سازی نشده است') (:245), gated on order.list.pdf.

src/views/order/OmdeList.vue (wholesale orders)

Table admin/orders/wholesale/list. Columns (OmdeListModel.ts:19-52): id شماره; customer.name مشتری (filterKey customer.fullName); customer.mobile موبایل (hidden); input جمع کل سفارش (filterKey totalGoldWeightGrams); discount جمع کل تخفیف (درصد); customer.city_id شهر (hidden, slot city — the filterSelect(cityFilters) call stays commented out, legacy quirk :26); items تعداد آیتم ها; caj/uaj dates; conditional on order.omde.state → sts وضعیت (slot sts_str, filterKey status, filterSelect {0:'معلق',1:'ثبت سفارش',2:'تایید مدیر سیستم',3:'در حال تامین',4:'در حال آماده سازی ',5:'تحویل مسئول فروش',6:'تحویل مشتری '}); confirmed وضعیت تایید مشتری (filterSelect {1:'تایید شده',2:'در انتظار تایید'}); opt عملیات. Detail GET admin/orders/{id} (:171); status POST order-sts/edit (:187, :198); delete → سفارش با موفقیت حذف گردید (:217), gated order.omde.remove.

src/views/order/TaminList.vue

Table admin/orders/tamin/list, title لیست سفارش های عمده (same as OmdeList — legacy quirk, TaminListModel.ts:3-4,11). Columns add wage اجرت (filterKey wageWeightGrams). The status column is gated on order.tamin.state and calls filterSelect() with no argument → an empty dropdown (legacy quirk, :22-24).

src/views/order/MakeOrder.vue

Table admin/pre-orders/list (MakeOrderModel.ts:13). Columns: customer.id شماره (filterKey id), customer.fname/customer.lname (filterKey customer.fullName), color رنگ محصول (filterKey colorName), size سایز محصول (sizeSpec), color_mina رنگ مینا (minaColor), color_stone رنگ سنگ (stoneColor), sum تعداد, time_creat_fa تاریخ ثبت (createdAt, date), time_update_fa (hidden), customer.name ثبت کننده (slot), conditional on order.tamin.state (NOT order.makeOrder.state — legacy quirk :28-29) → sts_str_fa وضعیت; opt عملیات. Workshop list POST workshop/table {page:1,limit:100} (:168); detail GET admin/pre-orders/{id} (:177); update PUT custom-order/{id} (:193).

src/views/order/create.vue (admin order builder, 425 LOC)

Loads GET order/type and POST single-customer {page:1,limit:500} (:394-395). Form fields (:126-161): نوع فروش (select, validated), نام دریافت کننده, نام خانوادگی دریافت کننده, استان, شهر, کد پستی, تلفن همراه, تلفن ثابت, توضیحات, آدرس. Validation (:350-352): customer_id, sts, type each empty → مقدار را وارد نمایید. Cart table has a حذف column (:251, width 60, centered). Adding an item → message.info('اضافه شد') (:308). Custom weight → POST add/variant (:327), error خطا در ثبت وزن (:336). Submit POST order (:373); success عملیات با موفقیت انجام شد (:380). Cart state from src/stores/cart.ts.

src/views/finical/addFinDoc.vue (358 LOC)

Type-dependent form (:30 نوع سند select). Per-type fields (:60-118): assay — وزن (گرم), عیار, نام آزمایشگاه, انگ, وزن نهایی اعمال شده; transfer — وزن (گرم), حواله به; cheque — مبلغ چک, وزن (گرم), نام صاحب حساب, نام بانک; cash — میزان پول, مظنه; always توضیحات (:116). Customer lookup GET query/customer (:325); edit GET cash/{id} (:333). Validation (:296): !form.customer_id → مقدار را وارد نمایید. Submit PUT cash/{id} / POST cash (:305-307); success اطلاعات با موفقیت به ثبت رسید.

src/views/finical/list.vue

Table admin/documents/list (list_model.ts:9). Columns: id شماره سفارش (filterNumber); type_str نوع سند (filterText); price پول (filterDate — legacy quirk, :13); value وزن نهایی (گرم) (filterDate); to به (hidden); customer.name نام و نام خانوادگی مشتری; note توضیحات; ayar عیار (filterNumber); lab آزمایشگاه; recive_date تاریخ دریافت (filterDate); account_name نام حساب (hidden); bank_name بانک (hidden); caj تاریخ ثبت; uaj بروزرسانی (hidden); confirmed وضعیت تایید سند (slot, filterSelect {0:'تایید نشده',1:'تایید شده'}); price_per_gram مظنه (hidden); original_value وزن طلای داده شده (hidden); ang انگ (hidden); opt عملیات. Confirm POST cash/confirem/{id} (:111) → تغییرات با موفقیت ثبت شد. Actions gated on document.list.confirm, document.list.deny, document.list.edit, document.list.remove. Delete → سند حذف شد (:124-126).

src/views/customer/omde/OmdeCustomerList.vue (448 LOC, backs 5 routes)

Table model via buildOmdeListModel(source); source ∈ omde|banker|shopkeeper|nonVerified|archived → urls in omdeListModel.ts:12-18. Columns (omdeListModel.ts:34-65): id کد (filterNumber); fname/lname (filterText, hidden); name نام و نام خانوادگی (filterText); mobile موبایل (hidden, noSort); city_id شهر (hidden, slot city, filterSelect from users.cityFilters); reagent.name کارشناس فروش; gram_level سطح اعتبار; percent درصد; remain بدهی گرمی; rial_remain بدهی ریالی; caj تاریخ ثبت (filterDate); uaj بروزرسانی (hidden); sts_str وضعیت ثبت سفارش (slot sts); active وضعیت حساب کاربری (slot, filterSelect {'1':'فعال','-1':'غیر فعال'}); phone, address, store_name, zone, pasazh_name (all hidden); opt عملیات. Actions: verify POST major/verify (:352); toggle account POST customer/toggle-active (:391); toggle order permission POST customer/toggle-sts (:403); archive GET major/archive/{id} (:413). All success toasts تغییرات ثبت شد (with trailing space). Errors: خطا در تغییر وضعیت, خطا در بایگانی. OmdeNonVerifiedList.vue:5 notes a preserved legacy quirk: the step-one confirm rejects instead of accepting.

src/views/customer/single-customer/SingleCustomer.vue

Table admin/customers-omde/single/list. Columns (SingleCustomerListModel.ts:6-12): id کد (filterNumber); fname نام (filterKey fullName); lname نام خانوادگی (filterKey fullName, noSort); phone تلفن تماس (filterKey mobile, noSort); email ایمیل (noSort); branch.name نام شعبه (noSort); op عملیات. Wallet-charge modal → POST wallet/charge (:99), gated wallet.charge; empty value → لطفا مقدار را وارد کنید (:94); success عملیات با موفقیت انجام شد (:103).

src/views/customer/reagentList/ReagentList.vue

Table admin/customers-omde/reagent/list. Columns per ReagentListModel.ts:16-43, incl. the legacy quirk that name is added twice (visible نام و نام خانوادگی :18, hidden نام :20), plus grams_sell میزان فروش. Toggle active → PUT users/{id} {is_active} (:109).

src/views/customer/CustomerDetail.vue

Parallel loads (:174-177): GET customer/{id}, GET major/dashboard/{id} (catch→undefined), GET admin/customers-omde/{id}/orders (catch→[]), GET admin/customers-omde/{id}/documents (catch→[]), plus lazily GET log/ago/{id} (:191). Orders/documents tabs gated on persons.list.client.omde.view.orders / .view.documents.

src/views/customer/Comments/Comments.vue

Table admin/comments/list; changeState column added only with comment.state (CommentListModel.ts:21-23). Status change POST comment/sts {id, sts} (:198) → تغییرات ثبت شد ; guards حالت فعلی تایید می باشد / حالت فعلی عدم تایید می باشد when already in that state (:50, :61). Delete DELETE comment/{id} (:215) → دیدگاه حذف شد / خطا در حذف. Reply POST comment (:225).

src/views/invoice/AddInvoice.vue / ListInvoice.vue

Builder: branch list GET branch/list (:253); edit GET branch/product/{id} (:261); submit PUT branch/product/{id} / POST branch/product with {...form, products} (:231-233). Validation !form.branch_id → مقدار را وارد نمایید (:224). Cart from src/stores/invoiceCart.ts. Item table has a حذف column (:202); add → اضافه شد (:214); success عملیات با موفقیت انجام شد (:235). List columns (ListInvoice.ts:7-14): id ردیف; name نام مشتری (filterKey branch.name); sum_weight جمع کل سفارش; count_item تعداد آیتم ها; caj تاریخ ثبت (filterKey createdAt, filterDate); allocator_user.name ثبت کننده (slot user); note توضیحات; op عملیات (slot actions). Detail GET branch/product/{id} (:113); delete → فاکتور حذف شد (:118-120).

src/views/user/Permission.vue

Loads user types GET const/__list__ut (:118), then the type's rids GET roles/{id} (:136), rendering treeData as a checkable a-tree. Validation (:147, :151): !ut → لطفاسطح دسترسی را انتخاب کنید; !user → لطفا نام سطح دسترسی را وارد کنید. Create user type POST const (:160); save rids POST roles/create {ut, roles} (:167) → عملیات با موفقیت انجام شد. Gated on user.permission, user.permission.create, user.permission.edit.

src/views/user/UserCreate.vue / ListUser.vue

Create: parallel GET const/__list__ut + GET admin/users/role-options (:241-242); edit GET admin/users/{id} (:252). Validation: password mismatch → رمز عبور و تأیید آن یکسان نیستند (:200). Submit PUT users/{id} / POST users (:220-222). List columns (userTbl.ts:6-13): id کد مدیر; fname نام (filterKey fullName); lname نام خانوادگی (filterKey fullName); mobile موبایل (filterText); ut نوع کاربر (filterNumber, slot ut); op عملیات. Delete → کاربر حذف شد (:82-84), gated user.list.remove / user.list.edit.

src/views/manage/Const.vue (384 LOC) — 4 tabs

برند ها (:19): GET brand (:217), POST brand/create (:224) → برند اضافه شد, PUT brand/{id} (:241) → تغییرات ثبت شد, delete via handler → برند حذف شد. Gated config.statics.brand.create/.edit/.remove. برچسب ها (:65): GET const/__list__tags (:346), PUT const/__list__tags {val} (:275) → برچسب ها ذخیره شد. Gated config.statics.tags.create/.remove. اسلایدر موبایل (:99-102, only when custom.config.statics.slider, :347): GET const/slider, PUT const/slider {val} (:290) → اسلایدر ذخیره شد; uses ImageUploadInput. سینی ها (collections, :120): POST ciny {page:1,limit:200} (:299), PUT ciny/{id} / POST ciny/create (:307-309) → عملیات انجام شد, DELETE ciny/{id} (:333) → حذف شد.

src/views/manage/wallet.vue

GET wallet/config + GET wallet/birthday/config (:137-138); POST wallet/config {expire, percent} (:101); POST wallet/birthday/config (:122). Both → تغییرات ثبت شد. Gated config.wallet, wallet.percent.

src/views/promotion/PromotionForm.vue (408 LOC, backs omde + tak)

Loads in parallel (:356-358): POST products {page:1,limit:500}, POST category {page:1,limit:500}, GET brand; plus GET query/customer (:369) and, in edit mode, GET admin/promotions/{id} (:378). Validation (:323-325): !form.title → لطفا مقدار را وارد کنید; missing discount percent → لطفا مقدار را وارد کنید. Submit PUT product/variant/promotion/{id} / POST product/variant/promotion (:335-337) → اطلاعات با موفقیت به ثبت رسید.

src/views/promotion/promotion_list.vue

Table admin/promotions/list (promotionListModel.ts:5). Columns: id کد (filterNumber); title نام پروموشن (filterText); promotion_type_str نوع پروموشن; platform پلتفرم نمایش ; status وضعیت (slot sts); start تاریخ شروع (slot startDate); end تاریخ پایان (slot endDate); action عملیات (slot op). Delete DELETE major/promotion/{id} (:129) → تخفیف حذف شد / خطا در حذف تخفیف. Edit/remove gated on offers.list.edit / offers.list.tak.edit / offers.list.remove / offers.list.tak.remove.

src/views/promotion/autopromotion.vue

GET autopromotion (:69), POST autopromotion (:55). Validation: err.discount_percent = 'وارد کردن درصد تخفیف اتوماتیک الزامی است' (:50); success اطلاعات با موفقیت به ثبت رسید. (:59).

src/views/offersIgnore/offersIgnore-add-edit.vue

GET ignore (:198, error خطا در دریافت اطلاعات), POST ignore (:166) → عملیات با موفقیت انجام شد, DELETE ignore/{type} (:184) → حذف شد.

src/views/city/cityList.vue / createCity.vue

List table admin/misc/cities/list (CityListModel.ts:8). Columns: title نام شهر (filterKey name, filterText); level سطح (filterNumber, noSort — no such field in the payload, always blank; legacy quirk :3-4); percent درصد مشتری عمده (slot); percent_aux درصد کارشناس فروش (slot); opt عملیات. Editing a row loads GET cities/get-percent/{id} + GET cities/get-percent-aux/{id} (:92-93) then PUT cities/{id} (:106) → تغییرات ثبت شد. Gated cities.list.view.edit / cities.list.remove; delete → شهر حذف شد (:120-122). Create: validation err.title = 'لطفا مقدار را وارد کنید' (:80); POST cities (:85).

src/views/branch/AddBranches.vue / FacilityList.vue / workshop/AddWorkshop.vue

Branch form: GET user/list for the manager select (:162); edit GET branch/{id} (:169); submit PUT branch/{id} / POST branch (:139-141). Validation err.name = 'لطفا مقدار را وارد کنید' (:133). Workshop form: validation !form.workshop_name → لطفا مقدار را وارد کنید (:141); !user_phone → موبایل سرپرست کارگاه الزامی است (:144); submit PUT workshop/{id} / POST workshop (:151-153). FacilityList.vue is shared by ListBranches.vue and ListWorkshop.vue; the active toggle calls GET {activePath}/{id} (:104) → تغییرات ثبت شد / خطا در تغییر وضعیت. Branch columns (ListBranch.ts:10-15): name نام, address آدرس, phone شماره تلفن (filterNumber), user.name مدیر شعبه (filterNumber, noSort), op عملیات. Workshop columns (ListWorkshop.ts:6-9): name, address, phone (filterNumber), op.

src/views/sms/create.vue

Recipient mode gated per rid: sms.create.single, sms.create.multiple, sms.create.all, sms.create.reagent, sms.create.omde; log tab gated sms.list. Validation (:86-88): empty text → متن پیامک الزامی است; empty mobile → شماره موبایل الزامی است. Submit POST sms (:94); success پیامک برای ${data.sentCount} شماره ثبت شد (:99); error خطا در ارسال پیامک. Log table admin/misc/sms/list (smsTbl.ts:6-12): id شماره; text متن پیامک (filterKey body, filterText); mobile گیرندگان (noSort); sent_count تعداد; sender ارسال کننده; caj تاریخ (filterKey createdAt, filterDate).

src/views/notify/create.vue / user.vue

Create validation (:41-42): !form.title → عنوان الزامی است; !form.text → متن پیام الزامی است. POST notify (:47) → نوتیفیکیشن ارسال شد. Received list admin/misc/notify/list with noFilters() (notifyTbl.ts:11); columns id شماره, title عنوان, text متن (noSort, key differs from legacy's note — :8-9), opt عملیات. Detail GET notify/{id} (:73); mark read GET notify/read/{id} (:81) → علامت‌گذاری شد.

src/views/report/order_item.vue / product.vue

GET report/order-item and GET report/product, both with { params: filters } (:76, :60) — query-string filters, not the XTable POST envelope. Date filters use DateInput.vue (Jalali → ISO).

src/views/statics/index.vue

GET log/list/customer (:82) then per-customer GET log/ago/{customer} (:71).

src/views/wallet/wallet.vue

Table admin/documents/wallet/list with noFilters() (walletTbl.ts:11). Columns: id شماره; customer.name نام مشتری (filterKey customer.fullName); order.id شماره سفارش (filterText); value میزان شارژ (slot); expire_at تاریخ انقضا (noSort, slot time).

src/views/newsletters/newsletters.vue

Table admin/misc/newsletter/list (newsletter.ts:9). Columns: name نام (filterKey email, filterText); mobile شماره موبایل (filterNumber); op عملیات (slot actions).

src/views/reg.vue (PUBLIC — in noneAuthUrl)

Validation (:85-87): !fname → لطفا نام را وارد کنید; !lname → لطفا نام خانوادگی را وارد کنید; !mobile → لطفا شماره تلفن همراه را وارد کنید. Submit POST customer/create (:92) → اطلاعات با موفقیت ثبت شد.

src/views/branch/dashboard.vue (branch mode)

Parallel GET get-analytics-tak + POST products {page:1,limit:5} (:75-76).

src/views/user/myData.vue

GET admin/users/me (:37). Fallback target for the dashboard guard redirect (src/router/index.ts:121).

6.3 Export

XTable's export buttons (excel/json/print) and the fullscreen toggle were dropped — the legacy endpoint returning a file URL is not implemented in the new backend (src/components/x-table/XTable.vue:8-12). A decorative چاپ (print) button exists with no handler (XTable.vue:32-36). requestData() still sends select, titles and report_title (XTable.vue:263-280), i.e. the server-side export contract is preserved even though no client triggers it.


7. Store (Pinia)

Store File State Getters Actions Endpoints
auth src/stores/auth.ts userPermission: string[] (:120) token (localStorage tokenName, :123), user (JSON from userName, :124-132), isLoggedIn (:133-135) login(payload) (:148-162), inject(payload) (:165-170), logout() (:177-184) POST admin-login (:149)
users src/stores/users.ts cityFilters: Record<id,title> (:251), userInfo (:252), roles?: string[] (:255) hasPermission(id) (:263-269) setCities (:273), getCities() (:278-285), setUserInfo (:286), deleteUserInfo (:289), getRoles(roles) → checkParent (:293-295), deleteRoles (:296) POST cities/list (:280)
cart src/stores/cart.ts items: CartItem[] (:45) — addToCart (:49-61), setItems (persist, :64-67), add (:69), remove (:74), updateItemQuantity (:80), clear (:85), init() (:94-123) POST product/variant {products_id} (:102)
invoiceCart src/stores/invoiceCart.ts items: InvoiceCartItem[] (:28) — setItems (:32), add (:38), + remove/clear none (no rehydrate fetch, :12-13)
layout src/stores/layout.ts windowWidth, scrollY, theme, isSidebarActive, sidebarCollapsed (:323-329) isSmallScreen (<992, :333) updateWindowWidth, updateWindowScrollY, toggleSidebar, setSidebarCollapsed (persists admin-panel:sidebarCollapsed) (:336-356) —

auth.login (src/stores/auth.ts:148-162): POST admin-login → users.getRoles(data.roles.map(x => x.rid)), strips roles + ut, then inject() writes tokenName, login='true' (consumed once by App.vue for the welcome toast) and userName to localStorage. auth.logout (:177-184) — not in legacy: clears token, user, login, and both users.deleteRoles() / deleteUserInfo() (fixes the cross-session permission leak). cart persistence key: isBranch() ? 'branch_cart_items' : 'admin_cart_items' (:16). cart.init() refetches products and drops out-of-stock, non-pre-order variants (:108). invoiceCart key: isBranch() ? 'branch_invoice_items' : 'admin_invoice_items' (:17) — deliberately different from legacy, which reused admin_cart_items for both and clobbered data (:6-10).


8. Shared utils

8.1 src/utils/index.ts (19 lines)

Export Value Line
isBranch() import.meta.env.MODE === 'branch' :12-14
tokenName isBranch() ? 'branch_token' : 'token' :18
userName isBranch() ? 'branch_user' : 'user' :19

8.2 src/utils/filters.ts (72 lines) — formatters (Vue 3 has no Vue.filter, so these are plain functions)

Function Behaviour Line
capitalize(value) Capitalizes each space-separated word; '' when falsy :29-39
title(value, replacer='_') Capitalizes each replacer-separated word :41-51
truncate(value, limit) value.substring(0, limit) :53-55
priceUnit(val) Thousands separators + ' تومان ' (trailing space); returns input unchanged when falsy, so 0 renders "0" not "0 تومان" :58-63
price(val) Thousands separators only, regex /\B(?=(\d{3})+(?!\d))/g → ','; returns input unchanged when falsy :66-71
jdate(val) moment(val).format('jYYYY/jMM/jDD') :74-79
jdateTime(val) moment(val).format('jYYYY/jMM/jDD - hh:mm:ss') — 12-hour hh, legacy quirk preserved :82-87
none(val) Identity :89-91

Gram/weight: there is no dedicated gram formatter. Weights are rendered raw with a literal گرم suffix at each call site (e.g. Dashboard.vue:23,50,112,116) or via Number(x).toFixed(2) (Dashboard.vue:217,222).

Persian↔English digit conversion: only one implementation, inline in src/components/input/DateInput.vue:121: raw.replace(/[۰-۹]/g, (d) => String('۰۱۲۳۴۵۶۷۸۹'.indexOf(d))). There is no shared digit-normalisation util and no price-input mask.

8.3 Validators

No validator module exists. Every form hand-rolls a validate() that writes into a reactive err object; the recurring message is لطفا مقدار را وارد کنید / مقدار را وارد نمایید (see §6.2 per view).

8.4 Constants / enums with exact values

Constant Values Source
show_type (product display) 1: سایت, 2: گوشی, 3: سایت و گوشی src/views/apps/product/productListModel.ts:20-22; same in AddProduct.vue:100-102
Wholesale order status 0: معلق, 1: ثبت سفارش, 2: تایید مدیر سیستم, 3: در حال تامین, 4: در حال آماده سازی (trailing space), 5: تحویل مسئول فروش, 6: تحویل مشتری (trailing space) src/views/order/OmdeListModel.ts:35-41
Customer confirm status 1: تایید شده, 2: در انتظار تایید src/views/order/OmdeListModel.ts:48-49, TaminListModel.ts:29-30
Account active status '1': فعال, '-1': غیر فعال src/views/customer/omde/omdeListModel.ts:56-57, ReagentListModel.ts:39-40
Document confirm status 0: تایید نشده, 1: تایید شده src/views/finical/list_model.ts:27-28
Variant active tag فعال / غیر فعال (green/red) src/views/apps/product/AddProduct.vue:186-188
Variant pre-order tag بله / خیر (blue/none) src/views/apps/product/AddProduct.vue:191-193
Dashboard tag colours success→green, warning→orange, danger→red, primary→blue src/views/Dashboard.vue:209-211
Dashboard chart palette COLOR_PRIMARY '#1c2d56', COLOR_SUCCESS '#28c76f', COLOR_WARNING '#ff9f43', COLOR_DANGER '#ea5455' src/views/Dashboard.vue:190-193
BaseFormModal widths sm:520, md:720, lg:900, xl:1140 src/components/common/BaseFormModal.vue:230-235
OMDE list sources 5 url/title pairs src/views/customer/omde/omdeListModel.ts:11-19
FilterType 'default' | 'switch' | 'query' | 'date' | 'number' | 'select' src/components/x-table/model.ts:15
Sidebar persist key 'admin-panel:sidebarCollapsed' src/stores/layout.ts:308
Small-screen breakpoint 992 src/stores/layout.ts:333

9. Components

Component File Role / API
XTable src/components/x-table/XTable.vue The one surviving table wrapper (legacy had 4 forks, model.ts:9-12). Props model: XTbl, has-selection, v-model = selected rows. Contract: POST model.url with {search, filters, total, limit, page, order, select, titles, report_title} (:263-280), response {rows, query:{total,page,limit}} (:289-294). Exposes fetch(). Toolbar: search (:17-25), filter bar with badge count (:30-37), column show/hide dropdown (:41-53), reload (:55-60), decorative print button with no handler (:32-36). Pagination {current, pageSize, total, showSizeChanger:true} unless _hidePagination (:251-259). Sorting via onTableChange → sort + sortDirection (asc/desc, :331-337). addFilterRow() keys rows with a random float (:315-317).
XTbl / Xtc src/components/x-table/model.ts Fluent column API. Xtc(key,title) with .filterText() .filterSwitch() .filterQuery(action) .filterDate() .filterNumber() .filterSelect(itemsMap?) .filterKey(k) .renderSlot(name) .noSort() .hidden() .setWidth(w) (:55-129). XTbl(url,title) with .add(col) .addDefaultFilter() .hidePagination() .hideSearch() .noFilters() .getSortFields() (:139-188). filterSelect accepts an object map value→label and is legitimately callable with no argument (:98-112). Legacy's hideSearch() bug (set _hidePageination) is fixed here (:169-178).
ImageUploadInput src/components/upload/ImageUploadInput.vue Single-image uploader (a-upload, list-type="picture-card", max-count=1, accept="image/*"). POST file/create as FormData with file + optional purpose (:88-99). v-model is the stored URL; also emits ok with the whole response. Watches modelValue to keep the thumbnail in sync (:74-84). Preview modal (:32-34). Error بارگذاری فایل ناموفق بود (:103). Replaces legacy's four near-identical uploaders (:6-9).
DateInput src/components/input/DateInput.vue Jalali date input. Displays jYYYY/jMM/jDD, emits ISO YYYY-MM-DD (:126). Normalises Persian digits before parsing (:121). Invalid input → inline hint تاریخ معتبر نیست (:128), styled #ea5455 (:135). Replaces vue-persian-datetime-picker (no Vue 3 build, :56-60). Placeholder default مثال: ۱۴۰۳/۰۵/۱۲ (:84).
BaseFormModal src/components/common/BaseFormModal.vue Standard add/edit modal shell. Props open, id (presence switches title to editTitle), addTitle, editTitle, loading, submitting, width (sm|md|lg|xl), okText (default ثبت اطلاعات). Cancel text انصراف, mask-closable: false. Emits update:open, submit, cancel. Replaced 16 hand-rolled modals with inconsistent widths (:174-176).
ListPageHeader src/components/common/ListPageHeader.vue Permission-gated "add" button for list pages: `v-if="!rid
TooltipBtn src/components/tooltip-btn/TooltipBtn.vue Row-action icon button. Props title, popTitle, icon, iconClass (default w-5 h-5). With popTitle wraps in a-popconfirm (بله/خیر, :259-266); without, fires immediately. Icon resolved from a legacy Feather name via resolveIcon (:297), falling back to UnorderedListOutlined.
StatisticsCardLine src/components/statistics-cards/StatisticsCardLine.vue KPI tile: icon, color, statistic, statistic-title. The legacy sparkline branch is not ported — every live caller passed hideChart (:307-312). Used 6× on the Dashboard.
icons.ts src/components/icons.ts Legacy Feather-name → ant-design icon map, shared by TooltipBtn and the sidebar (:1-6). Exports ICONS and resolveIcon.
useDeleteHandler src/composables/useDeleteHandler.ts Shared delete composable (not a component). remove(url, {method?: 'delete'|'post'}); success → message[successType](successMessage) then onSuccess(); failure → Modal.error({ title: server message ?? fallback, okText:'بستن' }) (:43-46). Defaults: با موفقیت حذف شد, success, خطا در حذف. Used by 6 list views.
Layout src/layouts/main/Main.vue (91), components/TheNavbar.vue (223), components/VerticalNavMenu.vue (192), components/NavMenuNode.vue (45), full-page/FullPage.vue (6) Main shell = navbar + vertical nav + <router-view>; FullPage is a bare <router-view> for login/error pages.

Absent: there is no price-input component with a mask or digit conversion. Price/number fields use plain a-input-number (e.g. AddProduct.vue:212,215,218). Digit conversion exists only inside DateInput.vue.


10. PROTECTED zones — precise description

(a) Dashboard — src/views/Dashboard.vue (327 lines)

Route: two paths both mount this one file — /dashboard/ecommerce (dashboard-ecommerce, meta rule admin, role dashboard.view, src/router/admin.ts:54-61) and /dashboard/analytics (dashboard-analytics, rule editor, same role, :36-43). / redirects to /dashboard/ecommerce (:27-30). This merges legacy's two dashboards (آمار عمده فروشی + آمار تک فروشی, two rids) into one screen behind one rid (Dashboard.vue:2-10). A user lacking dashboard.view is redirected to {name:'my-data'} rather than the 403 page (src/router/index.ts:120-122).

Data: a single GET dashboard/overview in onMounted (:301) → admin/dashboard/overview (endpoints.ts:146). Object.assign(data, res), then two client-side sorts preserved from legacy: data.reagent desc by grams_sell (:304-306) and data.lastOrder desc by w (:307-309). Wrapped in a-spin :spinning="loading" (:11); errors only console.error (:311).

Reactive shape (:172-186): customerCount, allgram, ordercount, reagent[], lastOrder[], sumLastOrder, sumLastOrderMonth, ordersTrend[], statusBreakdown[], channelBreakdown[], topProducts[], goldPricePerGram, totalCustomerDebtGrams. Interfaces: OrdersTrendPoint {date,total,retail,wholesale} (:149-154), StatusBreakdownRow {status,label,count} (:155-159), ChannelBreakdownRow {channel,label,count,grams} (:160-165), TopProductRow {title,weight,qty} (:166-170).

Layout, top to bottom:

  1. KPI row — .grid-4, four StatisticsCardLine (:13-38):
    icon color statistic title line
    UsersIcon warning ${data.customerCount ?? 0} نفر مشتریان جدید (۳۰ روز گذشته) :14-19
    ShoppingBagIcon primary ${data.allgram ?? 0} گرم طلای فروش رفته (۳۰ روز گذشته) :20-25
    ShoppingBagIcon success ${data.ordercount ?? 0} سفارش تعداد سفارشات (۳۰ روز گذشته) :26-31
    DollarSignIcon danger goldPriceLabel نرخ هر گرم طلای ۱۸ عیار :32-37
  2. Second KPI row — .grid-2.mt-4, two tiles (:40-53): BarChartIcon/primary ${data.sumLastOrder ?? 0} سفارش "تعداد سفارشات تحویل‌شده (۳۰ روز گذشته)"; CreditCardIcon/warning ${totalDebtLabel} گرم "مجموع بدهی طلای مشتریان".
  3. روند سفارش‌ها (۳۰ روز گذشته) — a-card + apexchart type area, height 300 (:56-63). Two series: تک فروشی = ordersTrend.map(d => d.retail), عمده فروشی = d.wholesale (:226-232). Options (:233-252): colors [COLOR_PRIMARY, COLOR_SUCCESS], toolbar hidden, dataLabels off, stroke {curve:'smooth', width:2}, gradient fill opacityFrom .35 / opacityTo .05, legend top-right, x-axis categories jdate(d.date) with rotate:-45, rotateAlways:true, hideOverlappingLabels:true, fontSize 12px, fontWeight 500 (added deliberately because 30 daily labels overlapped, :242-249), tooltip {shared:true}.
  4. .grid-2.mt-4 — two donut charts, height 300 (:65-73):
    • وضعیت سفارشات (۳۰ روز گذشته): series statusBreakdown.map(s => s.count), labels s.label, an 8-entry colour array [WARNING, PRIMARY, PRIMARY, WARNING, WARNING, SUCCESS, SUCCESS, DANGER] (:259), legend bottom, dataLabels on.
    • تفکیک عمده / تک (۳۰ روز گذشته): series channelBreakdown.map(c => c.count), labels c.label, colors [PRIMARY, SUCCESS], legend bottom, dataLabels on (:265-272).
  5. .grid-2.mt-4 — two cards (:75-104):
    • عملکرد کارشناسان فروش: horizontal bar (plotOptions.bar {horizontal:true, borderRadius:4}, :280), height 280, rendered only v-if="agentChartSeries[0]?.data.length" (:78); series گرم فروخته‌شده from reagent[].grams_sell, x categories reagent[].name (:275-284). Always followed by an a-table (size small, pagination:false, row-key="id") with columns کارشناس فروش (name) / میزان فروش (گرم) (grams_sell) / تعداد سفارش (order_count) (:84-91, :195-199).
    • محصولات پرفروش (۳۰ روز گذشته): vertical bar, height 280, columnWidth '55%', borderRadius 4, color [COLOR_SUCCESS], dataLabels off, categories topProducts[].title, series وزن فروخته‌شده (گرم) from topProducts[].weight (:287-296); falls back to <a-empty description="داده‌ای برای نمایش وجود ندارد" /> (:102).
  6. میزان فروش — a-descriptions bordered size=small :column=3 (:106-118): تعداد سفارش (۳۰ روز) = sumLastOrder; مجموع وزن (۳۰ روز) = sumLastOrderMonth + گرم; میانگین هر سفارش = avgWeightPerOrder + گرم.
  7. سفارشات هفته جاری — a-table over data.lastOrder, size small, pagination {pageSize:10}, row-key="id" (:120-137). Columns (:201-207): شماره سفارش (name), مشتری (customer), وزن (w), وضعیت (sts_str), تاریخ (caj). bodyCell overrides: sts_str → <a-tag :color="tagColor(record.statusColor)"> (:129-131); caj → jdate(record.caj) (:132-134).

Computed (:213-223): goldPriceLabel = price(Math.round(Number(goldPricePerGram))) + ' تومان', or '—' when falsy; totalDebtLabel = price(Number(totalCustomerDebtGrams ?? 0).toFixed(2)); avgWeightPerOrder = sumLastOrderMonth / sumLastOrder to 2 dp, '0' when count is 0. tagColor(c) maps {success:'green', warning:'orange', danger:'red', primary:'blue'}, default '' (:209-211). Scoped styles only define .mt-4 (1rem) and .mt-3 (.75rem); .grid-2/.grid-3/.grid-4 come from the global utilities.less (:318-326).

No filters, no date-range picker, no export, no drill-down links, no refresh button on this screen — one fetch on mount.

(b) Variants section inside Create/Edit Product — src/views/apps/product/AddProduct.vue

AddProduct.vue (579 lines) backs both create (/apps/product/add, role product.create) and edit (/apps/product/edit/:id/:variant_id?, role product.list.edit) — EditProduct.vue (17 lines) is only a wrapper.

Card gate: the whole تنوع ها card renders only with product.list.variants (:151). The header افزودن تنوع button needs product.list.variants.add (:155-161).

Variant table (:165-196, row-key="__key", :pagination="false", size="small"). Columns (:363-372):

title dataIndex rendering
تصویر image <img :src="record.image_url" class="variant-thumb"> (40×40, cover, radius 4) or a dashed — placeholder (:173-176, :520-533)
عنوان title plain
وزن grams plain
اجرت درصدی percentage_pay plain
تعداد qty plain
فعال is_active <a-tag :color="green|red"> فعال / غیر فعال (:185-189)
پیش سفارش (leading space) is_pre_order <a-tag :color="blue|''"> بله / خیر (:190-194)
عملیات op ویرایش (product.list.variants.edit, EditOutlined) and حذف (product.list.variants.remove, DeleteOutlined → variants.splice(index,1), no confirm dialog) (:177-184)

Variant modal (:206-270), title افزودن تنوع when editingIndex === null, otherwise ویرایش تنوع:

Field Control Line
عنوان a-input :208-210
وزن a-input-number :step="0.001", full width :211-213
اجرت درصدی a-input-number :step="0.1", full width :214-216
تعداد a-input-number, full width :217-219
تصویر تنوع gallery picker — the extra option :230-257
فعال a-switch :259-261
پیش سفارش (leading space) a-switch :262-264

Footer: انصراف (closes) and ثبت (saveVariant) (:266-269).

⭐ THE EXTRA / NEW OPTION — تصویر تنوع (variant image picker), :221-257 + :315-361 + :519-568:

  • It is not an uploader. It is a picker over the product's already-saved gallery: the variant points at the same ProductImage row the gallery shows, so one photo can back several weights (:222-224).
  • Source data is gallery = ref<GalleryImage[]>([]) where GalleryImage {id, url, role, sortOrder} (:318-324), populated only in edit mode from data.gallery on GET product/show/{id} (:487). On create it is empty, because uploaded files have no ProductImage rows (hence no ids) until the first save (:226-228, :315-317).
  • UI when gallery.length (:231-250): a .variant-picker flex-wrap row of 64×64 buttons. The first button is a بدون تصویر ("no image") tile calling pickVariantImage(null), highlighted when !variantForm.image_id (:232-239). Then one <img> button per gallery image, highlighted (.is-on, blue #1890ff 2px border, :556-558) when variantForm.image_id === img.id.
  • UI when the gallery is empty (:251-256): an a-alert type="info" show-icon whose message is context-dependent — برای این محصول تصویری ثبت نشده است. in edit mode (id truthy) vs ابتدا محصول را ذخیره کنید تا بتوانید تصویر تنوع را انتخاب کنید. on create.
  • pickVariantImage(img) sets both variantForm.image_id = img?.id ?? null and variantForm.image_url = img?.url ?? null (:358-361). image_id is what gets posted; image_url is carried purely so the table can draw the thumbnail without a lookup (:336-340).

Variant row type VariantRow (:327-341): __key (client key), id?, title, grams?, percentage_pay?, qty?, is_active, is_pre_order, image_id?, image_url?. variantForm defaults (:346-356): title:'', grams:undefined, percentage_pay:undefined, qty:0, is_active:true, is_pre_order:false, image_id:null, image_url:null. showModal(record?, index?) (:374-394): editing copies the record via Object.assign; adding resets and mints __key = 'v-' + Date.now() + '-' + random base36 slice (:381). saveVariant() (:396-408): the only variant validation is if (!variantForm.title) message.error('عنوان تنوع الزامی است') (:397-400); otherwise pushes or replaces at editingIndex and closes.

Persistence — variants are edited in memory and saved with the product, never through a per-variant endpoint. handleSubmit() (:416-448) builds payload = {...form, cats: form.category_id ? [form.category_id] : [], images: gallerySite, images_mobile: galleryMobile, variants: variants.map(v => ({id, title, grams, percentage_pay, qty, is_active, is_pre_order, image_id: v.image_id ?? null}))} (:420-435) — note image_url and __key are stripped before posting. Then PUT product/update/{id} (edit) or POST product/create (:436-440), success اطلاعات با موفقیت به ثبت رسید. and router.push('/apps/product/list'). On load in edit mode, variants come from data.variants_all mapped with __key: v.id ?? 'v-'+i, grams: Number(v.grams), percentage_pay: Number(v.percentage_pay), image_id: v.image_id ?? null, image_url: v.image?.url ?? null (:488-499).

Rest of the product form (context, also in the same file): image card with 4 slots — تصویر شاخص (indicator_id, purpose product.indicator), تصویر شاخص موبایل (mobile_pic, product.indicator_mobile), گالری تصاویر سایت (gallerySite[], product.gallery), گالری تصاویر موبایل (galleryMobile[], product.gallery_mobile) (:35-82). Basic-info card: کد محصول, نام محصول (the only validated field → لطفا مقدار را وارد کنید, :412), نوع نمایش (1/2/3), دسته بندی, برند, سینی ها (multiple), شرح محصول (سایت) textarea, نمایش محصول switch (:84-148). Lookups on mount: GET brand, POST category, GET admin/products/collections (:452-456); category titles get legacy's (${type_str}) suffix (:460-463). Fields with no schema column are deliberately not rendered: delivery_type, message_long_delivery, offer_day, has_warranty, is_exist, tags, and the variant colour/rial-wage fields (:10-13); legacy's per-gallery-image imageBody caption and the 5th "پادکست" audio slot are also dropped (:27-30).


11. Env / config

11.1 import.meta.env usages (only 4 runtime reads)

Key Use Line
VITE_API_BASE_URL axios baseURL, falls back to '/api/' src/api/client.ts:11
BASE_URL 401 redirect target ${BASE_URL}auth/login src/api/client.ts:77
BASE_URL createWebHistory(...) src/router/index.ts:10
MODE isBranch() → MODE === 'branch' src/utils/index.ts:13

No other VITE_* variable is read anywhere in src.

11.2 vite.config.ts (70 lines)

defineConfig(({ mode }) => ...); const isBranch = mode === 'branch' (:10).

Setting Default mode branch mode Line
base '/' '/branch/' :25
build.outDir 'dist' 'branchDist' :51
build.emptyOutDir true true :52
build.sourcemap false false :53
  • envDir points at the repo root (../..), so the app has no .env of its own (:19, :12-13).
  • loadEnv(mode, <repo root>, '') reads ADMIN_PANEL_PORT (default 5180) and API_PORT (default 3000) (:14-16). Confirmed in the root .env: API_PORT=3000 (line 24), ADMIN_PANEL_PORT=5180 (line 25).
  • Alias @ → ./src (:27-31).
  • css.preprocessorOptions.less.javascriptEnabled: true (:32-42); ant-design-vue 4 dropped LESS modifyVars, so theming now lives in App.vue's <a-config-provider :theme> (:35-38) — LESS only compiles the app's own farsi.less / utilities.less.
  • server: host '0.0.0.0', port, strictPort: true (fail loudly rather than drift to another port, :58-60), proxy /api → http://localhost:${apiPort} and /uploads → same (needed in dev because uploaded ProductImage.url files are served by the API's static handler; production nginx already proxies them) (:61-67).

branch mode summary — it is a different application variant, not a theme: --mode branch → isBranch() true → different routes (branchRoute/fullPageBranchRoute instead of adminRoute, src/router/index.ts:20,27), different menu (branchMenu, navMenuItems.ts:637-639), different localStorage keys (branch_token/branch_user, src/utils/index.ts:18-19; branch_cart_items, src/stores/cart.ts:16; branch_invoice_items, src/stores/invoiceCart.ts:17), a different login page (views/branch/login.vue), different base path and output dir. ProductList.vue:32 additionally hides all row actions in branch mode.

Other config files: tsconfig.json, Dockerfile (COPY dist /usr/share/nginx/html), nginx.conf (proxies /api and /uploads), .dockerignore, .gitignore, public/.