feat: complete admin panel and backend admin api migration

This commit is contained in:
s.esmaeili
2026-09-13 21:02:25 +03:30
parent 03119cd655
commit 267fc7b7d1
239 changed files with 25431 additions and 935 deletions
@@ -0,0 +1,98 @@
<template>
<!--
Ported from legacy henza_admin/src/views/user/ListUser.vue — the admin user
grid. The `ut` column renders the access-level TITLE looked up from the
user-type list, as legacy did.
-->
<div id="page-user-list">
<a-card title="لیست مدیران">
<!--
legacy ListUser.vue:4 — the add-user button, gated on the same rid.
It was dropped in the original port, which left the create route
(/user/create) reachable only by typing the URL.
-->
<a-button
v-if="hasPermission('user.list.create')"
type="primary"
class="mb-5"
@click="router.push('/user/create')"
>
افزودن کاربر
</a-button>
<XTable ref="tbl" :model="model">
<template #ut="{ text }">
{{ userTypeTitle(text) }}
</template>
<template #op="{ record }">
<a-tooltip v-if="hasPermission('user.list.edit')" title="ویرایش">
<EditOutlined class="cursor-pointer mx-1 text-primary" @click="edit(record)" />
</a-tooltip>
<a-tooltip v-if="hasPermission('user.list.remove')" title="حذف">
<a-popconfirm
placement="top"
ok-text="بله"
cancel-text="خیر"
@confirm="confirmDelete(record.id)"
>
<template #title><p>آیا مطمئن به حذف کاربر مورد نظر هستید ؟</p></template>
<DeleteOutlined class="cursor-pointer mx-1 text-danger" />
</a-popconfirm>
</a-tooltip>
</template>
</XTable>
</a-card>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { message, Modal } from 'ant-design-vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import XTable from '../../components/x-table/XTable.vue';
import { buildUserListModel } from './userTbl';
import { useUsersStore } from '../../stores/users';
import api from '../../api/client';
const router = useRouter();
const users = useUsersStore();
const hasPermission = (rid: string) => Boolean(users.hasPermission(rid));
const model = buildUserListModel();
const tbl = ref<{ fetch: () => Promise<void> } | null>(null);
const userTypes = ref<{ id: number; title: string }[]>([]);
function userTypeTitle(ut: number | null | undefined) {
if (ut === null || ut === undefined) return '-';
return userTypes.value.find((t) => t.id === Number(ut))?.title ?? String(ut);
}
function edit(record: Record<string, any>) {
router.push(`/user/edit/${record.id}`);
}
async function confirmDelete(id: string) {
try {
await api.delete(`users/${id}`);
message.success('کاربر حذف شد');
tbl.value?.fetch();
} catch (e: any) {
// The backend refuses when the admin owns ledger entries or status events —
// deleting them would erase the audit trail.
Modal.error({ title: e?.response?.data?.message ?? 'خطا در حذف کاربر', okText: 'بستن' });
}
}
onMounted(async () => {
try {
const { data } = await api.get('const/__list__ut');
userTypes.value = data;
} catch (e) {
console.log(e);
}
});
</script>
@@ -0,0 +1,218 @@
<template>
<!--
Ported from legacy henza_admin/src/views/user/Permission.vue — the screen
that administers the permission(rid, userTypeId) matrix built in Phase 1.
Legacy's magic ids, preserved:
84 = the "new access level" sentinel — picking it reveals a name field
85 = the default selection on load
104 = a reserved level; unless the signed-in admin belongs to it, both the
level and the whole "custom" branch of the tree are hidden
The RTL checkbox CSS from legacy is carried over verbatim — ant's tick mark
is drawn with a rotated border that lands wrong under [dir=rtl].
-->
<a-form layout="vertical" @submit.prevent="handleSubmit">
<a-card id="rid" title="دسترسی ها">
<div class="grid-2">
<a-form-item
label="سطح کاربر"
:validate-status="err.ut ? 'error' : ''"
:help="err.ut"
>
<a-select v-model:value="ut" allow-clear @change="selectChange">
<a-select-option v-for="t in visibleUserTypes" :key="t.id" :value="String(t.id)">
{{ t.title }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
v-if="newUT && hasPermission('user.permission.create')"
label="نام سطح دسترسی"
:validate-status="err.user ? 'error' : ''"
:help="err.user"
>
<a-input v-model:value="newTypeName" allow-clear />
</a-form-item>
<a-form-item label="نقش ها" class="tree-item">
<a-tree
v-model:checked-keys="rid"
:check-strictly="false"
checkable
style="width: 100%"
:tree-data="visibleTree"
:field-names="{ title: 'title', key: 'key', children: 'children' }"
:auto-expand-parent="autoExpandParent"
/>
</a-form-item>
</div>
<a-button
v-if="!newUT && hasPermission('user.permission.edit')"
type="primary"
html-type="submit"
>
ثبت تغییرات
</a-button>
<a-button
v-if="newUT && hasPermission('user.permission.create')"
type="primary"
html-type="submit"
>
افزودن
</a-button>
</a-card>
</a-form>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
import { treeData, type PermissionNode } from './permissionConst';
import { useUsersStore } from '../../stores/users';
import { useAuthStore } from '../../stores/auth';
import api from '../../api/client';
const users = useUsersStore();
const auth = useAuthStore();
const hasPermission = (rid: string) => Boolean(users.hasPermission(rid));
/** legacy sentinels */
const NEW_TYPE_ID = '84';
const DEFAULT_TYPE_ID = '85';
const RESERVED_TYPE_ID = 104;
const ut = ref<string | undefined>(DEFAULT_TYPE_ID);
const newTypeName = ref('');
const rid = ref<string[]>([]);
const autoExpandParent = ref(true);
const userTypes = ref<{ id: number; title: string }[]>([]);
const err = reactive<Record<string, string>>({});
const newUT = computed(() => ut.value === NEW_TYPE_ID);
/** legacy: hide the "new" option without the create rid, and hide 104 unless you are in it */
const visibleUserTypes = computed(() => {
let list = userTypes.value;
if (!hasPermission('user.permission.create')) {
list = list.filter((t) => String(t.id) !== NEW_TYPE_ID);
}
if (auth.user?.ut !== RESERVED_TYPE_ID) {
list = list.filter((t) => t.id !== RESERVED_TYPE_ID);
}
return list;
});
/** legacy drops the whole "custom" branch for anyone outside level 104 */
const visibleTree = computed<PermissionNode[]>(() =>
auth.user?.ut === RESERVED_TYPE_ID
? treeData
: treeData.filter((x) => x.key !== 'custom'),
);
async function getData() {
try {
const { data } = await api.get('const/__list__ut');
userTypes.value = data;
} catch (e) {
console.log(e);
}
}
/** legacy selectChange(): id 84 means "creating a new level", so start empty */
async function selectChange(id: string | undefined) {
if (!id) {
rid.value = [];
return;
}
if (id === NEW_TYPE_ID) {
rid.value = [];
return;
}
try {
const { data } = await api.get(`roles/${id}`);
// The server returns a flat array of rid strings.
rid.value = data;
} catch (e) {
message.error('خطا در دریافت اطلاعات');
}
}
async function handleSubmit() {
Object.keys(err).forEach((k) => delete err[k]);
if (!ut.value) {
err.ut = 'لطفاسطح دسترسی را انتخاب کنید';
return;
}
if (newUT.value && !newTypeName.value) {
err.user = 'لطفا نام سطح دسترسی را وارد کنید';
return;
}
try {
let targetUt = ut.value;
if (newUT.value) {
// legacy: create the level first, then grant the ticked rids to it
const { data } = await api.post('const', {
key: '__list__ut',
val: newTypeName.value,
});
targetUt = String(data.id);
}
await api.post('roles/create', { ut: targetUt, roles: rid.value });
await getData();
if (newUT.value) {
ut.value = targetUt;
newTypeName.value = '';
await selectChange(targetUt);
}
message.success('عملیات با موفقیت انجام شد');
} catch (e: any) {
message.error(e?.response?.data?.message ?? 'خطا در ثبت اطلاعات');
}
}
onMounted(async () => {
await getData();
// legacy: setFieldsValue({ut:"85"}) then selectChange("85")
ut.value = DEFAULT_TYPE_ID;
await selectChange(DEFAULT_TYPE_ID);
});
</script>
<style>
/*
Carried over verbatim from legacy. Ant draws the checkbox tick with a rotated
border, which lands on the wrong edge under [dir=rtl].
*/
[dir='rtl'] .ant-tree-checkbox-inner::after {
right: 55%;
top: 35%;
border-left: 0 !important;
-webkit-transform: rotate(-45deg) scale(0) translate(50%, -50%);
transform: rotate(-45deg) scale(0) translate(50%, -50%);
}
[dir='rtl'] .ant-tree-checkbox-checked .ant-tree-checkbox-inner::after {
border-left: 0 !important;
border-right: 2px solid #fff;
-webkit-transform: rotate(45deg) scale(1) translate(0%, -60%);
transform: rotate(45deg) scale(1) translate(0%, -60%);
}
</style>
<style scoped>
.grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
}
.tree-item {
grid-column: 1 / -1;
}
</style>
@@ -0,0 +1,171 @@
<template>
<!--
Ported from legacy henza_admin/src/views/user/UserCreate.vue.
Serves BOTH create and edit — UserEdit.vue is only a wrapper around it.
Legacy fields: fname, lname, mobile, ut, password, password_confirmation.
The schema stores one `fullName`, so the two name inputs are joined on submit
and split back on load.
-->
<a-spin :spinning="loading">
<a-form layout="vertical" @submit.prevent="handleSubmit">
<a-card :title="id ? 'ویرایش مدیر' : 'افزودن مدیر'" class="mb-base">
<div class="grid-3">
<a-form-item label="نام" :validate-status="err.fname ? 'error' : ''" :help="err.fname">
<a-input v-model:value="form.fname" />
</a-form-item>
<a-form-item
label="نام خانوادگی"
:validate-status="err.lname ? 'error' : ''"
:help="err.lname"
>
<a-input v-model:value="form.lname" />
</a-form-item>
<a-form-item
label="شماره موبایل"
:validate-status="err.mobile ? 'error' : ''"
:help="err.mobile"
>
<a-input v-model:value="form.mobile" />
</a-form-item>
<a-form-item label="نوع کاربری" :validate-status="err.ut ? 'error' : ''" :help="err.ut">
<a-select v-model:value="form.ut" allow-clear placeholder="انتخاب کنید">
<a-select-option v-for="t in userTypes" :key="t.id" :value="t.id">
{{ t.title }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="رمز عبور"
:validate-status="err.password ? 'error' : ''"
:help="err.password"
>
<a-input-password v-model:value="form.password" />
</a-form-item>
<a-form-item
label="تأیید رمز عبور"
:validate-status="err.password_confirmation ? 'error' : ''"
:help="err.password_confirmation"
>
<a-input-password v-model:value="form.password_confirmation" />
</a-form-item>
</div>
<p v-if="id" class="hint">برای تغییر ندادن رمز عبور، هر دو فیلد رمز را خالی بگذارید.</p>
<a-button type="primary" @click="handleSubmit">ثبت اطلاعات</a-button>
</a-card>
</a-form>
</a-spin>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import api from '../../api/client';
const route = useRoute();
const router = useRouter();
const id = route.params.id as string | undefined;
const loading = ref(false);
const userTypes = ref<{ id: number; title: string }[]>([]);
const form = reactive<Record<string, any>>({
fname: '',
lname: '',
mobile: '',
ut: undefined,
password: '',
password_confirmation: '',
});
const err = reactive<Record<string, string>>({});
const REQUIRED = 'لطفا مقدار را وارد کنید';
function validate(): boolean {
Object.keys(err).forEach((k) => delete err[k]);
if (!form.fname) err.fname = REQUIRED;
if (!form.lname) err.lname = REQUIRED;
if (!form.mobile) err.mobile = REQUIRED;
if (form.ut === undefined || form.ut === null) err.ut = REQUIRED;
// On create a password is mandatory; on edit, blank means "leave unchanged".
if (!id && !form.password) err.password = REQUIRED;
if (form.password && form.password !== form.password_confirmation) {
err.password_confirmation = 'رمز عبور و تأیید آن یکسان نیستند';
}
return Object.keys(err).length === 0;
}
async function handleSubmit() {
if (!validate()) return;
try {
loading.value = true;
const payload: Record<string, unknown> = {
name: `${form.fname} ${form.lname}`.trim(),
mobile: form.mobile,
ut: form.ut,
};
if (form.password) payload.password = form.password;
if (id) {
await api.put(`users/${id}`, payload);
} else {
await api.post('users', payload);
}
message.info('اطلاعات با موفقیت به ثبت رسید.');
router.push('/user/list');
} catch (e: any) {
message.error(e?.response?.data?.message ?? 'خطا در ثبت اطلاعات.');
} finally {
loading.value = false;
}
}
onMounted(async () => {
try {
const { data } = await api.get('const/__list__ut');
userTypes.value = data;
} catch (e) {
console.log(e);
}
if (id) {
try {
const { data } = await api.get(`admin/users/${id}`);
// one fullName -> two inputs: first token is the given name
const parts = String(data.name ?? '').trim().split(' ');
Object.assign(form, {
fname: parts[0] ?? '',
lname: parts.slice(1).join(' '),
mobile: data.mobile ?? '',
ut: data.ut ?? undefined,
password: '',
password_confirmation: '',
});
} catch (e) {
console.log(e);
}
}
});
</script>
<style scoped>
.grid-3 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}
.hint {
color: #888;
font-size: 12px;
margin-bottom: 1rem;
}
</style>
@@ -0,0 +1,12 @@
<template>
<!--
Ported from legacy henza_admin/src/views/user/UserEdit.vue — a 14-line
wrapper whose entire template is <User/>. Kept as a wrapper; UserCreate
reads the :id route param to switch between create and edit.
-->
<UserCreate />
</template>
<script setup lang="ts">
import UserCreate from './UserCreate.vue';
</script>
@@ -0,0 +1,44 @@
<template>
<!--
Ported from legacy henza_admin/src/views/user/myData.vue.
LEGACY BUG preserved in effect, not in code: legacy reads
`user.user_type.val`, but the users/show payload carries the title under
`title` (and `config_static` rows used `val` only in the raw lookup table),
so that cell fell through to its 'آنلاین' fallback for everyone. Here the
real title is shown when present, with the same fallback — the fallback is
what legacy actually displayed.
-->
<a-card title="اطلاعات کاربری">
<a-descriptions>
<a-descriptions-item label="نام">{{ user?.fname || '-' }}</a-descriptions-item>
<a-descriptions-item label="نام خانوادگی">{{ user?.lname || '-' }}</a-descriptions-item>
<a-descriptions-item label="شماره تلفن">{{ user?.mobile || '-' }}</a-descriptions-item>
<a-descriptions-item label="نوع کاربری">
{{ userTypeTitle || 'آنلاین' }}
</a-descriptions-item>
</a-descriptions>
</a-card>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useAuthStore } from '../../stores/auth';
import api from '../../api/client';
const auth = useAuthStore();
const fresh = ref<Record<string, any> | null>(null);
const user = computed(() => fresh.value ?? auth.user);
const userTypeTitle = computed(() => user.value?.user_type?.title);
onMounted(async () => {
try {
const { data } = await api.get('admin/users/me');
fresh.value = data;
} catch (e) {
// fall back to the cached login payload, as legacy did
console.log(e);
}
});
</script>
@@ -0,0 +1,872 @@
// Ported from legacy henza_admin/src/views/user/permissionConst.js
//
// Converted MECHANICALLY (see scratchpad/convert_tree.js) so all 181 Farsi
// permission labels survive byte-exact. 22 top-level groups, up to 4 levels deep.
//
// This tree is the vocabulary the Permission screen offers. Note it is NOT the
// same set as the rids the app enforces — see the audit in
// docs/ADMIN-PORT-PLAN.md:
// - 144 rids are actually enforced (58 by routes, 100 by $hasPermission, overlapping)
// - 176 distinct keys appear in this tree
// - 36 tree keys are never checked anywhere (dead entries, kept for fidelity)
// - 4 enforced rids are MISSING from this tree, so they cannot be granted
// from the UI at all: buy.order.list, newsletters,
// persons.create.customer.tak, persons.list.sellers.view
// All four quirks are preserved rather than "fixed".
export interface PermissionNode {
title: string;
key: string;
children?: PermissionNode[];
}
export const treeData: PermissionNode[] = [
{
"title": "پیشخوان",
"key": "dashboard",
"children": [
{
"title": "آمار عمده فروشی",
"key": "dashboard.omde"
},
{
"title": "آمار تک فروشی",
"key": "dashboard.tak"
}
]
},
{
"title": "اشخاص",
"key": "persons",
"children": [
{
"title": "اضافه کردن شخص",
"key": "persons.create",
"children": [
{
"title": "افزودن کارشناس فروش",
"key": "persons.create.seller"
},
{
"title": "افزودن مشتری عمده",
"key": "persons.create.client"
},
{
"title": "افزودن مشتری تک",
"key": "persons.create.single"
},
]
},
{
"title": "لیست کارشناسان فروش",
"key": "persons.list.sellers",
"children": [
{
"title": "ویرایش اطلاعات کارشناس فروش",
"key": "persons.list.sellers.edit"
},
{
"title": "مشاهده سفارشات کارشناس فروش",
"key": "persons.list.sellers.view.orders"
},
{
"title": "مشاهده اسناد مالی کارشناس فروش",
"key": "persons.list.sellers.view.documents"
},
{
"title": "توقف سفارش کارشناس فروش",
"key": "persons.list.sellers.unable.order"
},
{
"title": "فعال سازی سفارش کارشناس فروش",
"key": "persons.list.sellers.able.order"
},
{
"title": "فعال سازی حساب کاربری کارشناس فروش",
"key": "persons.list.sellers.able"
},
{
"title": "غیر فعال سازی حساب کاربری کارشناس فروش",
"key": "persons.list.sellers.unable"
}
]
},
{
"title": "لیست مشتریان عمده",
"key": "persons.list.client.omde",
"children": [
{
"title": "مشاهده اطلاعات مشتریان عمده",
"key": "persons.list.client.omde.view"
},
{
"title": "ویرایش اطلاعات مشتری عمده",
"key": "persons.list.client.omde.edit"
},
{
"title": "مشاهده سفارشات مشتری عمده",
"key": "persons.list.client.omde.view.orders"
},
{
"title": "مشاهده اسناد مالی مشتری عمده",
"key": "persons.list.client.omde.view.documents"
},
{
"title": "توقف سفارش مشتری عمده",
"key": "persons.list.client.omde.unable.order"
},
{
"title": "فعال سازی سفارش مشتری عمده",
"key": "persons.list.client.omde.able.order"
},
{
"title": "فعال سازی مشتری عمده",
"key": "persons.list.client.omde.able"
},
{
"title": "غیر فعال سازی مشتری عمده",
"key": "persons.list.client.omde.unable"
}
]
},
{
"title": "لیست مشتریان تک",
"key": "persons.list.client.single",
"children": [
{
"title": "مشاهده اطلاعات مشتریان تک",
"key": "persons.list.client.single.view"
},
{
"title": "ویرایش اطلاعات مشتری تک",
"key": "persons.list.client.single.edit"
},
{
"title": "مشاهده اطلاعات مشتری تک",
"key": "persons.list.client.single.view"
},
]
}
]
},
{
"title": "شعب",
"key": "branch",
"children":
[
{
"title": "افزودن شعب",
"key": "branch.create"
},
{
"title": " لیست شعب",
"key": "branch.list",
}
]
},
{
"title": "کارگاه",
"key": "workshop",
"children":
[
{
"title": "افزودن کارگاه",
"key": "workshop.create"
},
{
"title": " لیست کارگاه ها",
"key": "workshop.list",
}
]
},
{
"title": "محصولات",
"key": "product",
"children":
[
{
"title": "افزودن محصول",
"key": "product.create"
},
{
"title": "لیست محصولات",
"key": "product.list",
"children":
[
{
"title": "مشاهده لیست محصولات",
"key": "product.list.view"
},
{
"title": "ویرایش اطلاعات محصول",
"key": "product.list.edit"
},
{
"title": "مشاهده اطلاعات جامع محصول",
"key": "product.list.view"
},
{
"title": "تنوع های محصول",
"key": "product.list.variants",
"children":
[
{
"title": "مشاهده تنوع های محصول",
"key": "product.list.variants.view",
},
{
"title": "ویرایش تنوع محصول",
"key": "product.list.variants.edit",
},
{
"title": "حذف تنوع محصول",
"key": "product.list.variants.remove",
},
{
"title": "افزودن تنوع محصول",
"key": "product.list.variants.add",
}
]
},
{
"title": "حذف محصول",
"key": "product.list.remove"
},
{
"title": "تغییر دسته ای",
"key": "product.list.group.edit"
}
]
},
{
"title": "تغییر درصد محصولات",
"key": "product.percent"
}
]
},
{
"title": "سفارشات",
"key": "order",
"children":
[
{
"title": "لیست سفارشات",
"key": "order.list",
"children":
[
{
"title": "مشاهده لیست سفارشات",
"key": "order.list.view"
},
{
"title": "وضعیت سفارش",
"key": "order.list.sts",
"children":
[
{
"title": "مشاهده وضعیت سفارش",
"key": "order.list.sts.view",
},
{
"title": "حذف وضعیت",
"key": "order.list.sts.remove",
},
{
"title": "ثبت وضعیت",
"key": "order.list.sts.submit",
}
]
},
{
"title": "مشاهده سفارش",
"key": "order.list.view"
},
{
"title": "ویرایش سفارش",
"key": "order.list.view.edit",
},
{
"title": "دریافت pdf",
"key": "order.list.pdf",
}
]
},
{
"title": "سفارشات عمده",
"key": "order.omde",
"children":
[
{
"title": "وضعیت سفارش",
"key": "order.omde.state",
"children":
[
{
"title": "مشاهده وضعیت سفارش",
"key": "order.omde.state.view"
},
{
"title": "ثبت وضعیت سفارش",
"key": "order.omde.state.submit"
},
{
"title": "حذف وضعیت سفارش",
"key": "order.omde.state.remove"
}
]
},
{
"title": "مشاهده سفارش",
"key": "order.omde.view",
},
{
"title": "ویرایش سفارش",
"key": "order.omde.view.edit"
},
{
"title": "حذف آیتم از سفارش",
"key": "order.omde.view.remove"
},
{
"title": "حذف سفارش",
"key": "order.omde.remove",
},
{
"title": "دریافت pdf سفارش",
"key": "order.omde.get",
}
]
},
{
"title": "سفارشات ساختنی",
"key": "order.makeOrder",
"children":
[
{
"title": "وضعیت سفارش",
"key": "order.makeOrder.state",
"children":
[
{
"title": "مشاهده وضعیت سفارش",
"key": "order.makeOrder.state.view"
},
{
"title": "ثبت وضعیت سفارش",
"key": "order.makeOrder.state.submit"
},
{
"title": "حذف وضعیت سفارش",
"key": "order.makeOrder.state.remove"
}
]
},
{
"title": "مشاهده سفارش",
"key": "order.makeOrder.view",
},
{
"title": "ویرایش سفارش",
"key": "order.makeOrder.view.edit"
},
{
"title": "حذف آیتم از سفارش",
"key": "order.makeOrder.view.remove"
},
{
"title": "حذف سفارش",
"key": "order.makeOrder.remove",
},
{
"title": "دریافت pdf سفارش",
"key": "order.makeOrder.get",
}
]
},
{
"title": "لیست تامین",
"key": "order.tamin",
"children":
[
{
"title": "مشاهده لیست تامین",
"key": "order.tamin.view"
},
{
"title": "وضعیت سفارش",
"key": "order.tamin.state",
"children":
[
{
"title": "مشاهده وضعیت سفارش",
"key": "order.tamin.state.view"
},
{
"title": "ثبت وضعیت سفارش",
"key": "order.tamin.state.submit"
},
{
"title": "حذف وضعیت سفارش",
"key": "order.tamin.state.remove"
}
]
},
{
"title": "مشاهده سفارش",
"key": "order.tamin.view"
},
{
"title": "ویرایش سفارش",
"key": "order.tamin.view.edit"
}
]
},
{
"title": "ثبت سفارش تک",
"key": "order.create",
}
]
},
{
"title": "مدیران",
"key": "user",
"children":
[
{
"title": "افزودن مدیر",
"key": "user.create"
},
{
"title": "لیست مدیران",
"key": "user.list",
"children":
[
{
"title": "مشاهده لیست مدیران",
"key": "user.list.view"
},
{
"title": "ویرایش مدیر",
"key": "user.list.edit"
},
{
"title": "حذف مدیر",
"key": "user.list.remove"
},
{
"title": "افزودن مدیر",
"key": "user.list.create"
}
]
},
{
"title": "دسترسی ها",
"key": "user.permission",
"children":
[
{
"title": "ویرایش دسترسی",
"key": "user.permission.edit"
},
{
"title": "افزودن دسترسی",
"key": "user.permission.create"
}
]
}
]
},
{
"title": "دسته بندی ها",
"key": "collection",
"children":
[
{
"title": "افزودن دسته بندی",
"key": "collection.create"
},
{
"title": " لیست دسته بندی ها",
"key": "collection.list",
"children":
[
{
"title": "مشاهده لیست دسته بندی ها",
"key": "collection.list.view"
},
{
"title": "ویرایش دسته",
"key": "collection.list.edit"
},
{
"title": "حذف دسته",
"key": "collection.list.remove"
}
]
}
]
},
{
"title": "اسناد مالی",
"key": "document",
"children":
[
{
"title": " لیست اسناد مالی",
"key": "document.list",
"children":
[
{
"title": "مشاهده لیست اسناد مالی",
"key": "document.list.view"
},
{
"title": "ویرایش سند مالی",
"key": "document.list.edit"
},
{
"title": "حذف سند مالی",
"key": "document.list.remove"
},
{
"title": "رد کردن سند مالی",
"key": "document.list.deny"
},
{
"title": "تأیید کردن سند مالی",
"key": "document.list.confirm"
},
{
"title": "دریافت pdf سند مالی",
"key": "document.list.get",
}
]
},
{
"title": "وارد کردن سند مالی مشتری",
"key": "document.create"
}
]
},
{
"title": "استثناعات تخفیف",
"key": "offersIgnore",
},
{
"title": "تخفیفات",
"key": "offers",
"children":
[
{
"title": " لیست تخفیفات",
"key": "offers.list",
"children":
[
{
"title": "مشاهده لیست تخفیفات",
"key": "offers.list.view"
},
{
"title": "ویرایش تخفیف عمده",
"key": "offers.list.edit"
},
{
"title": "ویرایش تخفیف تک",
"key": "offers.list.tak.edit"
},
{
"title": "حذف تخفیف عمده",
"key": "offers.list.remove"
},
{
"title": "حذف تخفیف تک",
"key": "offers.list.tak.remove"
}
]
},
{
"title": "تعریف تخفیف عمده",
"key": "offers.create"
},
{
"title": "تعریف تخفیف تک",
"key": "offers.create.tak"
},
{
"title": "تخفیف اتوماتیک",
"key": "offers.autopromotion"
}
]
},
{
"title": "فاکتور انتقال",
"key": "invoice",
"children":
[
{
"title": "افزودن فاکتور",
"key": "invoice.create"
},
{
"title": " لیست فاکتورها",
"key": "invoice.list",
}
]
},
{
"title": "گزارشات",
"key": "report",
"children":
[
{
"title": "گزارش تجمیعی سفارشات و اسناد مالی",
"key": "report.order.item"
},
{
"title": "گزارش محصولات",
"key": "report.product"
},
]
},
{
"title": "شهرها",
"key": "cities",
"children":
[
{
"title": "افزودن شهر",
"key": "cities.create"
},
{
"title": "لیست شهرها",
"key": "cities.list",
"children":
[
{
"title": "مشاهده لیست شهرها",
"key": "cities.list.view"
},
{
"title": "حذف شهر",
"key": "cities.list.remove"
},
{
"title": "مشاهده شهر",
"key": "cities.list.view"
},
{
"title": "ویرایش شهر",
"key": "cities.list.view.edit"
}
]
}
]
},
{
"title": "دیدگاه ها",
"key": "comment",
"children": [
{
"title": "مشاهده لیست دیدگاه ها",
"key": "comment.view.all"
},
{
"title": "تغییر وضعیت دیدگاه",
"key": "comment.state"
},
{
"title": "مشاهده دیدگاه",
"key": "comment.view"
},
{
"title": "ویرایش دیدگاه",
"key": "comment.view.edit",
},
{
"title": "حذف دیدگاه",
"key": "comment.remove"
}
]
},
{
"title": "پیامک",
"key": "sms",
"children":
[
{
"title": "ارسال پیامک",
"key": "sms.create",
"children":
[
{
"title": "ارسال برای یک شماره تلفن",
"key": "sms.create.single"
},
{
"title": "ارسال برای همه کاربران",
"key": "sms.create.all"
},
{
"title": "ارسال برای کارشناسان فروش",
"key": "sms.create.reagent"
},
{
"title": "ارسال برای مشتریان عمده",
"key": "sms.create.omde"
},
{
"title": "ارسال برای چند شماره تلفن",
"key": "sms.create.multiple"
}
],
},
{
"title": "لیست پیامک های ارسال شده",
"key": "sms.list",
// "children": [],
},
]
},
{
"title": "کیف پول",
"key": "wallet",
"children":
[
{
"title": "لیست کیف پول های شارژ شده",
"key": "wallet.list",
},
{
"title": "شارژ کیف پول",
"key": "wallet.charge",
},
{
"title": "تغییر درصد شارژ کیف پول",
"key": "wallet.percent",
},
]
},
{
"title": "نوتیفیکیشن",
"key": "notify",
"children":
[
{
"title": "ارسال نوتیفیکیشن",
"key": "notify.create",
"children":
[
{
"title": "ارسال برای کارشناسان فروش",
"key": "notify.create.multiple.reagent"
},
{
"title": "ارسال برای یک مشتری",
"key": "notify.create.single.customer"
},
{
"title": "ارسال برای مشتریان عمده",
"key": "notify.create.multiple.omde"
},
{
"title": "ارسال برای همه مشتریان",
"key": "notify.create.multiple.customer"
},
{
"title": "ارسال برای یک مدیر",
"key": "notify.create.single.admin"
},
{
"title": "ارسال برای همه مدیران",
"key": "notify.create.multiple.admin"
}
],
},
{
"title": "لیست نوتیفیکیشن های دریافتی",
"key": "notify.me",
},
]
},
{
"title": "پیکربندی",
"key": "config",
"children": [
{
"title": "مدیریت ثابت ها",
"key": "config.statics",
"children":
[
{
"title": "برند ها",
"key": "config.statics.brand",
"children":
[
{
"title": "افزودن برند",
"key": "config.statics.brand.create"
},
{
"title": "ویرایش برند",
"key": "config.statics.brand.edit"
},
{
"title": "حذف برند",
"key": "config.statics.brand.remove"
},
]
},
{
"title": "برچسب ها",
"key": "config.statics.tags",
"children":
[
{
"title": "افزودن برچسب",
"key": "config.statics.tags.create"
},
{
"title": "ویرایش برچسب",
"key": "config.statics.tags.edit"
},
{
"title": "حذف برچسب",
"key": "config.statics.tags.remove"
},
]
}
]
},
{
"title": "مدیریت کیف پول",
"key": "config.wallet",
},
{
"title": "گزارش",
"key": "config.report",
},
]
},
{
"title": "آمار",
"key": "statics",
},
{
"title": "خبرنامه",
"key": "newsletters.newsletters",
},
{
"title": "سفارشی",
"key": "custom",
"children": [
{
"title": "اسلایدر موبایل",
"key": "custom.config.statics.slider"
},
{
"title": "برچسب بنر اول سایت",
"key": "custom.config.statics.tags.banner.ids"
},
]
}
]
@@ -0,0 +1,17 @@
// Ported from legacy henza_admin/src/table/userTbl.js
import XTbl, { Xtc } from '../../components/x-table/model';
export function buildUserListModel(): XTbl {
const tbl = new XTbl('admin/users/list', 'لیست مدیران');
tbl.add(new Xtc('id', 'کد مدیر'));
// Legacy stored fname/lname separately; the schema has one fullName, which the
// API returns under BOTH keys so these two columns bind unchanged.
tbl.add(new Xtc('fname', 'نام').filterKey('fullName').filterText());
tbl.add(new Xtc('lname', 'نام خانوادگی').filterKey('fullName').filterText());
tbl.add(new Xtc('mobile', 'موبایل').filterText());
tbl.add(new Xtc('ut', 'نوع کاربر').filterNumber().renderSlot('ut'));
tbl.add(new Xtc('op', 'عملیات').noSort().renderSlot('op'));
return tbl;
}
export default buildUserListModel;