Files
boursecu_admin/src/pages/menuPages/customers/base/index.vue
T
2026-08-01 17:30:37 +03:30

841 lines
30 KiB
Vue

<template>
<div class="h-full min-h-0 min-w-0 overflow-hidden">
<tbl
ref="table"
class="h-full min-h-0 w-full"
@info="info"
@edit="edit"
@add="add"
@chargeWallet="openWalletCharge"
/>
<!-- <BasicMenu :menu="menu" width="13rem" @onChange="tabSelected" />
<component :is="selectedComponent" class="mr-5" />-->
<!-- <BasicShowContext title="پروژه ها"> <Search /> </BasicShowContext>-->
<!-- </BasicMainCard>-->
<BasicActionSheet
v-model:visible="addUserVisible"
:title="`${editId ? 'ویرایش' : 'افزودن'} کاربر`"
modal-width="55rem"
:loading="loading"
:showButton="true"
:showCancel="true"
:showOk="true"
ok-text="ثبت"
cancel-text="انصراف"
@ok="ok"
@close="addUserVisible = false"
>
<div v-if="isMobile" class="mb-3 text-base font-700">{{ `${editId ? 'ویرایش' : 'افزودن'} کاربر` }}</div>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="grid gap-4 md:grid-cols-3">
<a-form-item name="fname" :rules="inputRules()">
<template #label>
<DashboardLabelWithHelp label="نام" :help="fnameHelp" size="sm" />
</template>
<BasicInput v-model:value="form.fname" />
</a-form-item>
<a-form-item name="lname" :rules="inputRules()">
<template #label>
<DashboardLabelWithHelp label="نام خانوادگی" :help="lnameHelp" size="sm" />
</template>
<BasicInput v-model:value="form.lname" />
</a-form-item>
<a-form-item name="mobile" :rules="inputLenRules(11)">
<template #label>
<DashboardLabelWithHelp label="شماره همراه" :help="mobileHelp" size="sm" />
</template>
<BasicInput v-model:value="form.mobile" type="number" />
</a-form-item>
<a-form-item name="password">
<template #label>
<DashboardLabelWithHelp label="پسورد" :help="passwordHelp" size="sm" />
</template>
<BasicInput is-password v-model:value="form.password" />
</a-form-item>
<a-form-item name="birth_date">
<template #label>
<DashboardLabelWithHelp label="تاریخ تولد" :help="birthDateHelp" size="sm" />
</template>
<BaseDatePicker v-model:value="form.birth_date" />
</a-form-item>
<a-form-item name="phone_number">
<template #label>
<DashboardLabelWithHelp label="شماره تلفن" :help="phoneNumberHelp" size="sm" />
</template>
<BasicInput v-model:value="form.phone_number" type="number" />
</a-form-item>
<a-form-item name="province">
<template #label>
<DashboardLabelWithHelp label="نام استان" :help="provinceHelp" size="sm" />
</template>
<BasicInput v-model:value="form.province" />
</a-form-item>
<a-form-item name="city">
<template #label>
<DashboardLabelWithHelp label="نام شهر" :help="cityHelp" size="sm" />
</template>
<BasicInput v-model:value="form.city" />
</a-form-item>
<a-form-item name="n_code">
<template #label>
<DashboardLabelWithHelp label="شماره ملی" :help="nCodeHelp" size="sm" />
</template>
<BasicInput v-model:value="form.n_code" />
</a-form-item>
<a-form-item name="add_user_limit">
<template #label>
<DashboardLabelWithHelp label="حداکثر معرفی" :help="addUserLimitHelp" size="sm" />
</template>
<BasicInput v-model:value="form.add_user_limit" />
</a-form-item>
<a-form-item name="referral_to_id">
<template #label>
<DashboardLabelWithHelp label="کد معرف" :help="referralToIdHelp" size="sm" />
</template>
<BasicInput v-model:value="form.referral_to_id" :disabled="editId" />
</a-form-item>
<!-- <a-form-item label="نام چاپی" name="print_name" :rules="inputRules()">
<BasicInput v-model:value="form.print_name" />
</a-form-item>-->
</div>
</a-form>
</BasicActionSheet>
<BasicActionSheet
v-model:visible="userInfoVisible"
title="اطلاعات کاربر"
modal-width="100vw"
:loading="loading"
:showButton="false"
@close="userInfoVisible = false"
>
<a-tabs v-if="userInfo" v-model:activeKey="activeKey">
<a-tab-pane key="اطلاعات جامع" tab="اطلاعات جامع">
<div
class="grid min-w-0 grid-cols-1 items-start gap-3 lg:grid-cols-[minmax(17rem,21rem)_minmax(0,1fr)] xl:grid-cols-[minmax(18rem,23rem)_minmax(0,1fr)]"
>
<div class="min-w-0 space-y-2 lg:order-2">
<div class="admin-stat-muted rounded-lg w-full flex flex-col gap-3 p-3 md:flex-row md:justify-between">
<div>
<div class="font-bold">{{ userInfo.name }}</div>
<CustomerPresenceBadge :last-seen-at="userInfo?.last_seen_at" />
<div class="mt-1">{{ userInfo?.mobile }}</div>
</div>
<div class="flex flex-col justify-center items-center pl-3">
<div class="flex flex-col justify-center items-center">
<Icon
:icon="
userInfo.is_legal === 0
? 'vuesax-linear:close-circle'
: 'vuesax-linear:verify'
"
:key="userInfo.is_legal"
:color="userInfo.is_legal === 0 ? '#ff0000' : '#008000'"
/>
<p
class="text-[11px] mt-2"
:class="userInfo.is_legal === 0 ? 'reject' : 'success'"
>
{{ userStatus(userInfo.is_legal).title }}
</p>
</div>
<div>{{ toJalaliNoTime(userInfo?.created_at) }}</div>
<div class="mt-2">
<CustomTag
:textColor="userInfo?.active ? '#25855A' : '#C53030'"
:bgColor="userInfo?.active ? '#E3F4E9' : '#FECACA'"
class="font-bold px-6"
>
{{ userInfo?.active ? 'فعال' : 'غیرفعال' }}
</CustomTag>
</div>
</div>
</div>
<div class="grid min-w-0 grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
<BasicInfoItem title="کد کاربری" :value="userInfo?.id" />
<BasicInfoItem
v-if="accountingCodeVisible"
title="کد حسابداری"
:value="accountingCodeValue"
/>
<BasicInfoItem
title="تاریخ تولد"
:value="userInfo?.birth_date ? toJalaliNoTime(userInfo?.birth_date) : '_'"
/>
<BasicInfoItem title="شماره ملی" :value="userInfo?.n_code ?? '_'" />
<BasicInfoItem title="نام استان" :value="userInfo?.province ?? '_'" />
<BasicInfoItem title="نام شهر" :value="userInfo?.city ?? '_'" />
<BasicInfoItem title="کد معرف" :value="userInfo?.referral_id" />
<BasicInfoItem
title="کد معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.id : '_'"
/>
<BasicInfoItem
title="نام معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.name : '_'"
/>
<BasicInfoItem
title="حداکثر معرفی"
:value="userInfo?.add_user_limit ? `${userInfo?.add_user_limit} نفر` : '_'"
/>
<BasicInfoItem title="وضعیت سفارش دهی" class="col-span-2 md:col-span-1">
<CustomTag
:textColor="userInfo?.trade_active === 1 ? '#25855A' : '#C53030'"
:bgColor="userInfo?.trade_active === 1 ? '#E3F4E9' : '#FECACA'"
class="font-bold px-6"
>
{{ userInfo?.trade_active === 1 ? 'مجاز' : 'غیر مجاز' }}
</CustomTag>
</BasicInfoItem>
</div>
<div v-if="userInfo?.id && isMobile" class="mt-2 space-y-2">
<PargarWalletSyncAction
v-if="pargarWalletSyncVisible"
:loading="pargarSyncLoading"
:can-sync="pargarWalletCanSync"
@sync="syncPargarWallet"
/>
<CustomerWalletBalancePanel
:key="`wallet-mobile-${walletPanelKey}`"
ref="walletPanelMobile"
:customer-id="userInfo.id"
compact
:charge-targets="infoChargeTargets"
:interactive="false"
/>
</div>
</div>
<aside
v-if="userInfo?.id && !isMobile"
class="surface-muted min-w-0 rounded-xl border border-base p-2.5 lg:order-1 lg:sticky lg:top-0"
>
<PargarWalletSyncAction
v-if="pargarWalletSyncVisible"
:loading="pargarSyncLoading"
:can-sync="pargarWalletCanSync"
@sync="syncPargarWallet"
/>
<CustomerWalletBalancePanel
:key="`wallet-desktop-${walletPanelKey}`"
ref="walletPanelDesktop"
:customer-id="userInfo.id"
layout="sidebar"
:interactive="false"
/>
</aside>
</div>
</a-tab-pane>
<a-tab-pane key="لیست سفارشات" tab="لیست سفارشات">
<orders-tbl ref="orderTable" :customerId="userInfo?.id" @info="infoOrder" />
</a-tab-pane>
<a-tab-pane key="لیست تراکنش ها" tab="لیست تراکنش ها">
<transactions-tbl ref="transactionTable" :customerId="userInfo?.id" />
</a-tab-pane>
<a-tab-pane key="پیامک ها" tab="پیامک ها">
<sms-tbl ref="smsTable" :mobile="userInfo?.mobile" />
</a-tab-pane>
<a-tab-pane key="معرفی کرده ها" tab="معرفی کرده ها">
<ReferralsTable ref="referralTable" :customerId="userInfo?.id" />
</a-tab-pane>
<a-tab-pane key="دارایی ها" tab="دارایی ها">
<FundsTable ref="fundTable" :customerId="userInfo?.id" />
</a-tab-pane>
<a-tab-pane key="قلک ها" tab="قلک ها">
<PiggyFundsTable ref="piggyFundTable" :customerId="userInfo?.id" />
</a-tab-pane>
<a-tab-pane v-if="pargarWalletSyncVisible" key="لاگ پرگار" tab="لاگ پرگار">
<CustomerPargarLogsTable ref="pargarLogsTable" :customer-id="userInfo?.id" />
</a-tab-pane>
<a-tab-pane
v-if="conditionalOrderEnabled"
key="سفارش شرطی"
tab="سفارش شرطی"
>
<CustomerConditionalOrderTab
v-if="userInfo?.id"
:customer-id="userInfo.id"
:conditional-order-active="userInfo.conditional_order_active"
:conditional-order-limit="userInfo.conditional_order_limit"
@updated="onConditionalOrderUpdated"
/>
</a-tab-pane>
</a-tabs>
</BasicActionSheet>
<WalletChargeModal
v-model:visible="showWalletCharge"
:customer-id="chargeCustomerId"
@success="onWalletChargeSuccess"
/>
<BasicInfoModal
v-model:visible="userInfoOrderVisible"
@close="userInfoOrderVisible = false"
:title="infoParams.title"
:date="infoParams.date"
:time="infoParams.time"
:type="infoParams.type"
:name="infoParams.name"
:items="infoParams.items"
/>
</div>
</template>
<script setup lang="ts">
// import BasicInfoModal from '@/components/BasicInfoModal'
import { inputLenRules, inputRules } from '@/utils/inputRules'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import { ref, watch, computed, onMounted, nextTick } from 'vue'
import tbl from './components/table.vue'
import type { FormInstance } from 'ant-design-vue'
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import { message } from 'ant-design-vue/es'
import type { Info } from '@/components/BasicInfoModal/model/Info'
import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime'
import { confirmDeleteItem } from '@/utils/confirmModal'
import { cloneDeep } from 'lodash'
import type { Item } from '@/model/Item'
import type { Customer } from '@/model/customer'
import { createCustomer, deleteCustomer, getCustomer, updateCustomer } from '@/api/customer'
import customers from '@/router/routes/customers'
import { usePrice } from '@core/utils/usePrice'
import jalaliday from 'jalaliday'
import { useRoute, useRouter } from 'vue-router'
import ordersTbl from '@/pages/menuPages/orders/components/table.vue'
import transactionsTbl from '@/pages/menuPages/transactions/components/table.vue'
import smsTbl from '@/pages/menuPages/sms/components/table.vue'
import FundsTable from '@/pages/menuPages/customers/base/components/fundsTable.vue'
import PiggyFundsTable from '@/pages/menuPages/customers/base/components/piggyFundsTable.vue'
import ReferralsTable from '@/pages/menuPages/customers/base/components/referralsTable.vue'
import CustomerPargarLogsTable from '@/pages/menuPages/customers/base/components/CustomerPargarLogsTable.vue'
import CustomerConditionalOrderTab from '@/pages/menuPages/customers/base/components/CustomerConditionalOrderTab.vue'
import type { Order } from '@/model/order'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { isMobile } from '@core/utils/is'
import WalletChargeModal from './components/WalletChargeModal.vue'
import CustomerWalletBalancePanel from '@/components/customer/CustomerWalletBalancePanel.vue'
import { getChargeTargets } from '@/api/wallet'
import type { ChargeTarget } from '@/model/wallet'
import { metalLabel } from '@/utils/adminMetalLabels'
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
import DashboardLabelWithHelp from '@/pages/dashboard/components/DashboardLabelWithHelp.vue'
import {
accountingProviderSetting,
conditionalOrderEnabledSetting,
initAccountingSettingsFromApi,
initWalletSettingsFromApi
} from '@/utils/config'
import { displayAccountingCode, isValidAccountingCode } from '@/utils/accountingCode'
import CustomerPresenceBadge from '@/pages/menuPages/customers/base/components/CustomerPresenceBadge.vue'
import { syncCustomerWalletFromPargar, type PargarSyncWalletResult } from '@/api/pargar'
import PargarWalletSyncAction from './components/PargarWalletSyncAction.vue'
const fnameHelp: DashboardHelp = {
description: 'نام کوچک مشتری که در اپ و پنل نمایش داده می‌شود.',
}
const lnameHelp: DashboardHelp = {
description: 'نام خانوادگی مشتری در پروفایل و گزارش‌ها.',
}
const mobileHelp: DashboardHelp = {
description: 'شماره همراه ۱۱ رقمی برای ورود به اپ و ارسال پیامک.',
}
const passwordHelp: DashboardHelp = {
description: 'رمز عبور ورود به اپ. در ویرایش خالی بگذارید اگر نمی‌خواهید تغییر کند.',
}
const birthDateHelp: DashboardHelp = {
description: 'تاریخ تولد مشتری برای احراز هویت و پرونده کاربری.',
}
const phoneNumberHelp: DashboardHelp = {
description: 'شماره تلفن ثابت (اختیاری) برای تماس.',
}
const provinceHelp: DashboardHelp = {
description: 'نام استان محل سکونت مشتری.',
}
const cityHelp: DashboardHelp = {
description: 'نام شهر محل سکونت مشتری.',
}
const nCodeHelp: DashboardHelp = {
description: 'کد ملی ۱۰ رقمی برای احراز هویت.',
}
const addUserLimitHelp: DashboardHelp = {
description: 'حداکثر تعداد نفری که این مشتری می‌تواند با کد معرف خود دعوت کند.',
}
const referralToIdHelp: DashboardHelp = {
description: 'کد معرف فردی که این مشتری را دعوت کرده. پس از ثبت قابل تغییر نیست.',
}
//tabs
const activeKey = ref('اطلاعات جامع')
//loading
const loading = ref<boolean>(false)
//define store
const store = useStaticApiListsStore()
//define tbl
const table = ref(null)
const orderTable = ref(null)
const transactionTable = ref(null)
const smsTable = ref(null)
const referralTable = ref(null)
const fundTable = ref(null)
const piggyFundTable = ref(null)
const pargarLogsTable = ref<{ fetch?: () => void } | null>(null)
const refreshTable = () => table?.value?.fetch()
const refreshOrderTable = () => orderTable?.value?.fetch()
const refreshTransactionTable = () => transactionTable?.value?.fetch()
const refreshSMSTable = () => smsTable?.value?.fetch()
const refreshReferralTable = () => referralTable?.value?.fetch()
const refreshFundTable = () => fundTable?.value?.fetch()
const refreshPiggyFundTable = () => piggyFundTable?.value?.fetch()
const refreshPargarLogsTable = () => pargarLogsTable.value?.fetch?.()
let updateKey = ref<number>(0)
//role confirm items
const confirmItems: Array<Item> = [
{
value: 'بله',
key: true
},
{
value: 'خیر',
key: false
}
]
//wallet
const showWalletCharge = ref<boolean>(false)
const chargeCustomerId = ref<number | null>(null)
const userInfoOrderVisible = ref<boolean>(false)
//form
const form = ref<Partial<Customer>>({})
const formRef = ref<FormInstance>()
//info
let infoParams = reactive<Info>({})
function infoOrder(val: Order) {
infoParams.name = val.customer.name
infoParams.type = val.customer.mobile
infoParams.date = toJalaliNoTime(val.created_at)
infoParams.time = toTime(val.created_at)
infoParams.itemsTitle = 'اطلاعات کاربری'
infoParams.items = [
{
key: 'مقدار (گرم)',
value: val.amount ?? '-'
},
{
key: metalLabel.goldGramPriceCustomer(),
value: usePrice(val.price ?? 0) ?? '-'
},
{
key: 'قیمت کل برای مشتری (ریال)',
value: usePrice(val.total_price ?? 0) ?? '-'
},
{
key: metalLabel.goldGramPriceOriginalSlash(),
value: usePrice(val.price_original ?? 0) ?? '-'
},
{
key: 'قیمت کل بدون سود (ریال)',
value: usePrice(val.total_price_original ?? 0) ?? '-'
},
{
key: 'سود هر گرم (ریال)',
value: usePrice(val.fee ?? 0) ?? '-'
},
{
key: 'سود کل (ریال)',
value: usePrice(val.total_fee ?? 0) ?? '-'
},
{
key: 'کد کاربری',
value: val.customer.id ?? '-'
},
{
key: 'شماره ملی',
value: val.customer.n_code ?? '-'
},
{
key: 'کارمزد (طلا)',
value: val.service_charge != null && val.service_charge !== ''
? `${val.service_charge} گرم`
: '-'
},
{
key: 'کارمزد (ریال)',
value: usePrice(Number(val.service_charge_price ?? 0)) ?? '-'
},
{
key: val.direction === 2 ? 'مبلغ دریافتی مشتری (ریال)' : 'مبلغ پرداختی مشتری (ریال)',
value: usePrice(val.payment_amount ?? val.total_price ?? 0) ?? '-'
},
/* {
key: 'تاریخ تولد',
value: toJalaliNoTime(val.customer.birth_date) ?? '-'
},*/
/* {
key: 'نام فروشگاه',
value: val.customer.shop_name ?? '-'
},*/
{
key: 'شماره تلفن',
value: val.customer.phone_number ?? '-'
},
{
key: 'نام استان',
value: val.customer.province ?? '-'
},
{
key: 'نام شهر',
value: val.customer.city ?? '-'
}
/* {
key: 'نام چاپی',
value: val.print_name ?? '-'
}*/
]
userInfoOrderVisible.value = true
}
/** جلوگیری از race وقتی چند بار پشت‌سرهم مدال باز می‌شود */
let infoLoadSeq = 0
async function refetchCustomerProfile(customerId?: number | null) {
const id = customerId ?? userInfo.value?.id
if (id == null) return
const seq = ++infoLoadSeq
try {
const fresh = await getCustomer(id)
if (seq !== infoLoadSeq || !fresh) return
userInfo.value = cloneDeep(fresh)
} catch (e) {
console.error(e)
if (seq === infoLoadSeq) {
message.warning('به‌روزرسانی مشخصات مشتری ناموفق بود؛ داده‌های جدول نمایش داده شد')
}
}
}
/** رفرش دادهٔ همان تب فعال داخل مدال مشخصات */
async function refreshInfoTab(key: string) {
if (!userInfoVisible.value || !userInfo.value?.id) return
switch (key) {
case 'اطلاعات جامع':
void loadInfoChargeTargets()
await Promise.allSettled([refetchCustomerProfile(), refreshWalletPanels()])
break
case 'لیست سفارشات':
await nextTick()
refreshOrderTable()
break
case 'لیست تراکنش ها':
await nextTick()
refreshTransactionTable()
break
case 'پیامک ها':
await nextTick()
refreshSMSTable()
break
case 'معرفی کرده ها':
await nextTick()
refreshReferralTable()
break
case 'دارایی ها':
await nextTick()
refreshFundTable()
break
case 'قلک ها':
await nextTick()
refreshPiggyFundTable()
break
case 'لاگ پرگار':
await nextTick()
refreshPargarLogsTable()
break
case 'سفارش شرطی':
await refetchCustomerProfile()
break
default:
break
}
}
async function info(val: Customer) {
// فوری از ردیف جدول نشان بده؛ بعد تکی از API تازه می‌گیریم
userInfo.value = cloneDeep(val)
userInfoVisible.value = true
// اگر تب از قبل «اطلاعات جامع» بود watch شلیک نمی‌کند — دستی رفرش کن
const alreadyOnOverview = activeKey.value === 'اطلاعات جامع'
activeKey.value = 'اطلاعات جامع'
if (alreadyOnOverview) {
await refreshInfoTab('اطلاعات جامع')
}
}
//edit
let editId: number | undefined = undefined
async function edit(record: Customer) {
addUserVisible.value = true
editId = record.id
form.value = cloneDeep(record)
}
//add
async function add() {
loading.value = true
editId = undefined
// await store.getUsersList()
addUserVisible.value = true
updateKey.value++
resetData()
loading.value = false
}
function openWalletCharge(val: Customer) {
chargeCustomerId.value = val.id
showWalletCharge.value = true
}
function onWalletChargeSuccess() {
table.value?.fetch?.()
}
//reset data
const resetData = () => {
formRef.value?.resetFields()
form.value = {
fname: '',
lname: '',
active: true,
avatar_id: undefined,
sts: undefined,
id: undefined,
mobile: undefined
}
}
//delete
const deleteItem = (id: number) => confirmDeleteItem('کاربر', id, deleteCustomer, refreshTable)
//define selects
const route = useRoute()
const router = useRouter()
async function openInfoFromQuery() {
const raw = route.query.infoId
const id = Number(Array.isArray(raw) ? raw[0] : raw)
if (!Number.isFinite(id) || id <= 0) return
try {
loading.value = true
const customer = await getCustomer(id)
if (customer) await info(customer)
} catch (e) {
console.error(e)
message.error('بارگذاری مشخصات مشتری ممکن نیست')
} finally {
loading.value = false
const nextQuery = { ...route.query }
delete nextQuery.infoId
await router.replace({ query: nextQuery })
}
}
onMounted(async () => {
loading.value = true
await Promise.allSettled([initAccountingSettingsFromApi(), initWalletSettingsFromApi()])
loading.value = false
})
watch(
() => route.query.infoId,
() => {
void openInfoFromQuery()
},
{ immediate: true }
)
/** با هر عوض شدن تب داخل مدال، دادهٔ همان تب دوباره از API گرفته شود */
watch(activeKey, (key) => {
if (!userInfoVisible.value) return
void refreshInfoTab(key)
})
//add project modal
const addUserVisible = ref<boolean>(false)
const userInfoVisible = ref<boolean>(false)
const infoChargeTargets = ref<ChargeTarget[]>([])
async function loadInfoChargeTargets() {
if (!isMobile.value) return
try {
const res = await getChargeTargets()
infoChargeTargets.value = res?.targets ?? []
} catch {
infoChargeTargets.value = []
}
}
const userInfo = ref<Customer>()
const accountingCodeVisible = computed(() => {
const provider = accountingProviderSetting.value
return provider === 'pargar' || provider === 'tahesab' || provider === 'zarin'
})
const conditionalOrderEnabled = computed(() => conditionalOrderEnabledSetting.value)
function onConditionalOrderUpdated(payload: {
conditional_order_active?: boolean
conditional_order_limit?: number | null
}) {
if (!userInfo.value) return
if (payload.conditional_order_active !== undefined) {
userInfo.value.conditional_order_active = payload.conditional_order_active
}
if (payload.conditional_order_limit !== undefined) {
userInfo.value.conditional_order_limit = payload.conditional_order_limit
}
refreshTable()
}
const accountingCodeValue = computed(() => {
const provider = accountingProviderSetting.value
if (provider === 'pargar') return displayAccountingCode(userInfo.value?.pargar_account_id)
if (provider === 'tahesab' || provider === 'zarin') return displayAccountingCode(userInfo.value?.moshtari_code)
return '-'
})
const pargarSyncLoading = ref(false)
const walletPanelDesktop = ref<{ reload?: () => void | Promise<void> } | null>(null)
const walletPanelMobile = ref<{ reload?: () => void | Promise<void> } | null>(null)
const walletPanelKey = ref(0)
/** فقط وقتی حسابداری روی پرگار تنظیم شده باشد */
const pargarWalletSyncVisible = computed(
() => accountingProviderSetting.value === 'pargar'
)
const pargarWalletCanSync = computed(() =>
isValidAccountingCode(userInfo.value?.pargar_account_id)
)
async function refreshWalletPanels() {
await nextTick()
walletPanelKey.value += 1
await nextTick()
await Promise.allSettled([
Promise.resolve(walletPanelDesktop.value?.reload?.()),
Promise.resolve(walletPanelMobile.value?.reload?.()),
])
}
async function syncPargarWallet(force = false) {
if (!userInfo.value?.id || !pargarWalletCanSync.value || pargarSyncLoading.value) return
pargarSyncLoading.value = true
try {
const res = await syncCustomerWalletFromPargar(userInfo.value.id, force)
const applied =
res.ok ||
(res.result?.documents?.acknowledged ?? 0) > 0 ||
(res.result?.documents?.applied ?? 0) > 0 ||
Boolean(res.result?.mande?.applied)
if (applied) {
message.success(res.message || 'مانده از پرگار روی کیف اعمال شد')
} else {
message.info(res.message || 'تغییری برای اعمال نبود')
}
await refreshWalletPanels()
refreshTransactionTable()
refreshPargarLogsTable()
refreshTable()
} catch (e: unknown) {
// فقط خطای واقعی شبکه/سرور — پیام ۴۲۲ را interceptor هم نشان می‌دهد؛ دوباره نزن
const data = (e as { response?: { data?: PargarSyncWalletResult } })?.response?.data
const status = (e as { response?: { status?: number } })?.response?.status
const applied =
Boolean(data?.ok) ||
(data?.result?.documents?.acknowledged ?? 0) > 0 ||
(data?.result?.documents?.applied ?? 0) > 0 ||
Boolean(data?.result?.mande?.applied)
await refreshWalletPanels()
refreshTransactionTable()
refreshPargarLogsTable()
refreshTable()
if (applied) {
message.success(data?.message || 'مانده از پرگار روی کیف اعمال شد')
} else if (status !== 422) {
// ۴۲۲ را axios خودش با message.error نشان داده
message.warning(data?.message || 'اعمال مانده از پرگار ناموفق بود')
}
} finally {
pargarSyncLoading.value = false
}
}
//submit user
async function ok() {
formRef.value?.validateFields().then(async () => {
// console.log('salam', form.value)
// return
loading.value = true
try {
if (editId) {
await updateCustomer(editId, form.value)
} else {
await createCustomer(form.value)
}
addUserVisible.value = false
refreshTable()
resetData()
message.success('عملیات با موفقیت انجام شد')
// await getResponsible()
// await getQC()
// await getManager()
} catch (e) {
console.error(e)
// message.error('خطا در بارگذاری اطلاعات')
} finally {
loading.value = false
}
})
}
const result = {
title: ''
}
const userStatus = (status: number) => {
switch (status) {
case 0:
result.title = 'احراز هویت نشده'
break
case 1:
result.title = 'احراز هویت شده توسط سامانه هوشمند'
break
case 2:
result.title = 'احراز هویت شده توسط کارشناس'
break
}
return result
}
</script>
<style scoped>
.reject {
color: #ff0000 !important;
}
.success {
color: #008000 !important;
}
</style>