fix some bugs
This commit is contained in:
+10
-1
@@ -1,10 +1,19 @@
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import useAxios from '@core/utils/useAxios'
|
||||
// import { Customer } from '@/model/customer'
|
||||
import type { Charge, Wallet, Withdraw, SendWeight } from '@/model/wallet'
|
||||
import type { Charge, SendWeight, Wallet, WalletChargeTarget, Withdraw } from '@/model/wallet'
|
||||
|
||||
export const getWalletTable = async (params?: TableInput) =>
|
||||
await useAxios.post<Array<Wallet>>('/admin/wallet/table', params)
|
||||
|
||||
export const getWalletChargeTargets = async () =>
|
||||
await useAxios.get<{ targets: WalletChargeTarget[] }>('/admin/wallet/charge-targets')
|
||||
|
||||
/** شارژ یکپارچه — نقدی یا محصول (product_id=null یعنی ریالی) */
|
||||
export const chargeWalletUnified = async (params: Charge) =>
|
||||
await useAxios.post<boolean>('/admin/wallet/charge_wallet', params)
|
||||
|
||||
/** @deprecated از chargeWalletUnified استفاده کنید */
|
||||
export const chargeWallet = async (params: Charge, type: 'gold' | 'money') =>
|
||||
type === 'money'
|
||||
? await useAxios.post<boolean>('/admin/wallet/charge', params)
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface Customer {
|
||||
wallet: Wallet
|
||||
wallet_cash?: number
|
||||
wallet_gold?: number
|
||||
wallet_metal_gold?: Record<string, number>
|
||||
n_code: string | number
|
||||
last_seen_at:string
|
||||
profit_amount: number
|
||||
|
||||
@@ -2,6 +2,11 @@ import type { Customer } from '@/model/customer'
|
||||
|
||||
export interface Order {
|
||||
amount?: number
|
||||
amount_display?: number
|
||||
amount_unit?: string
|
||||
order_metal_id?: number
|
||||
order_metal_label?: string
|
||||
metal_prices?: Record<string, number>
|
||||
created_at?: string
|
||||
customer?: Customer
|
||||
customer_id?: number
|
||||
|
||||
@@ -58,6 +58,16 @@ export interface SetActiveTime {
|
||||
auto_close_at: string
|
||||
}
|
||||
|
||||
export interface MetalBalanceItem {
|
||||
metal_id: number
|
||||
label: string
|
||||
balance: number
|
||||
goldBalance: number
|
||||
cashBalance?: number
|
||||
goldBalanceOfLiman?: number
|
||||
goldBalanceOfKalhor?: number
|
||||
}
|
||||
|
||||
export interface Balance {
|
||||
cashBalance: number
|
||||
cashBalanceOfKalhor: number
|
||||
@@ -65,6 +75,7 @@ export interface Balance {
|
||||
goldBalance: number
|
||||
goldBalanceOfKalhor: number
|
||||
goldBalanceOfLiman: number
|
||||
metalBalances?: MetalBalanceItem[]
|
||||
mazanehBalance: number
|
||||
mazanehBalanceOfKalhor: number
|
||||
income_money: number
|
||||
|
||||
@@ -14,6 +14,9 @@ export interface Transaction {
|
||||
hash: string
|
||||
id: number
|
||||
note: number
|
||||
metal_gold?: Record<string, number>
|
||||
transaction_metal_id?: number
|
||||
transaction_metal_label?: string
|
||||
order_id: number
|
||||
transaction_type: string
|
||||
type: number
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import type { User } from '@/model/user'
|
||||
|
||||
export interface WalletMetalBalance {
|
||||
metal_id: number
|
||||
label: string
|
||||
gold: number
|
||||
goldToCash: number
|
||||
display_unit?: string
|
||||
}
|
||||
|
||||
export interface Wallet {
|
||||
owner: number
|
||||
id: number
|
||||
@@ -7,9 +15,18 @@ export interface Wallet {
|
||||
cash: number
|
||||
fund_id?: number | null
|
||||
product_id?: number | null
|
||||
metal_gold?: Record<string, number>
|
||||
customer: User
|
||||
}
|
||||
|
||||
export interface WalletTableSums {
|
||||
cash?: number
|
||||
gold?: number
|
||||
cashToGold?: number
|
||||
goldToCash?: number
|
||||
metalBalances?: WalletMetalBalance[]
|
||||
}
|
||||
|
||||
export interface WithdrawTransaction {
|
||||
title?: string
|
||||
gold?: number
|
||||
@@ -37,6 +54,20 @@ export interface Withdraw {
|
||||
|
||||
export type WalletChargeDirection = 'charge' | 'discharge'
|
||||
|
||||
export type WalletChargeTargetKind = 'money' | 'metal'
|
||||
|
||||
export interface WalletChargeTarget {
|
||||
key: string
|
||||
kind: WalletChargeTargetKind
|
||||
label: string
|
||||
unit: string
|
||||
product_id?: number | null
|
||||
metal_id?: number
|
||||
uses_main_wallet?: boolean
|
||||
trade_by_quantity?: boolean
|
||||
group_type?: string
|
||||
}
|
||||
|
||||
export interface Charge {
|
||||
value?: number
|
||||
note?: string
|
||||
@@ -44,6 +75,8 @@ export interface Charge {
|
||||
talabBedehi?: boolean
|
||||
direction?: WalletChargeDirection
|
||||
amountAbs?: number | string
|
||||
chargeTargetKey?: string | null
|
||||
product_id?: number | null
|
||||
}
|
||||
|
||||
export interface WithdrawStatus {
|
||||
|
||||
@@ -27,13 +27,27 @@
|
||||
un-checked-children="مشتری"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 px-2" v-if="balance?.metalBalances?.length">
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<div
|
||||
v-for="item in balance.metalBalances"
|
||||
:key="item.metal_id"
|
||||
class="rounded-lg border border-gray-200 bg-white px-3 py-2"
|
||||
>
|
||||
<p class="mb-1 text-xs text-gray-500">{{ metalBalanceTitleWithUnit(item.metal_id, item.label) }}</p>
|
||||
<p class="mb-0 text-base font-bold">
|
||||
{{ formatMetalWeightFromGrams(item.goldBalanceOfLiman ?? 0, item.metal_id) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2 col-span-2 grid grid-cols-4 gap-1">
|
||||
<div class="bg-blue rounded text-white py-1 flex justify-center items-center">تراز ها</div>
|
||||
<div class="bg-blue rounded text-white py-1 flex justify-center items-center">
|
||||
تراز ریالی (ریال)
|
||||
</div>
|
||||
<div class="bg-blue rounded text-white py-1 flex justify-center items-center">
|
||||
تراز طلایی (گرم)
|
||||
{{ m('تراز طلایی (گرم)') }}
|
||||
</div>
|
||||
<div class="bg-blue rounded text-white py-1 flex justify-center items-center">
|
||||
تراز مظنه (ریال)
|
||||
@@ -187,6 +201,9 @@ import { balance, getTaraz } from '@core/utils/price'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getNow, toJalali } from '@core/utils/useDataTime'
|
||||
import { m } from '@/utils/metalLabels'
|
||||
import { metalBalanceTitleWithUnit } from '@/utils/adminMetalLabels'
|
||||
import { formatMetalWeightFromGrams } from '@/utils/metalWeight'
|
||||
const startTaraz = ref<string>('')
|
||||
const endTaraz = ref<string>('')
|
||||
const selectedItem = ref<object>({})
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</span>
|
||||
<span class="dash-quick-sheet__featured-text">
|
||||
<span class="dash-quick-sheet__featured-label">شارژ / دشارژ کیف پول</span>
|
||||
<span class="dash-quick-sheet__featured-hint">مدیریت موجودی ریالی و طلایی</span>
|
||||
<span class="dash-quick-sheet__featured-hint">{{ metalLabel.manageGoldCash() }}</span>
|
||||
</span>
|
||||
<BaseIcon icon="vuesax-linear:arrow-left-2" :size="16" class="dash-quick-sheet__featured-arrow" />
|
||||
</button>
|
||||
@@ -106,6 +106,7 @@ import type { RouteLocationRaw } from 'vue-router'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import DashboardQuickActionBtn from '@/pages/dashboard/components/DashboardQuickActionBtn.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
type ActionVariant = 'default' | 'accent' | 'danger' | 'primary'
|
||||
type ItemTone = 'slate' | 'emerald' | 'rose' | 'amber' | 'sky' | 'violet'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<BasicActionSheet
|
||||
v-model:visible="visibleProxy"
|
||||
:title="modalTitle"
|
||||
modal-width="22rem"
|
||||
modal-width="24rem"
|
||||
drawer-height="auto"
|
||||
compact
|
||||
:loading="loading"
|
||||
@@ -24,33 +24,18 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="selectedCustomer"
|
||||
class="dashboard-wallet-modal__balances"
|
||||
>
|
||||
<div>
|
||||
<span class="text-slate-500">طلایی</span>
|
||||
<p class="font-semibold text-slate-800 dark:text-slate-100">{{ formatGold(selectedCustomer) }}</p>
|
||||
</div>
|
||||
<div v-if="selectedCustomer" class="dashboard-wallet-modal__balances">
|
||||
<div>
|
||||
<span class="text-slate-500">ریالی</span>
|
||||
<p class="font-semibold text-slate-800 dark:text-slate-100">{{ formatCash(selectedCustomer) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-wallet-modal__section">
|
||||
<label class="dashboard-wallet-modal__label">نوع کیف پول</label>
|
||||
<a-radio-group v-model:value="walletVariant" button-style="solid" class="flex w-full">
|
||||
<a-radio-button value="money" class="w-1/2 text-center">ریالی</a-radio-button>
|
||||
<a-radio-button value="gold" class="w-1/2 text-center">طلایی</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<WalletChargeForm
|
||||
ref="chargeFormRef"
|
||||
class="dashboard-wallet-modal__form"
|
||||
:model-value="formCharge"
|
||||
:variant="walletVariant"
|
||||
:targets="chargeTargets"
|
||||
/>
|
||||
|
||||
<div class="dashboard-wallet-modal__footer">
|
||||
@@ -78,16 +63,18 @@ import { message } from 'ant-design-vue'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import WalletChargeForm from '@/pages/menuPages/customers/base/components/WalletChargeForm.vue'
|
||||
import {
|
||||
applyChargeTarget,
|
||||
buildChargePayload,
|
||||
createEmptyChargeForm,
|
||||
resolveChargeTarget,
|
||||
walletChargeOkText,
|
||||
walletChargeSuccessMessage,
|
||||
walletChargeTitle,
|
||||
} from '@/pages/menuPages/customers/base/utils/walletChargeForm'
|
||||
import { getCustomersList } from '@/api/customer'
|
||||
import { chargeWallet } from '@/api/wallet'
|
||||
import { chargeWalletUnified, getWalletChargeTargets } from '@/api/wallet'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import type { Charge, Wallet } from '@/model/wallet'
|
||||
import type { Charge, Wallet, WalletChargeTarget } from '@/model/wallet'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
|
||||
const props = defineProps<{ visible: boolean }>()
|
||||
@@ -101,7 +88,7 @@ const visibleProxy = computed({
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const customers = ref<Customer[]>([])
|
||||
const walletVariant = ref<'money' | 'gold'>('money')
|
||||
const chargeTargets = ref<WalletChargeTarget[]>([])
|
||||
const formCharge = ref<Charge>(createEmptyChargeForm())
|
||||
const chargeFormRef = ref<InstanceType<typeof WalletChargeForm>>()
|
||||
|
||||
@@ -123,8 +110,12 @@ const selectedCustomer = computed(() =>
|
||||
customers.value.find((customer) => customer.id === formCharge.value.customer_id),
|
||||
)
|
||||
|
||||
const selectedTarget = computed(() =>
|
||||
resolveChargeTarget(chargeTargets.value, formCharge.value.chargeTargetKey),
|
||||
)
|
||||
|
||||
const modalTitle = computed(() =>
|
||||
walletChargeTitle(walletVariant.value, formCharge.value.direction),
|
||||
walletChargeTitle(selectedTarget.value, formCharge.value.direction),
|
||||
)
|
||||
|
||||
const okText = computed(() => walletChargeOkText(formCharge.value.direction))
|
||||
@@ -138,20 +129,25 @@ function customerWallet(customer: Customer): Wallet | undefined {
|
||||
return Array.isArray(wallet) ? wallet[0] : wallet
|
||||
}
|
||||
|
||||
function formatGold(customer: Customer) {
|
||||
const gold = customerWallet(customer)?.gold ?? customer.wallet_gold ?? customer.gold
|
||||
return `${Number(gold ?? 0).toLocaleString('fa-IR', { maximumFractionDigits: 3 })} گرم`
|
||||
}
|
||||
|
||||
function formatCash(customer: Customer) {
|
||||
const cash = customerWallet(customer)?.cash ?? customer.wallet_cash
|
||||
return usePrice(Number(cash ?? 0), true, '')
|
||||
}
|
||||
|
||||
async function loadChargeTargets() {
|
||||
const data = await getWalletChargeTargets()
|
||||
chargeTargets.value = data?.targets ?? []
|
||||
const firstTarget = chargeTargets.value[0]
|
||||
if (firstTarget) {
|
||||
Object.assign(formCharge.value, applyChargeTarget(formCharge.value, firstTarget))
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCustomers() {
|
||||
loading.value = true
|
||||
try {
|
||||
customers.value = (await getCustomersList()) ?? []
|
||||
const [customerList] = await Promise.all([getCustomersList(), loadChargeTargets()])
|
||||
customers.value = customerList ?? []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
message.error('خطا در بارگذاری لیست مشتریان')
|
||||
@@ -161,8 +157,11 @@ async function loadCustomers() {
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
walletVariant.value = 'money'
|
||||
formCharge.value = createEmptyChargeForm()
|
||||
const firstTarget = chargeTargets.value[0]
|
||||
if (firstTarget) {
|
||||
Object.assign(formCharge.value, applyChargeTarget(formCharge.value, firstTarget))
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
@@ -188,7 +187,7 @@ async function submit() {
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
await chargeWallet(buildChargePayload(formCharge.value), walletVariant.value)
|
||||
await chargeWalletUnified(buildChargePayload(formCharge.value))
|
||||
message.success(walletChargeSuccessMessage(formCharge.value.direction))
|
||||
closeModal()
|
||||
} catch (e) {
|
||||
@@ -231,7 +230,7 @@ watch(
|
||||
}
|
||||
|
||||
.dashboard-wallet-modal__balances {
|
||||
@apply grid grid-cols-2 gap-3 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5 text-xs dark:border-slate-600 dark:bg-slate-800/60;
|
||||
@apply grid grid-cols-1 gap-3 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5 text-xs dark:border-slate-600 dark:bg-slate-800/60;
|
||||
}
|
||||
|
||||
.dashboard-wallet-modal__footer {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
toggleGateway,
|
||||
} from '@/api/dashboard'
|
||||
import { getWalletTable } from '@/api/wallet'
|
||||
import { formatMetalWeightFromGrams } from '@/utils/metalWeight'
|
||||
import type {
|
||||
DashboardFlowPoint,
|
||||
DashboardFinance,
|
||||
@@ -23,6 +24,7 @@ import type { Order } from '@/model/order'
|
||||
import type { Balance } from '@/model/product'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { isStoreApp } from '@/utils/config'
|
||||
import { useAdminTheme } from '@core/composable/useAdminTheme'
|
||||
@@ -229,7 +231,10 @@ export function useDashboardPage() {
|
||||
master.value = await getDashboard()
|
||||
}
|
||||
|
||||
function formatGram(value?: number) {
|
||||
function formatGram(value?: number, metalId?: number) {
|
||||
if (metalId != null) {
|
||||
return formatMetalWeightFromGrams(value ?? 0, metalId)
|
||||
}
|
||||
return `${Number(value ?? 0).toLocaleString('fa-IR', { maximumFractionDigits: 3 })} گرم`
|
||||
}
|
||||
|
||||
@@ -277,7 +282,7 @@ export function useDashboardPage() {
|
||||
return {
|
||||
...chartSurfaceOptions(),
|
||||
chart: { type: 'donut', fontFamily: 'inherit', offsetY: 4, foreColor: chartSurfaceOptions().chart.foreColor },
|
||||
labels: ['سود معامله', 'کارمزد طلایی', 'هدایای طلا'],
|
||||
labels: ['سود معامله', metalLabel.goldCommission(), metalLabel.goldGifts()],
|
||||
colors: ['#10b981', '#f59e0b', '#8b5cf6'],
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
@@ -576,6 +581,8 @@ export function useDashboardPage() {
|
||||
}
|
||||
})
|
||||
|
||||
const metalBalanceItems = computed(() => balance.value?.metalBalances ?? [])
|
||||
|
||||
onMounted(() => {
|
||||
void refreshAll()
|
||||
})
|
||||
@@ -588,6 +595,7 @@ export function useDashboardPage() {
|
||||
master,
|
||||
finance,
|
||||
balance,
|
||||
metalBalanceItems,
|
||||
walletSums,
|
||||
recentOrders,
|
||||
customerStats,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** توضیح و فرمول فارسی — برای tooltip آیکون راهنما */
|
||||
|
||||
import { m } from '@/utils/metalLabels'
|
||||
|
||||
export type DashboardHelp = {
|
||||
description: string
|
||||
formula: string
|
||||
@@ -7,133 +9,119 @@ export type DashboardHelp = {
|
||||
|
||||
export const MESGHAL_FACTOR = '۴.۳۳۱۸۰۲'
|
||||
|
||||
export const dashboardHero: DashboardHelp = {
|
||||
description: 'آخرین قیمت پایه هر گرم طلا در سیستم.',
|
||||
formula: `قیمت هر مثقال = قیمت هر گرم × ${MESGHAL_FACTOR}`,
|
||||
function help(description: string, formula: string): DashboardHelp {
|
||||
return {
|
||||
description: m(description),
|
||||
formula: m(formula),
|
||||
}
|
||||
}
|
||||
|
||||
export const dashboardGateway: DashboardHelp = {
|
||||
description: 'وقتی فعال باشد مشتری میتواند از درگاه بانکی واریز کند؛ در حالت غیرفعال فقط واریز دستی ممکن است.',
|
||||
formula: 'وضعیت: فعال یا غیرفعال',
|
||||
}
|
||||
export const dashboardHero = help(
|
||||
'آخرین قیمت پایه هر گرم طلا در سیستم.',
|
||||
`قیمت هر مثقال = قیمت هر گرم × ${MESGHAL_FACTOR}`
|
||||
)
|
||||
|
||||
export const dashboardGateway = help(
|
||||
'وقتی فعال باشد مشتری میتواند از درگاه بانکی واریز کند؛ در حالت غیرفعال فقط واریز دستی ممکن است.',
|
||||
'وضعیت: فعال یا غیرفعال'
|
||||
)
|
||||
|
||||
export const dashboardKpi = {
|
||||
profit: {
|
||||
description: 'مجموع سود اسپرد (تفاوت قیمت خرید/فروش با مشتری) روی همه سفارشهای ثبتشده در بازه.',
|
||||
formula: 'سود کل = جمع «سود هر سفارش»',
|
||||
},
|
||||
commission: {
|
||||
description: 'مجموع کارمزد طلایی محصول که روی همان سفارشها محاسبه شده.',
|
||||
formula: 'کارمزد کل = جمع «کارمزد هر سفارش»',
|
||||
},
|
||||
orderCount: {
|
||||
description: 'تعداد همه سفارشهایی که در بازه انتخابی ثبت شدهاند (هر وضعیتی).',
|
||||
formula: 'تعداد = شمارش سفارشها بین دو تاریخ',
|
||||
},
|
||||
goldBalance: {
|
||||
description: 'تراز وزنی طلا از سفارشات موفق؛ مثبت یعنی به مشتری بیشتر فروختهاید.',
|
||||
formula: 'تراز طلا = گرم فروش − گرم خرید (سفارش موفق)',
|
||||
},
|
||||
cashBalance: {
|
||||
description: 'تراز ریالی همان سفارشات موفق؛ ورود و خروج نقد از معاملات.',
|
||||
formula: 'تراز ریال = مبلغ فروش − مبلغ خرید',
|
||||
},
|
||||
mazanehBalance: {
|
||||
description: 'میانگین موزون قیمت معاملات، تبدیلشده به مثقال.',
|
||||
formula: `(جمع مبلغ معاملات ÷ جمع گرم) × ${MESGHAL_FACTOR}`,
|
||||
},
|
||||
profit: help(
|
||||
'مجموع سود اسپرد (تفاوت قیمت خرید/فروش با مشتری) روی همه سفارشهای ثبتشده در بازه.',
|
||||
'سود کل = جمع «سود هر سفارش»'
|
||||
),
|
||||
commission: help(
|
||||
'مجموع کارمزد طلایی محصول که روی همان سفارشها محاسبه شده.',
|
||||
'کارمزد کل = جمع «کارمزد هر سفارش»'
|
||||
),
|
||||
orderCount: help(
|
||||
'تعداد همه سفارشهایی که در بازه انتخابی ثبت شدهاند (هر وضعیتی).',
|
||||
'تعداد = شمارش سفارشها بین دو تاریخ'
|
||||
),
|
||||
goldBalance: help(
|
||||
'تراز وزنی طلا از سفارشات موفق؛ مثبت یعنی به مشتری بیشتر فروختهاید.',
|
||||
'تراز طلا = گرم فروش − گرم خرید (سفارش موفق)'
|
||||
),
|
||||
cashBalance: help(
|
||||
'تراز ریالی همان سفارشات موفق؛ ورود و خروج نقد از معاملات.',
|
||||
'تراز ریال = مبلغ فروش − مبلغ خرید'
|
||||
),
|
||||
mazanehBalance: help(
|
||||
'میانگین موزون قیمت معاملات، تبدیلشده به مثقال.',
|
||||
`(جمع مبلغ معاملات ÷ جمع گرم) × ${MESGHAL_FACTOR}`
|
||||
),
|
||||
} as const satisfies Record<string, DashboardHelp>
|
||||
|
||||
export const dashboardDateRange: DashboardHelp = {
|
||||
description: 'تاریخ شروع و پایان برای محاسبه کارتها، تراز و نمودارها.',
|
||||
formula: 'گزارش = از تاریخ … تا تاریخ …',
|
||||
}
|
||||
export const dashboardDateRange = help(
|
||||
'تاریخ شروع و پایان برای محاسبه کارتها، تراز و نمودارها.',
|
||||
'گزارش = از تاریخ … تا تاریخ …'
|
||||
)
|
||||
|
||||
export const dashboardDateMetrics = {
|
||||
bankDeposit: {
|
||||
description: 'کل واریزهای موفق مشتریان از درگاه آنلاین در این بازه.',
|
||||
formula: 'جمع مبلغ واریزهای تأییدشده از درگاه',
|
||||
},
|
||||
netIncome: {
|
||||
description: 'درآمد پس از کسر ارزش طلای هدیهدادهشده.',
|
||||
formula: 'درآمد خالص = (اختلاف قیمت اصلی و پرداختی) − (گرم هدیه × مظنه)',
|
||||
},
|
||||
gifts: {
|
||||
description: 'طلایی که ادمین به مشتریان هدیه داده در همین بازه.',
|
||||
formula: 'گرم هدیه = جمع طلای ثبتشده در تراکنشهای ادمین',
|
||||
},
|
||||
bankDeposit: help(
|
||||
'کل واریزهای موفق مشتریان از درگاه آنلاین در این بازه.',
|
||||
'جمع مبلغ واریزهای تأییدشده از درگاه'
|
||||
),
|
||||
netIncome: help(
|
||||
'درآمد پس از کسر ارزش طلای هدیهدادهشده.',
|
||||
'درآمد خالص = (اختلاف قیمت اصلی و پرداختی) − (گرم هدیه × مظنه)'
|
||||
),
|
||||
gifts: help(
|
||||
'طلایی که ادمین به مشتریان هدیه داده در همین بازه.',
|
||||
'گرم هدیه = جمع طلای ثبتشده در تراکنشهای ادمین'
|
||||
),
|
||||
} as const satisfies Record<string, DashboardHelp>
|
||||
|
||||
export const dashboardCharts = {
|
||||
incomeMix: {
|
||||
description: 'نشان میدهد سود معامله، کارمزد و هدایا چقدر از درآمد کل را تشکیل میدهند.',
|
||||
formula: 'درآمد = سود معامله + کارمزد + (گرم هدیه × مظنه)',
|
||||
},
|
||||
buySellVolume: {
|
||||
description: 'مقدار طلای خرید و فروش مشتری در هر روز (فقط سفارش موفق).',
|
||||
formula: 'هر روز: گرم خرید مشتری و گرم فروش به مشتری',
|
||||
},
|
||||
priceChart: {
|
||||
description: 'روند تغییر قیمت پایه هر گرم در ۲۴ ساعت، هفته یا ماه.',
|
||||
formula: 'نمودار = قیمت پایه در هر لحظه',
|
||||
},
|
||||
productActivity: {
|
||||
description: 'مقایسه حجم خرید و فروش هر محصول در بازه انتخابی.',
|
||||
formula: 'خرید = جمع گرم مثبت | فروش = جمع گرم منفی (معاملات همان بازه)',
|
||||
},
|
||||
incomeMix: help(
|
||||
'نشان میدهد سود معامله، کارمزد و هدایا چقدر از درآمد کل را تشکیل میدهند.',
|
||||
'درآمد = سود معامله + کارمزد + (گرم هدیه × مظنه)'
|
||||
),
|
||||
buySellVolume: help(
|
||||
'مقدار طلای خرید و فروش مشتری در هر روز (فقط سفارش موفق).',
|
||||
'هر روز: گرم خرید مشتری و گرم فروش به مشتری'
|
||||
),
|
||||
priceChart: help(
|
||||
'روند تغییر قیمت پایه هر گرم در ۲۴ ساعت، هفته یا ماه.',
|
||||
'نمودار = قیمت پایه در هر لحظه'
|
||||
),
|
||||
productActivity: help(
|
||||
'مقایسه حجم خرید و فروش هر محصول در بازه انتخابی.',
|
||||
'خرید = جمع گرم مثبت | فروش = جمع گرم منفی (معاملات همان بازه)'
|
||||
),
|
||||
} as const satisfies Record<string, DashboardHelp>
|
||||
|
||||
export const dashboardOrders = {
|
||||
peakHours: {
|
||||
description: 'توزیع سفارشهای موفق مشتریان خانگی در ۲۴ ساعت شبانهروز؛ برای شناخت ساعات شلوغ.',
|
||||
formula: 'هر ستون = تعداد سفارش در آن ساعت (جمع چند روز اخیر)',
|
||||
},
|
||||
recent: {
|
||||
description: '۱۴ سفارش آخر با جزئیات مظنه و سود.',
|
||||
formula: 'مظنه = قیمت پایه | قیمت مشتری = مظنه ± اسپرد | سود/گرم = تفاوت اسپرد',
|
||||
},
|
||||
peakHours: help(
|
||||
'توزیع سفارشهای موفق مشتریان خانگی در ۲۴ ساعت شبانهروز؛ برای شناخت ساعات شلوغ.',
|
||||
'هر ستون = تعداد سفارش در آن ساعت (جمع چند روز اخیر)'
|
||||
),
|
||||
recent: help(
|
||||
'۱۴ سفارش آخر با جزئیات مظنه و سود.',
|
||||
'مظنه = قیمت پایه | قیمت مشتری = مظنه ± اسپرد | سود/گرم = تفاوت اسپرد'
|
||||
),
|
||||
} as const satisfies Record<string, DashboardHelp>
|
||||
|
||||
export const dashboardCustomers: DashboardHelp = {
|
||||
description: 'آمار مشتریان خانگی ثبتشده در سیستم.',
|
||||
formula: 'فعال = دارای کیف پول | بدون احراز = هویت تأیید نشده',
|
||||
}
|
||||
export const dashboardCustomers = help(
|
||||
'آمار مشتریان خانگی ثبتشده در سیستم.',
|
||||
'فعال = دارای کیف پول | بدون احراز = هویت تأیید نشده'
|
||||
)
|
||||
|
||||
export const dashboardCustomerMetrics = {
|
||||
total: {
|
||||
description: 'همه مشتریان خانگی ثبتشده.',
|
||||
formula: 'شمارش کل مشتریان',
|
||||
},
|
||||
new: {
|
||||
description: 'مشتریانی که در ۳ ماه اخیر ثبتنام کردهاند.',
|
||||
formula: 'ثبتنام در ۹۰ روز گذشته',
|
||||
},
|
||||
active: {
|
||||
description: 'مشتریانی که حداقل یک کیف پول دارند.',
|
||||
formula: 'دارای موجودی در کیف',
|
||||
},
|
||||
notVerified: {
|
||||
description: 'مشتریانی که احراز هویت را تکمیل نکردهاند.',
|
||||
formula: 'وضعیت احراز: ناقص',
|
||||
},
|
||||
total: help('همه مشتریان خانگی ثبتشده.', 'شمارش کل مشتریان'),
|
||||
new: help('مشتریانی که در ۳ ماه اخیر ثبتنام کردهاند.', 'ثبتنام در ۹۰ روز گذشته'),
|
||||
active: help('مشتریانی که حداقل یک کیف پول دارند.', 'دارای موجودی در کیف'),
|
||||
notVerified: help('مشتریانی که احراز هویت را تکمیل نکردهاند.', 'وضعیت احراز: ناقص'),
|
||||
} as const satisfies Record<string, DashboardHelp>
|
||||
|
||||
export const dashboardWallet: DashboardHelp = {
|
||||
description: 'مجموع موجودی طلا و ریال همه مشتریان.',
|
||||
formula: 'ارزش طلا به ریال = جمع گرم × مظنه لحظهای',
|
||||
}
|
||||
export const dashboardWallet = help(
|
||||
'مجموع موجودی طلا و ریال همه مشتریان.',
|
||||
'ارزش طلا به ریال = جمع گرم × مظنه لحظهای'
|
||||
)
|
||||
|
||||
export const dashboardWalletMetrics = {
|
||||
gold: {
|
||||
description: 'مجموع طلای موجود در کیف همه مشتریان.',
|
||||
formula: 'جمع گرم طلا در کیفها',
|
||||
},
|
||||
cash: {
|
||||
description: 'مجموع ریال موجود در کیف همه مشتریان.',
|
||||
formula: 'جمع ریال در کیفها',
|
||||
},
|
||||
goldToCash: {
|
||||
description: 'ارزش ریالی کل طلای مشتریان با مظنه فعلی.',
|
||||
formula: 'جمع گرم × قیمت هر گرم',
|
||||
},
|
||||
gold: help('مجموع طلای موجود در کیف همه مشتریان.', 'جمع گرم طلا در کیفها'),
|
||||
cash: help('مجموع ریال موجود در کیف همه مشتریان.', 'جمع ریال در کیفها'),
|
||||
goldToCash: help('ارزش ریالی کل طلای مشتریان با مظنه فعلی.', 'جمع گرم × قیمت هر گرم'),
|
||||
} as const satisfies Record<string, DashboardHelp>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<DashboardStatCard
|
||||
tone="amber"
|
||||
icon="vuesax-linear:coin"
|
||||
title="کارمزد طلایی"
|
||||
:title="metalLabel.goldCommission()"
|
||||
:value="usePrice(finance?.fullPriceKarmozd ?? finance?.fullPriceKaermozd ?? 0, true, '')"
|
||||
:help="copy.kpi.commission"
|
||||
/>
|
||||
@@ -68,10 +68,22 @@
|
||||
:value="String(finance?.orderCount ?? 0)"
|
||||
:help="copy.kpi.orderCount"
|
||||
/>
|
||||
<template v-if="metalBalanceItems.length">
|
||||
<DashboardStatCard
|
||||
v-for="item in metalBalanceItems"
|
||||
:key="item.metal_id"
|
||||
tone="blue"
|
||||
icon="vuesax-linear:empty-wallet"
|
||||
:title="metalBalanceTitle(item.metal_id, item.label)"
|
||||
:value="formatGram(item.goldBalance, item.metal_id)"
|
||||
:help="copy.kpi.goldBalance"
|
||||
/>
|
||||
</template>
|
||||
<DashboardStatCard
|
||||
v-else
|
||||
tone="blue"
|
||||
icon="vuesax-linear:empty-wallet"
|
||||
title="تراز طلایی"
|
||||
:title="metalLabel.goldBalance()"
|
||||
:value="formatGram(balance?.goldBalance)"
|
||||
:help="copy.kpi.goldBalance"
|
||||
/>
|
||||
@@ -452,6 +464,7 @@ import {
|
||||
} from '@/pages/dashboard/dashboardSectionCopy'
|
||||
import { useDashboardPage } from '@/pages/dashboard/composables/useDashboardPage'
|
||||
import { useDashboardQuickActions } from '@/pages/dashboard/composables/useDashboardQuickActions'
|
||||
import { metalBalanceTitle, metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
const copy = {
|
||||
hero: dashboardHero,
|
||||
@@ -472,6 +485,7 @@ const {
|
||||
master,
|
||||
finance,
|
||||
balance,
|
||||
metalBalanceItems,
|
||||
walletSums,
|
||||
recentOrders,
|
||||
peakHoursRange,
|
||||
@@ -573,7 +587,7 @@ const customerMetricItems = computed(() => [
|
||||
|
||||
const walletMetricItems = computed(() => [
|
||||
{
|
||||
label: 'طلای مشتریان',
|
||||
label: metalLabel.customersGold(),
|
||||
value: formatGram(walletSums.value?.gold),
|
||||
help: dashboardWalletMetrics.gold,
|
||||
variant: 'amber',
|
||||
@@ -585,7 +599,7 @@ const walletMetricItems = computed(() => [
|
||||
variant: 'sky',
|
||||
},
|
||||
{
|
||||
label: 'ارزش طلا به ریال',
|
||||
label: metalLabel.goldValueInRial(),
|
||||
value: usePrice(walletSums.value?.goldToCash ?? 0, true, ''),
|
||||
help: dashboardWalletMetrics.goldToCash,
|
||||
variant: 'violet',
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
<MobileStatCard bordered variant="success" label="موجودی طلایی" :value="`${row.balance_gold ?? 0} گرم`">
|
||||
<MobileStatCard bordered variant="success" :label="metalLabel.goldInventory()" :value="`${row.balance_gold ?? 0} گرم`">
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" color="#15803d" />
|
||||
</template>
|
||||
@@ -26,7 +26,7 @@
|
||||
<MobileDetailRow label="نام مشتری دعوت شده" :value="row.customer?.name || '-'" />
|
||||
<MobileDetailRow label="زمان ثبت" :value="row.created_at ? toJalali(row.created_at) : '-'" />
|
||||
<MobileDetailRow label="نقد (ریال)" :value="usePrice(row.cash ?? 0, true, '')" />
|
||||
<MobileDetailRow label="طلا (گرم)" :value="`${row.gold ?? 0}`" />
|
||||
<MobileDetailRow :label="m('طلا (گرم)')" :value="`${row.gold ?? 0}`" />
|
||||
<MobileDetailRow label="توضیحات" full-width :value="row.note || '-'" />
|
||||
</MobileInfoGrid>
|
||||
</template>
|
||||
@@ -44,6 +44,8 @@ import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { m } from '@/utils/metalLabels'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="text-xs text-gray-400">{{ createdAtLabel }}</div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-600">نقد: <span class="font-600 text-gray-900">{{ cashLabel }}</span></span>
|
||||
<span class="text-xs text-gray-600">طلا: <span class="font-600 text-gray-900">{{ goldLabel }}</span></span>
|
||||
<span class="text-xs text-gray-600">{{ metalLabel.goldUnit() }}: <span class="font-600 text-gray-900">{{ goldLabel }}</span></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -21,6 +21,7 @@ import type { TotalAccounting } from '@/model/accounting'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
const props = defineProps<{
|
||||
item: TotalAccounting
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { getTotalAccountingTable } from '@/api/accounting'
|
||||
import { cols } from '@/pages/menuPages/accounting/hooks/accountingColumns'
|
||||
import { getAccountingColumns } from '@/pages/menuPages/accounting/hooks/accountingColumns'
|
||||
import AccountingMobileCardList from '@/pages/menuPages/accounting/components/AccountingMobileCardList.vue'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
||||
@@ -51,7 +51,7 @@ const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
const tblProps = ref({
|
||||
columns: cols,
|
||||
columns: getAccountingColumns(),
|
||||
api: (params: TableInput) => getTotalAccountingTable(params)
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
@@ -21,7 +22,8 @@ const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
}
|
||||
})*/
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
export function getAccountingColumns(): BaseTableColumn[] {
|
||||
return [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
@@ -63,7 +65,7 @@ export const cols: BaseTableColumn[] = [
|
||||
// }
|
||||
},
|
||||
{
|
||||
title: 'طلا',
|
||||
title: metalLabel.goldUnit(),
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
type: 'Number',
|
||||
@@ -81,4 +83,5 @@ export const cols: BaseTableColumn[] = [
|
||||
slot: 'action',
|
||||
filterable: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<a-form :model="form" ref="formRef" autocomplete="off">
|
||||
<div class="grid md:grid-cols-1 gap-4">
|
||||
<div class="bg-gray-50 px-10 py-3 flex justify-center gap-4">
|
||||
<div>قیمت {{ form.direction === 1 ? 'خرید' : 'فروش' }} طلا:</div>
|
||||
<div>{{ metalLabel.goldTradeBuySell(form.direction) }}</div>
|
||||
<div class="font-bold">
|
||||
{{ usePrice(form.direction === 1 ? dataLast?.buy ?? 0 : dataLast?.sell ?? 0) }}
|
||||
</div>
|
||||
@@ -65,7 +65,7 @@
|
||||
</a-form-item>
|
||||
<BasicInfoItem
|
||||
is-line
|
||||
title="کیف پول طلا"
|
||||
:title="metalLabel.goldWalletTitle()"
|
||||
class="mt-2"
|
||||
:value="getGoldBalance(selectedCustomer?.wallet) || '_'"
|
||||
/>
|
||||
@@ -95,7 +95,7 @@
|
||||
addon-before="گرم"
|
||||
addon-bg-color="#0A1B37"
|
||||
addon-text-color="white"
|
||||
place-holder="مقدار طلا"
|
||||
:place-holder="metalLabel.goldPlaceholder()"
|
||||
type="tel"
|
||||
@onchange="calculateCash"
|
||||
/>
|
||||
@@ -143,6 +143,7 @@ import { getLastPrice } from '@/api/price'
|
||||
import type { last } from '@/model/price'
|
||||
import { createOrder } from '@/api/order'
|
||||
import { getCashBalance, getGoldBalance } from '@/components/mobile/utils/wallet'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
@@ -248,7 +249,7 @@ function info(val: Order) {
|
||||
value: val.amount ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت هر گرم طلا برای مشتری (ریال)',
|
||||
key: metalLabel.goldGramPriceCustomer(),
|
||||
value: usePrice(val.price ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
@@ -256,7 +257,7 @@ function info(val: Order) {
|
||||
value: usePrice(val.total_price ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت هر گرم طلا بدون سود (ریال)',
|
||||
key: metalLabel.goldGramPriceOriginal(),
|
||||
value: usePrice(val.price_original ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
@@ -284,7 +285,7 @@ function info(val: Order) {
|
||||
value: usePrice(val.service_charge_price ?? 0)
|
||||
},*/
|
||||
{
|
||||
key: 'کارمزد طلایی',
|
||||
key: metalLabel.goldCommission(),
|
||||
value: val.service_charge ?? '-' + ' گرم'
|
||||
},
|
||||
/* {
|
||||
|
||||
@@ -93,7 +93,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
|
||||
title: 'عمومی و برند',
|
||||
icon: 'vuesax-linear:home-2',
|
||||
match: (k) =>
|
||||
/^(name_fa|name_en|app_url|admin_url|apk_download_link|banner_enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|has_page_earn_money)/.test(
|
||||
/^(name_fa|name_en|app_url|admin_url|apk_download_link|banner_enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|has_page_earn_money)/.test(
|
||||
k
|
||||
)
|
||||
},
|
||||
|
||||
@@ -123,9 +123,9 @@
|
||||
>
|
||||
<BasicInfoItem
|
||||
title="نام قلک"
|
||||
:value="item?.fund ? item?.fund?.title : 'کیف پول طلایی'"
|
||||
:value="item?.fund ? item?.fund?.title : metalLabel.goldWallet()"
|
||||
/>
|
||||
<BasicInfoItem title="مقدار طلا" :value="`${item?.gold} گرم`" />
|
||||
<BasicInfoItem :title="metalLabel.goldAmount()" :value="`${item?.gold} گرم`" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,6 +158,7 @@ import type { FormInstance } from 'ant-design-vue'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { message } from 'ant-design-vue/es'
|
||||
import type { Info } from '@/components/BasicInfoModal/model/Info'
|
||||
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
<CustomerMobileDetailSheet
|
||||
v-model:visible="sheetVisible"
|
||||
:item="selectedItem"
|
||||
:metals="catalogMetals"
|
||||
@info="forwardAndClose('info', $event)"
|
||||
@edit="forwardAndClose('edit', $event)"
|
||||
@archive="forwardAndClose('archive', $event)"
|
||||
@toggleActive="forward('toggleActive', $event)"
|
||||
@chargeMoney="forwardAndClose('chargeMoney', $event)"
|
||||
@chargeGold="forwardAndClose('chargeGold', $event)"
|
||||
@chargeWallet="forwardAndClose('chargeWallet', $event)"
|
||||
@toggleTrade="forward('toggleTrade', $event)"
|
||||
@confirmUser="forwardAndClose('confirmUser', $event)"
|
||||
/>
|
||||
@@ -40,14 +40,22 @@ import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileD
|
||||
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
|
||||
import CustomerMobileListItem from './CustomerMobileListItem.vue'
|
||||
import CustomerMobileDetailSheet from './CustomerMobileDetailSheet.vue'
|
||||
import type { CustomerMetalColumn } from '../hooks/customerColumns'
|
||||
|
||||
const catalogMetals = ref<CustomerMetalColumn[]>([])
|
||||
|
||||
async function fetchCustomers(params: Parameters<typeof getCustomerTable>[0]) {
|
||||
const data = await getCustomerTable(params)
|
||||
catalogMetals.value = data?.catalogMetals ?? []
|
||||
return data
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'info', item: Customer): void
|
||||
(e: 'edit', item: Customer): void
|
||||
(e: 'archive', item: Customer): void
|
||||
(e: 'toggleActive', item: Customer): void
|
||||
(e: 'chargeMoney', item: Customer): void
|
||||
(e: 'chargeGold', item: Customer): void
|
||||
(e: 'chargeWallet', item: Customer): void
|
||||
(e: 'toggleTrade', item: Customer): void
|
||||
(e: 'confirmUser', item: Customer): void
|
||||
}>()
|
||||
@@ -59,8 +67,7 @@ type SheetAction =
|
||||
| 'edit'
|
||||
| 'archive'
|
||||
| 'toggleActive'
|
||||
| 'chargeMoney'
|
||||
| 'chargeGold'
|
||||
| 'chargeWallet'
|
||||
| 'toggleTrade'
|
||||
| 'confirmUser'
|
||||
|
||||
@@ -74,7 +81,7 @@ function forwardAndClose(action: SheetAction, item: Customer) {
|
||||
}
|
||||
|
||||
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Customer>({
|
||||
fetchApi: getCustomerTable,
|
||||
fetchApi: fetchCustomers,
|
||||
onFetched: (rows) => syncSelectedItem(rows)
|
||||
})
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
<h2 class="text-base font-700 leading-relaxed text-gray-900">{{ row.name || '-' }}</h2>
|
||||
<p v-if="row.mobile" class="text-sm text-gray-500" dir="ltr">{{ row.mobile }}</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<BaseButton class="w-full" size="middle" type="primary" icon="vuesax-linear:wallet-add" @click="closeAndEmit('chargeMoney', row)">
|
||||
شارژ ریالی
|
||||
</BaseButton>
|
||||
<BaseButton class="w-full" size="middle" type="primary" icon="vuesax-linear:wallet-money" @click="closeAndEmit('chargeGold', row)">
|
||||
شارژ طلایی
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<BaseButton
|
||||
class="w-full"
|
||||
size="middle"
|
||||
type="primary"
|
||||
icon="vuesax-linear:wallet-add"
|
||||
@click="closeAndEmit('chargeWallet', row)"
|
||||
>
|
||||
شارژ / دشارژ کیف پول
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="!row.is_legal"
|
||||
@@ -51,7 +54,14 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<MobileStatCard bordered variant="success" label="موجودی طلایی" :value="`${goldBalance(row)} گرم`">
|
||||
<MobileStatCard
|
||||
v-for="entry in metalBalanceEntries(row)"
|
||||
:key="entry.metal_id"
|
||||
bordered
|
||||
variant="success"
|
||||
:label="customerWalletMetalBalanceTitle(entry.metal_id, entry.label)"
|
||||
:value="entry.value"
|
||||
>
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" color="#15803d" />
|
||||
</template>
|
||||
@@ -83,7 +93,6 @@
|
||||
<MobileDetailRow label="نوع کاربر" :value="row.type_str || '-'" />
|
||||
<MobileDetailRow label="کد معرف" :value="row.referral_id ?? '-'" />
|
||||
<MobileDetailRow label="نام معرف کاربر" :value="row.referral?.name ?? '-'" />
|
||||
<MobileDetailRow label="گنج (گرم)" :value="row.gold ?? '-'" />
|
||||
<MobileDetailRow label="آخرین بازدید" :value="lastSeen(row)" value-class="text-xs" />
|
||||
</MobileInfoGrid>
|
||||
</template>
|
||||
@@ -98,13 +107,17 @@ import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
|
||||
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
|
||||
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
|
||||
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
|
||||
import { getCashBalance, getGoldBalance } from '@/components/mobile/utils/wallet'
|
||||
import { getCashBalance } from '@/components/mobile/utils/wallet'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { customerWalletMetalBalanceTitle } from '@/utils/adminMetalLabels'
|
||||
import type { CustomerMetalColumn } from '../hooks/customerColumns'
|
||||
import { formatMetalDisplayValue, metalCellValue, metalDisplayUnit } from '@/utils/metalWeight'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
item: Customer | null
|
||||
metals?: CustomerMetalColumn[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -113,8 +126,7 @@ const emit = defineEmits<{
|
||||
(e: 'edit', item: Customer): void
|
||||
(e: 'archive', item: Customer): void
|
||||
(e: 'toggleActive', item: Customer): void
|
||||
(e: 'chargeMoney', item: Customer): void
|
||||
(e: 'chargeGold', item: Customer): void
|
||||
(e: 'chargeWallet', item: Customer): void
|
||||
(e: 'toggleTrade', item: Customer): void
|
||||
(e: 'confirmUser', item: Customer): void
|
||||
}>()
|
||||
@@ -124,7 +136,7 @@ const visibleProxy = computed({
|
||||
set: (value: boolean) => emit('update:visible', value)
|
||||
})
|
||||
|
||||
type SheetEmit = 'archive' | 'chargeMoney' | 'chargeGold' | 'info' | 'confirmUser' | 'edit'
|
||||
type SheetEmit = 'archive' | 'chargeWallet' | 'info' | 'confirmUser' | 'edit'
|
||||
|
||||
function closeAndEmit(event: SheetEmit, row: Customer) {
|
||||
visibleProxy.value = false
|
||||
@@ -157,8 +169,24 @@ function cashBalance(row: Customer) {
|
||||
return getCashBalance(row.wallet)
|
||||
}
|
||||
|
||||
function goldBalance(row: Customer) {
|
||||
return getGoldBalance(row.wallet)
|
||||
function metalBalanceEntries(row: Customer) {
|
||||
const metals = props.metals?.length
|
||||
? props.metals
|
||||
: [{ metal_id: 1, label: 'طلا' }]
|
||||
|
||||
return metals
|
||||
.map((metal) => {
|
||||
const value = metalCellValue(row as Record<string, unknown>, metal.metal_id, 'wallet_metal_gold')
|
||||
return {
|
||||
metal_id: metal.metal_id,
|
||||
label: metal.label,
|
||||
value: formatMetalDisplayValue(value, metal.metal_id)
|
||||
}
|
||||
})
|
||||
.filter((entry) => {
|
||||
const numeric = metalCellValue(row as Record<string, unknown>, entry.metal_id, 'wallet_metal_gold')
|
||||
return numeric !== 0
|
||||
})
|
||||
}
|
||||
|
||||
function lastSeen(row: Customer) {
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
class="wallet-charge-form"
|
||||
:class="formClass"
|
||||
>
|
||||
<a-form-item label="کیف پول / محصول" name="chargeTargetKey" :rules="targetRules">
|
||||
<a-select
|
||||
v-model:value="modelValue.chargeTargetKey"
|
||||
show-search
|
||||
placeholder="انتخاب کنید"
|
||||
class="w-full"
|
||||
:options="targetOptions"
|
||||
:filter-option="filterTarget"
|
||||
@change="onTargetChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="نوع عملیات" name="direction">
|
||||
<a-radio-group
|
||||
v-model:value="modelValue.direction"
|
||||
@@ -36,10 +48,10 @@
|
||||
<BasicInput
|
||||
v-model:value="modelValue.amountAbs"
|
||||
:addon-before="unit"
|
||||
:type="variant === 'money' ? 'price' : 'number'"
|
||||
:type="isMoney ? 'price' : 'number'"
|
||||
:place-holder="amountPlaceholder"
|
||||
/>
|
||||
<div v-if="variant === 'money' && Number(modelValue.amountAbs) > 10" class="wallet-charge-form__hint">
|
||||
<div v-if="isMoney && Number(modelValue.amountAbs) > 10" class="wallet-charge-form__hint">
|
||||
معادل {{ usePrice(Number(modelValue.amountAbs) / 10, true, 'تومان') }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
@@ -51,22 +63,46 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import type { Charge } from '@/model/wallet'
|
||||
import type { Charge, WalletChargeTarget } from '@/model/wallet'
|
||||
import { inputRules } from '@/utils/inputRules'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { applyChargeTarget, resolveChargeTarget, walletPreviewLabel } from '../utils/walletChargeForm'
|
||||
|
||||
const props = defineProps<{
|
||||
variant: 'money' | 'gold'
|
||||
modelValue: Charge
|
||||
targets: WalletChargeTarget[]
|
||||
}>()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const unit = computed(() => (props.variant === 'money' ? 'ریال' : 'گرم'))
|
||||
const selectedTarget = computed(() =>
|
||||
resolveChargeTarget(props.targets, props.modelValue.chargeTargetKey)
|
||||
)
|
||||
|
||||
const isMoney = computed(() => selectedTarget.value?.kind === 'money')
|
||||
const unit = computed(() => selectedTarget.value?.unit ?? 'ریال')
|
||||
const isDischarge = computed(() => props.modelValue.direction === 'discharge')
|
||||
|
||||
const targetOptions = computed(() =>
|
||||
props.targets.map((target) => ({
|
||||
value: target.key,
|
||||
label: target.label
|
||||
}))
|
||||
)
|
||||
|
||||
const targetRules = [
|
||||
{
|
||||
validator: (_rule: unknown, value: string | undefined) => {
|
||||
if (!value || !resolveChargeTarget(props.targets, value)) {
|
||||
return Promise.reject('کیف پول یا محصول را انتخاب کنید')
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const formClass = computed(() =>
|
||||
isDischarge.value ? 'wallet-charge-form--discharge' : 'wallet-charge-form--charge'
|
||||
)
|
||||
@@ -81,8 +117,7 @@ const previewSign = computed(() => (isDischarge.value ? '−' : '+'))
|
||||
|
||||
const previewText = computed(() => {
|
||||
const action = isDischarge.value ? 'کسر از' : 'افزایش'
|
||||
const wallet = props.variant === 'money' ? 'موجودی ریالی' : 'موجودی طلایی'
|
||||
return `${action} ${wallet} کاربر`
|
||||
return `${action} ${walletPreviewLabel(selectedTarget.value)}`
|
||||
})
|
||||
|
||||
const amountLabel = computed(() =>
|
||||
@@ -92,7 +127,7 @@ const amountLabel = computed(() =>
|
||||
const noteLabel = computed(() => (isDischarge.value ? 'علت دشارژ' : 'علت شارژ'))
|
||||
|
||||
const amountPlaceholder = computed(() =>
|
||||
props.variant === 'money' ? 'مثلاً ۱۰۰٬۰۰۰' : 'مثلاً ۰.۵'
|
||||
isMoney.value ? 'مثلاً ۱۰۰٬۰۰۰' : 'مثلاً ۰.۵'
|
||||
)
|
||||
|
||||
const notePlaceholder = computed(() =>
|
||||
@@ -112,6 +147,28 @@ const amountRules = [
|
||||
}
|
||||
]
|
||||
|
||||
function filterTarget(input: string, option?: { label?: string }) {
|
||||
return String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
|
||||
function onTargetChange(key: string) {
|
||||
const target = resolveChargeTarget(props.targets, key)
|
||||
if (!target) return
|
||||
Object.assign(props.modelValue, applyChargeTarget(props.modelValue, target))
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.targets,
|
||||
(targets) => {
|
||||
if (!targets.length) return
|
||||
const current = resolveChargeTarget(targets, props.modelValue.chargeTargetKey)
|
||||
if (!current) {
|
||||
Object.assign(props.modelValue, applyChargeTarget(props.modelValue, targets[0]))
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
validateFields: () => formRef.value?.validateFields()
|
||||
})
|
||||
|
||||
@@ -49,14 +49,10 @@
|
||||
:color="record.active ? '#54AD06' : '#ad0627'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('chargeMoney', record)">
|
||||
<template #title><span>شارژ کیف پول ریالی</span></template>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('chargeWallet', record)">
|
||||
<template #title><span>شارژ / دشارژ کیف پول</span></template>
|
||||
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('chargeGold', record)">
|
||||
<template #title><span>شارژ کیف پول طلایی</span></template>
|
||||
<Icon icon="vuesax-linear:wallet-money" color="#ea580c" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title><span>مشخصات</span></template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
@@ -100,8 +96,7 @@
|
||||
@edit="emits('edit', $event)"
|
||||
@archive="handleArchive"
|
||||
@toggleActive="(item) => changeCustomerActivationFunction(item.id)"
|
||||
@chargeMoney="emits('chargeMoney', $event)"
|
||||
@chargeGold="emits('chargeGold', $event)"
|
||||
@chargeWallet="emits('chargeWallet', $event)"
|
||||
@toggleTrade="(item) => changeCustomerOrderActivationFunction(item.id)"
|
||||
@confirmUser="openConfirmUser"
|
||||
/>
|
||||
@@ -144,7 +139,7 @@ import {
|
||||
confirmUser,
|
||||
getCustomerTable
|
||||
} from '@/api/customer'
|
||||
import { cols } from '@/pages/menuPages/customers/base/hooks/customerColumns'
|
||||
import { buildCustomerColumns } from '@/pages/menuPages/customers/base/hooks/customerColumns'
|
||||
import CustomerMobileCardList from '@/pages/menuPages/customers/base/components/CustomerMobileCardList.vue'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
||||
@@ -155,7 +150,7 @@ import { MODAL_ABOVE_ACTION_SHEET_Z_INDEX } from '@/constants/overlayZIndex'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeMoney', 'chargeGold'])
|
||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeWallet'])
|
||||
|
||||
const loading = ref(false)
|
||||
const confirmVisible = ref(false)
|
||||
@@ -163,8 +158,13 @@ const confirmInfo = ref<Customer>()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
|
||||
const tblProps = ref<any>({
|
||||
columns: cols,
|
||||
api: (params: TableInput) => getCustomerTable(params)
|
||||
columns: buildCustomerColumns(),
|
||||
api: async (params: TableInput) => {
|
||||
const data = await getCustomerTable(params)
|
||||
const metals = data?.catalogMetals ?? []
|
||||
tblProps.value.columns = buildCustomerColumns(metals)
|
||||
return data
|
||||
}
|
||||
})
|
||||
|
||||
const fetch = async () => {
|
||||
|
||||
@@ -1,212 +1,164 @@
|
||||
import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import {toJalali, toJalaliNoTime, toTime} from '@core/utils/useDataTime'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { getCashBalance, getGoldBalance } from '@/components/mobile/utils/wallet'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
import type { DocItem } from '@/model/docType'*/
|
||||
import { getCashBalance } from '@/components/mobile/utils/wallet'
|
||||
import { customerWalletMetalBalanceTitle } from '@/utils/adminMetalLabels'
|
||||
import { formatMetalDisplayValue, metalCellValue } from '@/utils/metalWeight'
|
||||
|
||||
//define store
|
||||
//const store = useStaticApiListsStore()
|
||||
//document types
|
||||
/*let docTypes: DocItem[]*/
|
||||
/*onMounted(async () => {
|
||||
docTypes = await store.readyDocTypeList()
|
||||
})
|
||||
const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
return {
|
||||
label: x.title,
|
||||
value: x.id
|
||||
}
|
||||
})*/
|
||||
export type CustomerMetalColumn = { metal_id: number; label: string; display_unit?: string }
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: any) => {
|
||||
return record.index + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'کد کاربری',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
// className: 'column-money'
|
||||
},
|
||||
{
|
||||
title: 'نام و نام خانوادگی',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'mobile',
|
||||
key: 'mobile',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'نوع کاربر',
|
||||
dataIndex: 'type_str',
|
||||
key: 'type_str',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'کد معرف',
|
||||
dataIndex: 'referral_id',
|
||||
key: 'referral_id',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'نام معرف کاربر',
|
||||
dataIndex: 'referral.name',
|
||||
key: 'referral.name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'موجودی ریالی (ریال)',
|
||||
dataIndex: 'wallet.cash',
|
||||
key: 'wallet.cash',
|
||||
type: 'Number',
|
||||
customRender(data) {
|
||||
return usePrice(getCashBalance(data.record?.wallet, { walletCash: data.record?.wallet_cash }), true, '')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'موجودی طلایی (گرم)',
|
||||
dataIndex: 'wallet.gold',
|
||||
key: 'wallet.gold',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
customRender(data) {
|
||||
return getGoldBalance(data.record?.wallet, { walletGold: data.record?.wallet_gold })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'اخرین بازدید',
|
||||
dataIndex: 'last_seen_at',
|
||||
key: 'last_seen_at',
|
||||
type: 'DateTime',
|
||||
customRender(data) {
|
||||
return toJalali(data.record.last_seen_at)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'گنج(گرم)',
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
},
|
||||
{
|
||||
title: 'وضعیت کاربر',
|
||||
dataIndex: 'active',
|
||||
key: 'active',
|
||||
type: 'Boolean',
|
||||
customRender: ({ record }) => {
|
||||
return record.active ? (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
|
||||
فعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
|
||||
غیر فعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'وضعیت سفارش دهی',
|
||||
dataIndex: 'trade_active',
|
||||
key: 'trade_active',
|
||||
sortable: false,
|
||||
type: 'Boolean',
|
||||
customRender: ({ record }) => {
|
||||
return record.trade_active ? (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
|
||||
مجاز
|
||||
</CustomTag>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
|
||||
غیر مجاز
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
/*{
|
||||
title: 'نقش',
|
||||
dataIndex: 'user_type_record',
|
||||
key: 'user_type_record',
|
||||
type: 'Select',
|
||||
export function buildCustomerColumns(metals: CustomerMetalColumn[] = []): BaseTableColumn[] {
|
||||
const baseColumns: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
filterComponentProps: {
|
||||
multiple: true
|
||||
},
|
||||
filterField: 'user_type_obj.id',
|
||||
filterComponent: ReadyUserTypeSelect,
|
||||
/!* customRender: (record: any) => {
|
||||
return record.record.user_type_record?.value
|
||||
}*!/
|
||||
customRender: ({ record }) => {
|
||||
const items = record.is_admin ? (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
ادمین
|
||||
</CustomTag>
|
||||
) : (
|
||||
record.user_type_obj.map((item: { title: any }, index: any) => {
|
||||
if (item) {
|
||||
return (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
{item?.title}
|
||||
</CustomTag>
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
return <div class="flex justify-center items-center gap-2">{items}</div>
|
||||
customRender: (record: any) => {
|
||||
return record.index + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'حوزه',
|
||||
dataIndex: 'section',
|
||||
key: 'section'
|
||||
title: 'کد کاربری',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'نام و نام خانوادگی',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'mobile',
|
||||
type: 'Number',
|
||||
key: 'mobile'
|
||||
key: 'mobile',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'تاریخ ثبت',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (record: any) => {
|
||||
return toJalaliNoTime(record.record.created_at)
|
||||
title: 'نوع کاربر',
|
||||
dataIndex: 'type_str',
|
||||
key: 'type_str',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'کد معرف',
|
||||
dataIndex: 'referral_id',
|
||||
key: 'referral_id',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'نام معرف کاربر',
|
||||
dataIndex: 'referral.name',
|
||||
key: 'referral.name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'موجودی ریالی (ریال)',
|
||||
dataIndex: 'wallet.cash',
|
||||
key: 'wallet.cash',
|
||||
type: 'Number',
|
||||
customRender(data) {
|
||||
return usePrice(getCashBalance(data.record?.wallet, { walletCash: data.record?.wallet_cash }), true, '')
|
||||
}
|
||||
},*/
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
slot: 'action',
|
||||
filterable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const metalColumns: BaseTableColumn[] =
|
||||
metals.length > 0
|
||||
? metals.map((metal) => ({
|
||||
title: customerWalletMetalBalanceTitle(metal.metal_id, metal.label),
|
||||
dataIndex: `wallet_metal_gold_${metal.metal_id}`,
|
||||
key: `wallet_metal_${metal.metal_id}`,
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
customRender(data) {
|
||||
const value = metalCellValue(data.record, metal.metal_id, 'wallet_metal_gold')
|
||||
return formatMetalDisplayValue(value, metal.metal_id)
|
||||
}
|
||||
}))
|
||||
: [
|
||||
{
|
||||
title: customerWalletMetalBalanceTitle(1, 'طلا'),
|
||||
dataIndex: 'wallet.gold',
|
||||
key: 'wallet.gold',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
customRender(data) {
|
||||
const value = Number(data.record?.wallet_gold ?? 0)
|
||||
return formatMetalDisplayValue(value, 1)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
return [
|
||||
...baseColumns,
|
||||
...metalColumns,
|
||||
{
|
||||
title: 'اخرین بازدید',
|
||||
dataIndex: 'last_seen_at',
|
||||
key: 'last_seen_at',
|
||||
type: 'DateTime',
|
||||
customRender(data) {
|
||||
return toJalali(data.record.last_seen_at)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'وضعیت کاربر',
|
||||
dataIndex: 'active',
|
||||
key: 'active',
|
||||
type: 'Boolean',
|
||||
customRender: ({ record }) => {
|
||||
return record.active ? (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
|
||||
فعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
|
||||
غیر فعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'وضعیت سفارش دهی',
|
||||
dataIndex: 'trade_active',
|
||||
key: 'trade_active',
|
||||
sortable: false,
|
||||
type: 'Boolean',
|
||||
customRender: ({ record }) => {
|
||||
return record.trade_active ? (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
|
||||
مجاز
|
||||
</CustomTag>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
|
||||
غیر مجاز
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
slot: 'action',
|
||||
filterable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function getCustomerColumns(): BaseTableColumn[] {
|
||||
return buildCustomerColumns()
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
@info="info"
|
||||
@edit="edit"
|
||||
@add="add"
|
||||
@chargeMoney="walletMoneyCharge"
|
||||
@chargeGold="walletGoldCharge"
|
||||
@chargeWallet="walletCharge"
|
||||
/>
|
||||
|
||||
<!-- <BasicMenu :menu="menu" width="13rem" @onChange="tabSelected" />
|
||||
@@ -66,9 +65,6 @@
|
||||
<a-form-item label="میزان پاداش" name="profit_amount">
|
||||
<BasicInput v-model:value="form.profit_amount" />
|
||||
</a-form-item>
|
||||
<a-form-item label="گنج" name="gold">
|
||||
<BasicInput v-model:value="form.gold" />
|
||||
</a-form-item>
|
||||
<a-form-item label="حداکثر معرفی" name="add_user_limit">
|
||||
<BasicInput v-model:value="form.add_user_limit" />
|
||||
</a-form-item>
|
||||
@@ -191,9 +187,9 @@
|
||||
>
|
||||
<BasicInfoItem
|
||||
title="نام قلک"
|
||||
:value="item?.fund ? item?.fund?.title : 'کیف پول طلایی'"
|
||||
:value="item?.fund ? item?.fund?.title : metalLabel.goldWallet()"
|
||||
/>
|
||||
<BasicInfoItem title="مقدار طلا" :value="`${item?.gold} گرم`" />
|
||||
<BasicInfoItem :title="metalLabel.goldAmount()" :value="`${item?.gold} گرم`" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,60 +213,39 @@
|
||||
</BasicActionSheet>
|
||||
<BasicActionSheet
|
||||
v-if="isMobile"
|
||||
v-model:visible="showMoneyWalletCharge"
|
||||
:title="moneyChargeTitle"
|
||||
modal-width="24rem"
|
||||
v-model:visible="showWalletCharge"
|
||||
:title="walletChargeModalTitle"
|
||||
modal-width="26rem"
|
||||
:loading="loading"
|
||||
:show-button="true"
|
||||
:show-cancel="true"
|
||||
:show-ok="true"
|
||||
:ok-text="moneyChargeOkText"
|
||||
:ok-text="walletChargeOkLabel"
|
||||
cancel-text="انصراف"
|
||||
@ok="chargeMoneyWallet"
|
||||
@close="showMoneyWalletCharge = false"
|
||||
@ok="submitWalletCharge"
|
||||
@close="closeWalletCharge"
|
||||
>
|
||||
<WalletChargeForm ref="moneyChargeFormRef" :model-value="formCharge" variant="money" />
|
||||
<WalletChargeForm
|
||||
ref="walletChargeFormRef"
|
||||
:model-value="formCharge"
|
||||
:targets="chargeTargets"
|
||||
/>
|
||||
</BasicActionSheet>
|
||||
<BasicModal
|
||||
v-else
|
||||
v-model:visible="showMoneyWalletCharge"
|
||||
width="22rem"
|
||||
:title="moneyChargeTitle"
|
||||
:ok-text="moneyChargeOkText"
|
||||
@ok="chargeMoneyWallet"
|
||||
v-model:visible="showWalletCharge"
|
||||
width="24rem"
|
||||
:title="walletChargeModalTitle"
|
||||
:ok-text="walletChargeOkLabel"
|
||||
@ok="submitWalletCharge"
|
||||
:loading="loading"
|
||||
@close="showMoneyWalletCharge = false"
|
||||
@close="closeWalletCharge"
|
||||
>
|
||||
<WalletChargeForm ref="moneyChargeFormRef" :model-value="formCharge" variant="money" />
|
||||
</BasicModal>
|
||||
|
||||
<BasicActionSheet
|
||||
v-if="isMobile"
|
||||
v-model:visible="showGoldWalletCharge"
|
||||
:title="goldChargeTitle"
|
||||
modal-width="24rem"
|
||||
:loading="loading"
|
||||
:show-button="true"
|
||||
:show-cancel="true"
|
||||
:show-ok="true"
|
||||
:ok-text="goldChargeOkText"
|
||||
cancel-text="انصراف"
|
||||
@ok="chargeGoldWallet"
|
||||
@close="showGoldWalletCharge = false"
|
||||
>
|
||||
<WalletChargeForm ref="goldChargeFormRef" :model-value="formCharge" variant="gold" />
|
||||
</BasicActionSheet>
|
||||
<BasicModal
|
||||
v-else
|
||||
v-model:visible="showGoldWalletCharge"
|
||||
width="22rem"
|
||||
:title="goldChargeTitle"
|
||||
:ok-text="goldChargeOkText"
|
||||
@ok="chargeGoldWallet"
|
||||
:loading="loading"
|
||||
@close="showGoldWalletCharge = false"
|
||||
>
|
||||
<WalletChargeForm ref="goldChargeFormRef" :model-value="formCharge" variant="gold" />
|
||||
<WalletChargeForm
|
||||
ref="walletChargeFormRef"
|
||||
:model-value="formCharge"
|
||||
:targets="chargeTargets"
|
||||
/>
|
||||
</BasicModal>
|
||||
<BasicInfoModal
|
||||
v-model:visible="userInfoOrderVisible"
|
||||
@@ -302,9 +277,9 @@ import { cloneDeep } from 'lodash'
|
||||
import type { Item } from '@/model/Item'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { createCustomer, deleteCustomer, updateCustomer } from '@/api/customer'
|
||||
import type { Charge } from '@/model/wallet'
|
||||
import type { Charge, WalletChargeTarget } from '@/model/wallet'
|
||||
import customers from '@/router/routes/customers'
|
||||
import { chargeWallet } from '@/api/wallet'
|
||||
import { chargeWalletUnified, getWalletChargeTargets } from '@/api/wallet'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import jalaliday from 'jalaliday'
|
||||
import ordersTbl from '@/pages/menuPages/orders/components/table.vue'
|
||||
@@ -317,12 +292,15 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import WalletChargeForm from './components/WalletChargeForm.vue'
|
||||
import {
|
||||
applyChargeTarget,
|
||||
buildChargePayload,
|
||||
createEmptyChargeForm,
|
||||
resolveChargeTarget,
|
||||
walletChargeOkText,
|
||||
walletChargeSuccessMessage,
|
||||
walletChargeTitle
|
||||
} from './utils/walletChargeForm'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
//tabs
|
||||
const activeKey = ref('اطلاعات جامع')
|
||||
@@ -376,21 +354,23 @@ const customerItems: Array<Item> = [
|
||||
]
|
||||
|
||||
//wallet
|
||||
const showMoneyWalletCharge = ref<boolean>(false)
|
||||
const showGoldWalletCharge = ref<boolean>(false)
|
||||
const showWalletCharge = ref<boolean>(false)
|
||||
const chargeTargets = ref<WalletChargeTarget[]>([])
|
||||
const userInfoOrderVisible = ref<boolean>(false)
|
||||
|
||||
//form
|
||||
const form = ref<Partial<Customer>>({})
|
||||
const formRef = ref<FormInstance>()
|
||||
const formCharge = ref<Charge>({})
|
||||
const moneyChargeFormRef = ref<InstanceType<typeof WalletChargeForm>>()
|
||||
const goldChargeFormRef = ref<InstanceType<typeof WalletChargeForm>>()
|
||||
const walletChargeFormRef = ref<InstanceType<typeof WalletChargeForm>>()
|
||||
|
||||
const moneyChargeTitle = computed(() => walletChargeTitle('money', formCharge.value.direction))
|
||||
const goldChargeTitle = computed(() => walletChargeTitle('gold', formCharge.value.direction))
|
||||
const moneyChargeOkText = computed(() => walletChargeOkText(formCharge.value.direction))
|
||||
const goldChargeOkText = computed(() => walletChargeOkText(formCharge.value.direction))
|
||||
const selectedChargeTarget = computed(() =>
|
||||
resolveChargeTarget(chargeTargets.value, formCharge.value.chargeTargetKey)
|
||||
)
|
||||
const walletChargeModalTitle = computed(() =>
|
||||
walletChargeTitle(selectedChargeTarget.value, formCharge.value.direction)
|
||||
)
|
||||
const walletChargeOkLabel = computed(() => walletChargeOkText(formCharge.value.direction))
|
||||
|
||||
//info
|
||||
let infoParams = reactive<Info>({})
|
||||
@@ -407,7 +387,7 @@ function infoOrder(val: Order) {
|
||||
value: val.amount ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت هر گرم طلا برای مشتری (ریال)',
|
||||
key: metalLabel.goldGramPriceCustomer(),
|
||||
value: usePrice(val.price ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
@@ -415,7 +395,7 @@ function infoOrder(val: Order) {
|
||||
value: usePrice(val.total_price ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
key: '/قیمت هر گرم طلا بدون سود (ریال)',
|
||||
key: metalLabel.goldGramPriceOriginalSlash(),
|
||||
value: usePrice(val.price_original ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
@@ -443,7 +423,7 @@ function infoOrder(val: Order) {
|
||||
value: usePrice(val.service_charge_price ?? 0)
|
||||
},*/
|
||||
{
|
||||
key: 'کارمزد طلایی',
|
||||
key: metalLabel.goldCommission(),
|
||||
value: val.service_charge ?? '-' + ' گرم'
|
||||
},
|
||||
/* {
|
||||
@@ -511,40 +491,43 @@ async function add() {
|
||||
|
||||
function resetFormCharge(customerId: number) {
|
||||
formCharge.value = createEmptyChargeForm(customerId)
|
||||
const firstTarget = chargeTargets.value[0]
|
||||
if (firstTarget) {
|
||||
Object.assign(formCharge.value, applyChargeTarget(formCharge.value, firstTarget))
|
||||
}
|
||||
}
|
||||
|
||||
async function walletMoneyCharge(val: Customer) {
|
||||
resetFormCharge(val.id)
|
||||
showMoneyWalletCharge.value = true
|
||||
async function loadChargeTargets() {
|
||||
if (chargeTargets.value.length) return
|
||||
const data = await getWalletChargeTargets()
|
||||
chargeTargets.value = data?.targets ?? []
|
||||
}
|
||||
|
||||
async function walletGoldCharge(val: Customer) {
|
||||
resetFormCharge(val.id)
|
||||
showGoldWalletCharge.value = true
|
||||
async function walletCharge(val: Customer) {
|
||||
try {
|
||||
loading.value = true
|
||||
await loadChargeTargets()
|
||||
resetFormCharge(val.id)
|
||||
showWalletCharge.value = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
message.error('خطا در بارگذاری گزینههای شارژ')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function chargeMoneyWallet() {
|
||||
moneyChargeFormRef.value?.validateFields()?.then(async () => {
|
||||
function closeWalletCharge() {
|
||||
showWalletCharge.value = false
|
||||
}
|
||||
|
||||
async function submitWalletCharge() {
|
||||
walletChargeFormRef.value?.validateFields()?.then(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
await chargeWallet(buildChargePayload(formCharge.value), 'money')
|
||||
await chargeWalletUnified(buildChargePayload(formCharge.value))
|
||||
message.success(walletChargeSuccessMessage(formCharge.value.direction))
|
||||
showMoneyWalletCharge.value = false
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function chargeGoldWallet() {
|
||||
goldChargeFormRef.value?.validateFields()?.then(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
await chargeWallet(buildChargePayload(formCharge.value), 'gold')
|
||||
message.success(walletChargeSuccessMessage(formCharge.value.direction))
|
||||
showGoldWalletCharge.value = false
|
||||
showWalletCharge.value = false
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Charge, WalletChargeDirection } from '@/model/wallet'
|
||||
import type { Charge, WalletChargeDirection, WalletChargeTarget } from '@/model/wallet'
|
||||
|
||||
export function createEmptyChargeForm(customerId?: number): Charge {
|
||||
return {
|
||||
@@ -7,7 +7,9 @@ export function createEmptyChargeForm(customerId?: number): Charge {
|
||||
note: undefined,
|
||||
talabBedehi: false,
|
||||
direction: 'charge',
|
||||
amountAbs: undefined
|
||||
amountAbs: undefined,
|
||||
chargeTargetKey: 'money',
|
||||
product_id: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +20,35 @@ export function buildChargePayload(form: Charge): Charge {
|
||||
value: signed,
|
||||
note: form.note,
|
||||
customer_id: form.customer_id,
|
||||
talabBedehi: form.talabBedehi === true
|
||||
talabBedehi: form.talabBedehi === true,
|
||||
product_id: form.product_id ?? null
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveChargeTarget(
|
||||
targets: WalletChargeTarget[],
|
||||
key?: string | null
|
||||
): WalletChargeTarget | undefined {
|
||||
if (!key) return targets[0]
|
||||
return targets.find((target) => target.key === key)
|
||||
}
|
||||
|
||||
export function applyChargeTarget(form: Charge, target?: WalletChargeTarget): Charge {
|
||||
if (!target) return form
|
||||
return {
|
||||
...form,
|
||||
chargeTargetKey: target.key,
|
||||
product_id: target.kind === 'metal' ? target.product_id ?? null : null
|
||||
}
|
||||
}
|
||||
|
||||
export function walletChargeTitle(
|
||||
variant: 'money' | 'gold',
|
||||
target: WalletChargeTarget | undefined,
|
||||
direction: WalletChargeDirection = 'charge'
|
||||
) {
|
||||
const wallet = variant === 'money' ? 'ریالی' : 'طلایی'
|
||||
const action = direction === 'discharge' ? 'دشارژ' : 'شارژ'
|
||||
return `${action} کیف پول ${wallet}`
|
||||
const wallet = target?.label ?? 'کیف پول'
|
||||
return `${action} ${wallet}`
|
||||
}
|
||||
|
||||
export function walletChargeOkText(direction: WalletChargeDirection = 'charge') {
|
||||
@@ -38,3 +58,10 @@ export function walletChargeOkText(direction: WalletChargeDirection = 'charge')
|
||||
export function walletChargeSuccessMessage(direction: WalletChargeDirection = 'charge') {
|
||||
return direction === 'discharge' ? 'دشارژ با موفقیت انجام شد' : 'شارژ با موفقیت انجام شد'
|
||||
}
|
||||
|
||||
export function walletPreviewLabel(target: WalletChargeTarget | undefined): string {
|
||||
if (!target) return 'موجودی کاربر'
|
||||
if (target.kind === 'money') return 'موجودی ریالی'
|
||||
if (target.uses_main_wallet) return `موجودی ${target.label.replace(/\s*\(کیف اصلی\)$/, '')} (کیف اصلی)`
|
||||
return `موجودی ${target.label}`
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
|
||||
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -82,7 +83,7 @@ function statusClass(row: Giftcardcode) {
|
||||
}
|
||||
|
||||
function unitLabel(row: Giftcardcode) {
|
||||
return row.unit === 2 ? 'طلایی' : 'ریالی'
|
||||
return row.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'
|
||||
}
|
||||
|
||||
function unitTagClass(row: Giftcardcode) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Giftcardcode } from '@/model/giftCard'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
const props = defineProps<{
|
||||
item: Giftcardcode
|
||||
@@ -50,7 +51,7 @@ const statusClass = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const unitLabel = computed(() => (props.item.unit === 2 ? 'طلایی' : 'ریالی'))
|
||||
const unitLabel = computed(() => (props.item.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'))
|
||||
const unitTagClass = computed(() =>
|
||||
props.item.unit === 2
|
||||
? 'border-amber-300 bg-amber-50 text-amber-700'
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<CustomTag v-else textColor="#C53030" bgColor="#FECACA">غیرفعال</CustomTag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'unit'">
|
||||
<CustomTag v-if="record.unit === 2" textColor="#C53030" bgColor="#FECACA">طلایی</CustomTag>
|
||||
<CustomTag v-if="record.unit === 2" textColor="#C53030" bgColor="#FECACA">{{ metalLabel.goldWalletShort() }}</CustomTag>
|
||||
<CustomTag v-else textColor="#25855A" bgColor="#E3F4E9">ریالی</CustomTag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
@@ -68,6 +68,7 @@ import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import GiftCardCodeMobileDetailSheet from './GiftCardCodeMobileDetailSheet.vue'
|
||||
import GiftCardCodeMobileListItem from './GiftCardCodeMobileListItem.vue'
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<a-form-item label="نوع کارت هدیه" name="unit" :rules="inputRules()">
|
||||
<a-radio-group v-model:value="form.unit" button-style="solid">
|
||||
<a-radio-button value="1">ریالی</a-radio-button>
|
||||
<a-radio-button value="2">طلایی</a-radio-button>
|
||||
<a-radio-button value="2">{{ metalLabel.goldWalletShort() }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import type { GiftcardInput } from '@/model/giftCard'
|
||||
import { inputLenRules, inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
defineProps<{
|
||||
form: GiftcardInput
|
||||
|
||||
@@ -63,6 +63,7 @@ import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali, toJalaliNoTime } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
|
||||
|
||||
@@ -83,7 +84,7 @@ const visibleProxy = computed({
|
||||
const sheetTitle = computed(() => props.item?.unique_code || `کارت هدیه #${props.item?.id ?? '-'}`)
|
||||
|
||||
function unitLabel(row: GiftCardRow) {
|
||||
return row.unit === 2 ? 'طلایی' : 'ریالی'
|
||||
return row.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'
|
||||
}
|
||||
|
||||
function unitTagClass(row: GiftCardRow) {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
import type { Giftcard } from '@/model/giftCard'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
|
||||
|
||||
@@ -44,7 +45,7 @@ defineEmits<{
|
||||
(e: 'giftCardCodes', item: GiftCardRow): void
|
||||
}>()
|
||||
|
||||
const unitLabel = computed(() => (props.item.unit === 2 ? 'طلایی' : 'ریالی'))
|
||||
const unitLabel = computed(() => (props.item.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'))
|
||||
const unitTagClass = computed(() =>
|
||||
props.item.unit === 2
|
||||
? 'border-amber-300 bg-amber-50 text-amber-700'
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { cols } from '../hooks/orderColumns'
|
||||
import { getGiftCardColumns } from '../hooks/orderColumns'
|
||||
import { usePrefix } from '@core/composable/usePrefix'
|
||||
import { getGiftCardTable } from '@/api/giftCard'
|
||||
import GiftCardMobileCardList from './GiftCardMobileCardList.vue'
|
||||
@@ -79,7 +79,7 @@ const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
const { prefixCls } = usePrefix('pophoverstatus')
|
||||
|
||||
const tblProps = ref<any>({
|
||||
columns: cols,
|
||||
columns: getGiftCardColumns(),
|
||||
api: (params: TableInput) => {
|
||||
if (props.customerId) {
|
||||
params.filters = []
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
@@ -21,7 +22,8 @@ const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
}
|
||||
})*/
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
export function getGiftCardColumns(): BaseTableColumn[] {
|
||||
return [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
@@ -50,7 +52,7 @@ export const cols: BaseTableColumn[] = [
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
طلایی
|
||||
{metalLabel.goldWalletShort()}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
@@ -236,4 +238,5 @@ export const cols: BaseTableColumn[] = [
|
||||
fixed: 'right',
|
||||
filterable: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ import type { Giftcard, Giftcardcode, GiftcardInput } from '@/model/giftCard'
|
||||
import { createGiftCard } from '@/api/giftCard'
|
||||
import dayjs from 'dayjs'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { isStoreApp } from '@/utils/config'
|
||||
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
import BasicInput from '@/components/BasicInput.vue'
|
||||
@@ -174,7 +175,7 @@ function info(val: Giftcard) {
|
||||
},
|
||||
{
|
||||
key: 'نوع کارت هدیه',
|
||||
value: val.unit === 3 ? 'تخفیف' : val.unit === 2 ? 'طلایی' : 'ریالی'
|
||||
value: val.unit === 3 ? 'تخفیف' : val.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'
|
||||
},
|
||||
{
|
||||
key: 'تعداد کدها',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<a-form :model="form" ref="formRef" autocomplete="off">
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-col items-center gap-1 rounded-xl bg-gray-50 px-4 py-3 sm:flex-row sm:justify-center sm:gap-4">
|
||||
<span class="text-sm text-gray-600">قیمت {{ form.direction === 1 ? 'خرید' : 'فروش' }} طلا:</span>
|
||||
<span class="text-sm text-gray-600">{{ metalLabel.goldTradeBuySell(form.direction) }}</span>
|
||||
<span class="text-sm font-700 text-gray-900">{{ marketPriceLabel }}</span>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
@change="$emit('changeUser', $event)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<BasicInfoItem is-line title="کیف پول طلا" :value="selectedCustomer?.wallet?.[0]?.gold ?? '_'" />
|
||||
<BasicInfoItem is-line :title="metalLabel.goldWalletTitle()" :value="selectedCustomer?.wallet?.[0]?.gold ?? '_'" />
|
||||
<BasicInfoItem is-line title="کیف پول ریالی" :value="selectedCustomer?.wallet?.[0]?.cash ?? '_'" />
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
addon-before="گرم"
|
||||
addon-bg-color="#0A1B37"
|
||||
addon-text-color="white"
|
||||
place-holder="مقدار طلا"
|
||||
:place-holder="metalLabel.goldPlaceholder()"
|
||||
type="tel"
|
||||
@onchange="$emit('calculateCash', $event)"
|
||||
/>
|
||||
@@ -88,6 +88,7 @@ import type { Order } from '@/model/order'
|
||||
import type { last } from '@/model/price'
|
||||
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
const props = defineProps<{
|
||||
form: Order
|
||||
|
||||
@@ -26,23 +26,18 @@
|
||||
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
<MobileStatCard bordered variant="warning" label="مقدار (گرم)" :value="`${row.amount ?? 0} گرم`">
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" color="#b45309" />
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
<MobileStatCard bordered variant="warning" label="مقدار" :value="amountLabel(row)" />
|
||||
<MobileStatCard bordered label="قیمت زمان معامله (ریال)" :value="usePrice(row.price ?? 0, true, '')" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<MobileStatCard bordered label="قیمت هر گرم طلا برای مشتری (ریال)" :value="usePrice(row.price ?? 0, true, '')" />
|
||||
<MobileStatCard bordered label="قیمت هر گرم طلا بدون سود (ریال)" :value="usePrice(row.price_original ?? 0, true, '')" />
|
||||
<!-- <MobileStatCard bordered label="قیمت کل لیمان (ریال)" :value="usePrice(row.total_price_liman ?? 0, true, '')" />-->
|
||||
<MobileStatCard bordered label="کارمزد" :value="serviceChargeLabel(row)" />
|
||||
<MobileStatCard bordered label="قیمت کل بدون سود (ریال)" :value="usePrice(row.total_price_original ?? 0, true, '')" />
|
||||
<MobileStatCard bordered label="سود هر گرم (ریال)" :value="usePrice(row.fee ?? 0, true, '')" />
|
||||
<MobileStatCard bordered label="کارمزد طلایی" :value="serviceChargeLabel(row)" />
|
||||
<MobileStatCard bordered label="سود واحد (ریال)" :value="usePrice(row.fee ?? 0, true, '')" />
|
||||
</div>
|
||||
|
||||
<MobileInfoGrid>
|
||||
<MobileDetailRow label="نوع فلز" :value="row.order_metal_label ?? '-'" />
|
||||
<MobileDetailRow label="کد سفارش" :value="row.id ?? '-'" />
|
||||
<MobileDetailRow label="شماره موبایل" :value="row.customer?.mobile || '-'" />
|
||||
<MobileDetailRow label="سود کل (ریال)" :value="usePrice(row.total_fee ?? 0, true, '')" />
|
||||
@@ -66,6 +61,7 @@ import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
|
||||
type OrderRow = Order & { sts_str?: string; service_charge?: number | string }
|
||||
|
||||
@@ -113,10 +109,14 @@ function createdAtLabel(row: OrderRow) {
|
||||
return row.created_at ? toJalali(row.created_at) : '-'
|
||||
}
|
||||
|
||||
function amountLabel(row: OrderRow) {
|
||||
return formatAmountWithUnit(row.amount_display ?? row.amount, row.amount_unit)
|
||||
}
|
||||
|
||||
function serviceChargeLabel(row: OrderRow) {
|
||||
if (row.service_charge == null || row.service_charge === '') return '-'
|
||||
const value = Number(row.service_charge)
|
||||
if (Number.isNaN(value)) return `${row.service_charge} گرم`
|
||||
return `${parseFloat(value.toFixed(5))} گرم`
|
||||
if (Number.isNaN(value)) return String(row.service_charge)
|
||||
return formatAmountWithUnit(value, row.amount_unit)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="directionClass">
|
||||
{{ directionLabel }}
|
||||
</span>
|
||||
<span
|
||||
v-if="item.order_metal_label"
|
||||
class="shrink-0 rounded-md border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-xs font-600 text-amber-700"
|
||||
>
|
||||
{{ item.order_metal_label }}
|
||||
</span>
|
||||
<span class="truncate text-sm font-700 text-gray-900">{{ customerName }}</span>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs font-600" :class="statusValueClass">{{ statusLabel }}</span>
|
||||
@@ -13,7 +19,7 @@
|
||||
|
||||
<div class="flex items-center justify-between gap-2 text-xs text-gray-600">
|
||||
<span>
|
||||
<span class="text-gray-500">طلا:</span>
|
||||
<span class="text-gray-500">مقدار:</span>
|
||||
<span class="font-600 text-gray-900">{{ amountLabel }}</span>
|
||||
</span>
|
||||
<span class="font-700 text-gray-900">{{ totalPriceLabel }}</span>
|
||||
@@ -26,6 +32,7 @@
|
||||
import type { Order } from '@/model/order'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
|
||||
const props = defineProps<{
|
||||
item: Order & { sts_str?: string; service_charge?: number }
|
||||
@@ -44,9 +51,8 @@ const directionClass = computed(() =>
|
||||
|
||||
const customerName = computed(() => props.item.customer?.name || '-')
|
||||
const amountLabel = computed(() => {
|
||||
const amount = props.item.amount
|
||||
if (amount == null) return '-'
|
||||
return `${Number(amount.toFixed(3))} گرم`
|
||||
const amount = props.item.amount_display ?? props.item.amount
|
||||
return formatAmountWithUnit(amount, props.item.amount_unit)
|
||||
})
|
||||
const totalPriceLabel = computed(() => usePrice(props.item.total_price ?? 0, true, ''))
|
||||
const statusLabel = computed(() => props.item.sts_str || '-')
|
||||
|
||||
@@ -42,7 +42,7 @@ import type { TableInput } from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { changeCustomerActivation, getCustomerTable } from '@/api/customer'
|
||||
import { cols } from '../hooks/orderColumns'
|
||||
import { buildOrderColumns } from '../hooks/orderColumns'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { usePrefix } from '@core/composable/usePrefix'
|
||||
import { message } from 'ant-design-vue'
|
||||
@@ -74,22 +74,22 @@ function checkPermission(temp: string) {
|
||||
const table = ref(null)
|
||||
|
||||
const tblProps = ref<any>({
|
||||
columns: cols,
|
||||
api: (params: TableInput) => {
|
||||
columns: buildOrderColumns(),
|
||||
api: async (params: TableInput) => {
|
||||
if (props.customerId) {
|
||||
params.filters = []
|
||||
params.filters?.push({
|
||||
field: "customer_id",
|
||||
type: "text",
|
||||
field: 'customer_id',
|
||||
type: 'text',
|
||||
value: props.customerId,
|
||||
cr: "and",
|
||||
op: "eq",
|
||||
cr: 'and',
|
||||
op: 'eq',
|
||||
hidden: false
|
||||
})
|
||||
}
|
||||
return getOrderTable(params)
|
||||
const data = await getOrderTable(params)
|
||||
return data
|
||||
}
|
||||
//bordered: true
|
||||
})
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
|
||||
@@ -1,239 +1,178 @@
|
||||
import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
import type { DocItem } from '@/model/docType'*/
|
||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
|
||||
//define store
|
||||
//const store = useStaticApiListsStore()
|
||||
//document types
|
||||
/*let docTypes: DocItem[]*/
|
||||
/*onMounted(async () => {
|
||||
docTypes = await store.readyDocTypeList()
|
||||
})
|
||||
const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
return {
|
||||
label: x.title,
|
||||
value: x.id
|
||||
}
|
||||
})*/
|
||||
export type OrderMetalColumn = { metal_id: number; label: string; display_unit?: string }
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: any) => {
|
||||
return record.index + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'کد سفارش',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
// className: 'column-money'
|
||||
},
|
||||
{
|
||||
title: 'نوع سفارش',
|
||||
dataIndex: 'direction',
|
||||
key: 'direction',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => {
|
||||
switch (record.direction) {
|
||||
case 2:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
فروش
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
case 1:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
خرید
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
export function buildOrderColumns(_metals: OrderMetalColumn[] = []): BaseTableColumn[] {
|
||||
return [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: any) => {
|
||||
return record.index + 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'نام و نام خانوادگی',
|
||||
dataIndex: 'customer.name',
|
||||
key: 'customer.name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'customer.mobile',
|
||||
key: 'customer.mobile',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'مقدار (گرم)',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
type: 'Text'
|
||||
},
|
||||
/*{
|
||||
title: 'کارمزد ریالی',
|
||||
dataIndex: 'service_charge_price',
|
||||
key: 'service_charge_price',
|
||||
type: 'Number',
|
||||
customRender(data) {
|
||||
return usePrice(data.record?.service_charge_price ?? 0, true, '')
|
||||
}
|
||||
// className: 'column-money'
|
||||
},*/
|
||||
{
|
||||
title: 'کارمزد طلایی',
|
||||
dataIndex: 'service_charge',
|
||||
key: 'service_charge',
|
||||
type: 'Number',
|
||||
customRender(data) {
|
||||
return data.record?.service_charge
|
||||
}
|
||||
// className: 'column-money'
|
||||
},
|
||||
{
|
||||
title: 'قیمت هر گرم طلا برای مشتری (ریال)',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
return usePrice(data.record?.price ?? 0, true, '')
|
||||
}
|
||||
},
|
||||
/* {
|
||||
title: 'قیمت هر گرم طلا در لیمان',
|
||||
dataIndex: 'limanPrice',
|
||||
key: 'limanPrice',
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
return usePrice(data.record?.total_price_liman ?? 0, true, '')
|
||||
}
|
||||
},*/
|
||||
{
|
||||
title: 'قیمت کل برای مشتری (ریال)',
|
||||
dataIndex: 'total_price',
|
||||
key: 'total_price',
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
return usePrice(data.record?.total_price ?? 0, true, '')
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
title: 'وضعیت',
|
||||
dataIndex: 'sts',
|
||||
key: 'sts',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => {
|
||||
switch (record.sts) {
|
||||
case 1:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#ca8a04" bgColor="#fef3c7">
|
||||
{record.sts_str}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
case 2:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
{record.sts_str}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
{record.sts_str}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'کد سفارش',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'نوع سفارش',
|
||||
dataIndex: 'direction',
|
||||
key: 'direction',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => {
|
||||
switch (record.direction) {
|
||||
case 2:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
فروش
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
case 1:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
خرید
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'تاریخ ثبت',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (record: any) => {
|
||||
return toJalali(record.record.created_at)
|
||||
}
|
||||
},
|
||||
/*{
|
||||
title: 'نقش',
|
||||
dataIndex: 'user_type_record',
|
||||
key: 'user_type_record',
|
||||
type: 'Select',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
filterComponentProps: {
|
||||
multiple: true
|
||||
},
|
||||
filterField: 'user_type_obj.id',
|
||||
filterComponent: ReadyUserTypeSelect,
|
||||
/!* customRender: (record: any) => {
|
||||
return record.record.user_type_record?.value
|
||||
}*!/
|
||||
customRender: ({ record }) => {
|
||||
const items = record.is_admin ? (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
ادمین
|
||||
},
|
||||
{
|
||||
title: 'نام و نام خانوادگی',
|
||||
dataIndex: 'customer.name',
|
||||
key: 'customer.name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'customer.mobile',
|
||||
key: 'customer.mobile',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'نوع فلز',
|
||||
dataIndex: 'order_metal_label',
|
||||
key: 'order_metal_label',
|
||||
type: 'Text',
|
||||
filterable: false,
|
||||
customRender({ record }) {
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#ca8a04" bgColor="#fef3c7" class={'font-bold px-6'}>
|
||||
{record.order_metal_label ?? '-'}
|
||||
</CustomTag>
|
||||
) : (
|
||||
record.user_type_obj.map((item: { title: any }, index: any) => {
|
||||
if (item) {
|
||||
return (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
{item?.title}
|
||||
</CustomTag>
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
return <div class="flex justify-center items-center gap-2">{items}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'مقدار',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
type: 'Text',
|
||||
customRender({ record }) {
|
||||
const amount = record.amount_display ?? record.amount
|
||||
return formatAmountWithUnit(amount, record.amount_unit)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'کارمزد',
|
||||
dataIndex: 'service_charge',
|
||||
key: 'service_charge',
|
||||
type: 'Number',
|
||||
customRender({ record }) {
|
||||
const value = record.service_charge
|
||||
if (value == null || value === '') {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'حوزه',
|
||||
dataIndex: 'section',
|
||||
key: 'section'
|
||||
},
|
||||
{
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'mobile',
|
||||
type: 'Number',
|
||||
key: 'mobile'
|
||||
},
|
||||
{
|
||||
title: 'تاریخ ثبت',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (record: any) => {
|
||||
return toJalaliNoTime(record.record.created_at)
|
||||
return formatAmountWithUnit(Number(value), record.amount_unit)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'قیمت زمان معامله (ریال)',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
type: 'Text',
|
||||
customRender({ record }) {
|
||||
return usePrice(record.price ?? 0, true, '')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'قیمت کل برای مشتری (ریال)',
|
||||
dataIndex: 'total_price',
|
||||
key: 'total_price',
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
return usePrice(data.record?.total_price ?? 0, true, '')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'وضعیت',
|
||||
dataIndex: 'sts',
|
||||
key: 'sts',
|
||||
filterable: false,
|
||||
customRender: ({ record }) => {
|
||||
switch (record.sts) {
|
||||
case 1:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#ca8a04" bgColor="#fef3c7">
|
||||
{record.sts_str}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
case 2:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
{record.sts_str}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
{record.sts_str}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},*/
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
slot: 'action',
|
||||
fixed: 'right',
|
||||
filterable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'تاریخ ثبت',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (record: any) => {
|
||||
return toJalali(record.record.created_at)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
slot: 'action',
|
||||
fixed: 'right',
|
||||
filterable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function getOrderColumns(): BaseTableColumn[] {
|
||||
return buildOrderColumns()
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ import { getLastPrice } from '@/api/price'
|
||||
import type { last } from '@/model/price'
|
||||
import { createOrder } from '@/api/order'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
@@ -182,11 +184,15 @@ function info(val: Order) {
|
||||
infoParams.itemsTitle = 'اطلاعات کاربری'
|
||||
infoParams.items = [
|
||||
{
|
||||
key: 'مقدار (گرم)',
|
||||
value: val.amount ?? '-'
|
||||
key: 'مقدار',
|
||||
value: formatAmountWithUnit(val.amount_display ?? val.amount, val.amount_unit)
|
||||
},
|
||||
{
|
||||
key: 'قیمت هر گرم طلا برای مشتری (ریال)',
|
||||
key: 'نوع فلز',
|
||||
value: val.order_metal_label ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت زمان معامله (ریال)',
|
||||
value: usePrice(val.price ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
@@ -194,7 +200,7 @@ function info(val: Order) {
|
||||
value: usePrice(val.total_price ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت هر گرم طلا بدون سود (ریال)',
|
||||
key: metalLabel.goldGramPriceOriginal(),
|
||||
value: usePrice(val.price_original ?? 0) ?? '-'
|
||||
},
|
||||
{
|
||||
@@ -226,8 +232,11 @@ function info(val: Order) {
|
||||
value: usePrice(val.service_charge_price ?? 0)
|
||||
},*/
|
||||
{
|
||||
key: 'کارمزد طلایی',
|
||||
value: val.service_charge ?? '-' + ' گرم'
|
||||
key: 'کارمزد',
|
||||
value:
|
||||
val.service_charge != null && val.service_charge !== ''
|
||||
? formatAmountWithUnit(Number(val.service_charge), val.amount_unit)
|
||||
: '-'
|
||||
},
|
||||
/* {
|
||||
key: 'تاریخ تولد',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<MobileSheetHeader :title="sheetTitle" :subtitle="dateTimeLabel(row)" :show-title="isMobile" />
|
||||
|
||||
<div class="space-y-2">
|
||||
<MobileStatCard bordered variant="success" label="قیمت هر گرم طلا برای مشتری (ریال)" :value="usePrice(row.price ?? 0, true, '')">
|
||||
<MobileStatCard bordered variant="success" :label="metalLabel.goldGramPriceCustomer()" :value="usePrice(row.price ?? 0, true, '')">
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
|
||||
</template>
|
||||
@@ -32,6 +32,7 @@ import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
@@ -21,7 +22,8 @@ const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
}
|
||||
})*/
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
export function getPriceColumns(): BaseTableColumn[] {
|
||||
return [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
@@ -53,7 +55,7 @@ export const cols: BaseTableColumn[] = [
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'قیمت هر گرم طلا برای مشتری (ریال)',
|
||||
title: metalLabel.goldGramPriceCustomer(),
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
type: 'Text',
|
||||
@@ -157,4 +159,5 @@ export const cols: BaseTableColumn[] = [
|
||||
fixed: 'right',
|
||||
filterable: false
|
||||
}*/
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -118,9 +118,9 @@
|
||||
>
|
||||
<BasicInfoItem
|
||||
title="نام قلک"
|
||||
:value="item?.fund ? item?.fund?.title : 'کیف پول طلایی'"
|
||||
:value="item?.fund ? item?.fund?.title : metalLabel.goldWallet()"
|
||||
/>
|
||||
<BasicInfoItem title="مقدار طلا" :value="`${item?.gold} گرم`" />
|
||||
<BasicInfoItem :title="metalLabel.goldAmount()" :value="`${item?.gold} گرم`" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,6 +162,7 @@ import { createCustomer, deleteCustomer, updateCustomer } from '@/api/customer'
|
||||
import type { Charge } from '@/model/wallet'
|
||||
import customers from '@/router/routes/customers'
|
||||
import { chargeWallet } from '@/api/wallet'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col gap-4 p-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="category-page flex h-full min-h-0 flex-col gap-4 p-4">
|
||||
<div class="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<BaseButton :loading="syncing" @click="handleSync">
|
||||
همگامسازی از محصولات
|
||||
</BaseButton>
|
||||
@@ -9,47 +9,58 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="items"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
size="middle"
|
||||
class="min-h-0 flex-1"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'active'">
|
||||
<a-switch
|
||||
:checked="asBool(record.active)"
|
||||
checked-children="فعال"
|
||||
un-checked-children="مخفی"
|
||||
@change="(checked: boolean) => toggleField(record, 'active', checked)"
|
||||
/>
|
||||
<div
|
||||
ref="tableWrapRef"
|
||||
class="category-page__table min-h-0 flex-1 overflow-hidden"
|
||||
:class="{ 'category-page__table--dragging': dragFromId !== null }"
|
||||
>
|
||||
<a-spin :spinning="loading" class="category-page__spin">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="items"
|
||||
:pagination="false"
|
||||
:scroll="tableScrollY ? { y: tableScrollY } : undefined"
|
||||
row-key="id"
|
||||
size="middle"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'drag'">
|
||||
<span
|
||||
class="category-page__drag-handle"
|
||||
title="کشیدن برای تغییر ترتیب"
|
||||
@pointerdown="onHandlePointerDown(record.id, $event)"
|
||||
>
|
||||
<BaseIcon icon="vuesax-linear:menu" size="18" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'active'">
|
||||
<a-switch
|
||||
:checked="asBool(record.active)"
|
||||
checked-children="فعال"
|
||||
un-checked-children="مخفی"
|
||||
@change="(checked: boolean) => toggleField(record, 'active', checked)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'show_in_select'">
|
||||
<a-switch
|
||||
:checked="asBool(record.show_in_select)"
|
||||
@change="(checked: boolean) => toggleField(record, 'show_in_select', checked)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sort_order'">
|
||||
<span class="text-gray-400">{{ record.sort_order }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'show_in_select'">
|
||||
<a-switch
|
||||
:checked="asBool(record.show_in_select)"
|
||||
@change="(checked: boolean) => toggleField(record, 'show_in_select', checked)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sort_order'">
|
||||
<BasicInput
|
||||
class="!w-16"
|
||||
type="number"
|
||||
v-model:value="record.sort_order"
|
||||
@onchange="() => scheduleSortOrder(record)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-spin>
|
||||
</a-table>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { TableColumnType } from 'ant-design-vue'
|
||||
import { useResizeObserver } from '@vueuse/core'
|
||||
import { debounce } from 'lodash'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { getCategories, syncCategoriesFromProducts, updateCategory } from '@/api/category'
|
||||
@@ -57,7 +68,17 @@ import type { Category } from '@/model/category'
|
||||
|
||||
const loading = ref(false)
|
||||
const syncing = ref(false)
|
||||
const savingOrder = ref(false)
|
||||
const items = ref<Category[]>([])
|
||||
const tableWrapRef = ref<HTMLElement | null>(null)
|
||||
const tableScrollY = ref<number>()
|
||||
const dragFromId = ref<number | null>(null)
|
||||
let highlightedRow: HTMLTableRowElement | null = null
|
||||
let draggingRow: HTMLTableRowElement | null = null
|
||||
let pointerDragActive = false
|
||||
let dragOverId: number | null = null
|
||||
let activePointerId: number | null = null
|
||||
let saveOrderGeneration = 0
|
||||
|
||||
type CategoryField = 'active' | 'show_in_select' | 'sort_order'
|
||||
|
||||
@@ -66,32 +87,216 @@ const debouncedFlush = new Map<number, ReturnType<typeof debounce>>()
|
||||
let syncInFlight: Promise<void> | null = null
|
||||
|
||||
const columns: TableColumnType<Category>[] = [
|
||||
{
|
||||
title: '',
|
||||
key: 'drag',
|
||||
width: 44,
|
||||
align: 'center',
|
||||
},
|
||||
{ title: 'نام دسته', dataIndex: 'name', key: 'name' },
|
||||
{
|
||||
title: 'نمایش محصولات در اپ',
|
||||
key: 'active',
|
||||
width: 160,
|
||||
align: 'center'
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: 'سلکت افزودن محصول',
|
||||
key: 'show_in_select',
|
||||
width: 150,
|
||||
align: 'center'
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: 'ترتیب',
|
||||
key: 'sort_order',
|
||||
width: 90,
|
||||
align: 'center'
|
||||
}
|
||||
width: 72,
|
||||
align: 'center',
|
||||
},
|
||||
]
|
||||
|
||||
function updateTableScrollY() {
|
||||
const height = tableWrapRef.value?.clientHeight ?? 0
|
||||
tableScrollY.value = height > 120 ? height - 56 : undefined
|
||||
}
|
||||
|
||||
useResizeObserver(tableWrapRef, () => {
|
||||
updateTableScrollY()
|
||||
})
|
||||
|
||||
/** پیشفرض: روشن — فقط صفر یا false یعنی خاموش */
|
||||
function asBool(value: boolean | number | undefined | null) {
|
||||
return value !== false && value !== 0
|
||||
}
|
||||
|
||||
function getTableBodyEl(): HTMLElement | null {
|
||||
return tableWrapRef.value?.querySelector('.ant-table-body') as HTMLElement | null
|
||||
}
|
||||
|
||||
function parseRowId(row: HTMLTableRowElement): number | null {
|
||||
const key = row.getAttribute('data-row-key')
|
||||
if (!key) return null
|
||||
const id = Number(key)
|
||||
return Number.isFinite(id) ? id : null
|
||||
}
|
||||
|
||||
function getRowFromPoint(clientX: number, clientY: number): HTMLTableRowElement | null {
|
||||
const wrap = tableWrapRef.value
|
||||
if (!wrap) return null
|
||||
|
||||
if (typeof document.elementsFromPoint === 'function') {
|
||||
for (const element of document.elementsFromPoint(clientX, clientY)) {
|
||||
if (!(element instanceof Element) || !wrap.contains(element)) continue
|
||||
const row = element.closest('tr.ant-table-row')
|
||||
if (row instanceof HTMLTableRowElement) return row
|
||||
}
|
||||
}
|
||||
|
||||
const rows = wrap.querySelectorAll('tr.ant-table-row')
|
||||
for (const row of rows) {
|
||||
const rect = row.getBoundingClientRect()
|
||||
if (
|
||||
clientY >= rect.top &&
|
||||
clientY <= rect.bottom &&
|
||||
clientX >= rect.left &&
|
||||
clientX <= rect.right
|
||||
) {
|
||||
return row as HTMLTableRowElement
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function setDragHighlight(row: HTMLTableRowElement | null) {
|
||||
if (highlightedRow && highlightedRow !== row) {
|
||||
highlightedRow.classList.remove('category-page__row--over')
|
||||
}
|
||||
highlightedRow = row
|
||||
row?.classList.add('category-page__row--over')
|
||||
}
|
||||
|
||||
function autoScrollAtPointer(clientY: number) {
|
||||
const body = getTableBodyEl()
|
||||
if (!body) return
|
||||
|
||||
const rect = body.getBoundingClientRect()
|
||||
const edge = 56
|
||||
if (clientY < rect.top + edge) {
|
||||
body.scrollTop -= Math.max(8, (rect.top + edge - clientY) / 3)
|
||||
} else if (clientY > rect.bottom - edge) {
|
||||
body.scrollTop += Math.max(8, (clientY - (rect.bottom - edge)) / 3)
|
||||
}
|
||||
}
|
||||
|
||||
function onHandlePointerDown(id: number, event: PointerEvent) {
|
||||
if (event.button !== 0) return
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
const handle = event.currentTarget as HTMLElement
|
||||
handle.setPointerCapture(event.pointerId)
|
||||
|
||||
activePointerId = event.pointerId
|
||||
pointerDragActive = true
|
||||
dragFromId.value = id
|
||||
dragOverId = id
|
||||
|
||||
draggingRow = handle.closest('tr.ant-table-row')
|
||||
draggingRow?.classList.add('category-page__row--dragging')
|
||||
|
||||
window.addEventListener('pointermove', onPointerMove)
|
||||
window.addEventListener('pointerup', onPointerUp)
|
||||
window.addEventListener('pointercancel', onPointerUp)
|
||||
}
|
||||
|
||||
function onPointerMove(event: PointerEvent) {
|
||||
if (!pointerDragActive || activePointerId !== event.pointerId) return
|
||||
|
||||
event.preventDefault()
|
||||
autoScrollAtPointer(event.clientY)
|
||||
|
||||
const row = getRowFromPoint(event.clientX, event.clientY)
|
||||
setDragHighlight(row)
|
||||
dragOverId = row ? parseRowId(row) : null
|
||||
}
|
||||
|
||||
function onPointerUp(event: PointerEvent) {
|
||||
if (!pointerDragActive || activePointerId !== event.pointerId) return
|
||||
|
||||
const fromId = dragFromId.value
|
||||
const toId =
|
||||
dragOverId ?? (() => {
|
||||
const row = getRowFromPoint(event.clientX, event.clientY)
|
||||
return row ? parseRowId(row) : null
|
||||
})()
|
||||
|
||||
if (fromId !== null && toId !== null && fromId !== toId) {
|
||||
const fromIndex = items.value.findIndex((item) => item.id === fromId)
|
||||
const toIndex = items.value.findIndex((item) => item.id === toId)
|
||||
if (fromIndex >= 0 && toIndex >= 0) {
|
||||
void reorderByDrag(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
|
||||
cleanupPointerDrag()
|
||||
}
|
||||
|
||||
function cleanupPointerDrag() {
|
||||
pointerDragActive = false
|
||||
activePointerId = null
|
||||
dragOverId = null
|
||||
dragFromId.value = null
|
||||
|
||||
draggingRow?.classList.remove('category-page__row--dragging')
|
||||
draggingRow = null
|
||||
setDragHighlight(null)
|
||||
|
||||
window.removeEventListener('pointermove', onPointerMove)
|
||||
window.removeEventListener('pointerup', onPointerUp)
|
||||
window.removeEventListener('pointercancel', onPointerUp)
|
||||
}
|
||||
|
||||
function applySortOrders(list: Category[]) {
|
||||
list.forEach((item, index) => {
|
||||
item.sort_order = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
async function reorderByDrag(fromIndex: number, toIndex: number) {
|
||||
if (fromIndex === toIndex) return
|
||||
|
||||
const list = [...items.value]
|
||||
const [moved] = list.splice(fromIndex, 1)
|
||||
list.splice(toIndex, 0, moved)
|
||||
|
||||
const previousOrders = new Map(items.value.map((item) => [item.id, item.sort_order]))
|
||||
applySortOrders(list)
|
||||
items.value = list
|
||||
|
||||
const updates = list.filter((item) => previousOrders.get(item.id) !== item.sort_order)
|
||||
if (!updates.length) return
|
||||
|
||||
const generation = ++saveOrderGeneration
|
||||
savingOrder.value = true
|
||||
try {
|
||||
await Promise.all(
|
||||
updates.map((item) => updateCategory(item.id, { sort_order: item.sort_order }))
|
||||
)
|
||||
if (generation !== saveOrderGeneration) return
|
||||
message.success('ترتیب ذخیره شد')
|
||||
} catch (e) {
|
||||
if (generation !== saveOrderGeneration) return
|
||||
console.error(e)
|
||||
message.error('خطا در ذخیره ترتیب')
|
||||
await fetchList()
|
||||
} finally {
|
||||
if (generation === saveOrderGeneration) {
|
||||
savingOrder.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -101,6 +306,7 @@ async function fetchList() {
|
||||
message.error('خطا در دریافت دستهبندیها')
|
||||
} finally {
|
||||
loading.value = false
|
||||
updateTableScrollY()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +335,7 @@ async function flushSave(id: number) {
|
||||
const snapshot = {
|
||||
active: record.active,
|
||||
show_in_select: record.show_in_select,
|
||||
sort_order: record.sort_order
|
||||
sort_order: record.sort_order,
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -154,12 +360,6 @@ function toggleField(
|
||||
scheduleSave(record, { [field]: next })
|
||||
}
|
||||
|
||||
function scheduleSortOrder(record: Category) {
|
||||
const order = Number(record.sort_order)
|
||||
if (Number.isNaN(order)) return
|
||||
scheduleSave(record, { sort_order: order })
|
||||
}
|
||||
|
||||
async function syncList(showMessage = false) {
|
||||
if (syncInFlight) {
|
||||
return syncInFlight
|
||||
@@ -183,6 +383,7 @@ async function syncList(showMessage = false) {
|
||||
} finally {
|
||||
syncing.value = false
|
||||
syncInFlight = null
|
||||
updateTableScrollY()
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -194,12 +395,66 @@ async function handleSync() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void syncList()
|
||||
void fetchList()
|
||||
updateTableScrollY()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanupPointerDrag()
|
||||
debouncedFlush.forEach((fn) => fn.cancel())
|
||||
debouncedFlush.clear()
|
||||
pendingPatches.clear()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.category-page__spin {
|
||||
display: block;
|
||||
height: 100%;
|
||||
|
||||
:deep(.ant-spin-container) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.category-page__drag-handle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: grab;
|
||||
color: #9ca3af;
|
||||
padding: 4px;
|
||||
border-radius: 6px;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
|
||||
&:hover {
|
||||
color: #6b7280;
|
||||
background: rgba(148, 163, 184, 0.16);
|
||||
}
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.category-page__table--dragging {
|
||||
user-select: none;
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.category-page__drag-handle) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.category-page__row--dragging > td) {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
:deep(.category-page__row--over > td) {
|
||||
background: rgba(59, 130, 246, 0.08) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
/>
|
||||
</MobileCardList>
|
||||
|
||||
<TransactionMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
|
||||
<TransactionMobileDetailSheet
|
||||
v-model:visible="sheetVisible"
|
||||
:item="selectedItem"
|
||||
:metals="catalogMetals"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -32,6 +36,9 @@ import { useMobileTableList } from '@/components/mobile/composables/useMobileTab
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import TransactionMobileDetailSheet from './TransactionMobileDetailSheet.vue'
|
||||
import TransactionMobileListItem from './TransactionMobileListItem.vue'
|
||||
import type { TransactionMetalColumn } from '../hooks/transactionsColumns'
|
||||
|
||||
const catalogMetals = ref<TransactionMetalColumn[]>([])
|
||||
|
||||
const props = defineProps<{
|
||||
customerId?: number
|
||||
@@ -51,7 +58,10 @@ function fetchTransactions(params: TableInput) {
|
||||
hidden: false
|
||||
})
|
||||
}
|
||||
return getTransactionTable(params)
|
||||
return getTransactionTable(params).then((data) => {
|
||||
catalogMetals.value = data?.catalogMetals ?? []
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Transaction>({
|
||||
|
||||
@@ -3,21 +3,29 @@
|
||||
<template #default="{ item: row }">
|
||||
<MobileSheetHeader :title="sheetTitle" :subtitle="dateLabel(row)" :show-title="isMobile" />
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="rounded-lg border px-2 py-0.5 text-xs font-600" :class="typeTagClass(row)">
|
||||
{{ typeLabel(row) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!isMoneyType(row) && row.transaction_metal_label"
|
||||
class="rounded-lg border border-amber-300 bg-amber-50 px-2 py-0.5 text-xs font-600 text-amber-700"
|
||||
>
|
||||
{{ row.transaction_metal_label }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<MobileStatCard
|
||||
v-for="entry in metalEntries(row)"
|
||||
:key="entry.metal_id"
|
||||
bordered
|
||||
:variant="(row.gold ?? 0) > 0 ? 'success' : 'danger'"
|
||||
label="مقدار طلا (گرم)"
|
||||
:value="goldLabel(row)"
|
||||
:variant="entry.value > 0 ? 'success' : 'danger'"
|
||||
:label="transactionMetalAmountTitle(entry.metal_id, entry.label)"
|
||||
:value="entry.display"
|
||||
>
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" :color="(row.gold ?? 0) > 0 ? '#15803d' : '#dc2626'" />
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" :color="entry.value > 0 ? '#15803d' : '#dc2626'" />
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
<MobileStatCard
|
||||
@@ -33,6 +41,7 @@
|
||||
</div>
|
||||
|
||||
<MobileInfoGrid>
|
||||
<MobileDetailRow label="نوع فلز" :value="isMoneyType(row) ? '-' : (row.transaction_metal_label ?? '-')" />
|
||||
<MobileDetailRow label="کد تراکنش" :value="row.id ?? '-'" />
|
||||
<MobileDetailRow label="نام تراکنش" :value="row.type_str || '-'" />
|
||||
<MobileDetailRow label="علت تراکنش" full-width :value="noteLabel(row)" />
|
||||
@@ -53,10 +62,14 @@ import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel, transactionMetalAmountTitle } from '@/utils/adminMetalLabels'
|
||||
import { formatMetalDisplayValue, metalCellValue } from '@/utils/metalWeight'
|
||||
import type { TransactionMetalColumn } from '../hooks/transactionsColumns'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
item: Transaction | null
|
||||
metals?: TransactionMetalColumn[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -75,7 +88,7 @@ function isMoneyType(row: Transaction) {
|
||||
}
|
||||
|
||||
function typeLabel(row: Transaction) {
|
||||
return isMoneyType(row) ? 'پول' : 'طلا'
|
||||
return isMoneyType(row) ? 'پول' : 'فلز'
|
||||
}
|
||||
|
||||
function typeTagClass(row: Transaction) {
|
||||
@@ -88,14 +101,26 @@ function dateLabel(row: Transaction) {
|
||||
return row.created_at ? toJalaliNoTime(row.created_at) : '-'
|
||||
}
|
||||
|
||||
function goldLabel(row: Transaction) {
|
||||
const gold = row.gold
|
||||
if (gold == null) return '-'
|
||||
return `${Number(Number(gold).toFixed(3))} گرم`
|
||||
}
|
||||
|
||||
function noteLabel(row: Transaction) {
|
||||
if (row.note == null) return '-'
|
||||
return String(row.note)
|
||||
}
|
||||
|
||||
function metalEntries(row: Transaction) {
|
||||
const metals = props.metals?.length
|
||||
? props.metals
|
||||
: [{ metal_id: row.transaction_metal_id ?? 1, label: row.transaction_metal_label ?? 'طلا' }]
|
||||
|
||||
return metals
|
||||
.map((metal) => {
|
||||
const value = metalCellValue(row as Record<string, unknown>, metal.metal_id)
|
||||
return {
|
||||
metal_id: metal.metal_id,
|
||||
label: metal.label,
|
||||
value,
|
||||
display: value ? formatMetalDisplayValue(value, metal.metal_id) : '-'
|
||||
}
|
||||
})
|
||||
.filter((entry) => entry.value !== 0)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="typeTagClass">
|
||||
{{ typeLabel }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!isMoneyType && item.transaction_metal_label"
|
||||
class="shrink-0 rounded-md border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-xs font-600 text-amber-700"
|
||||
>
|
||||
{{ item.transaction_metal_label }}
|
||||
</span>
|
||||
<span class="truncate text-sm font-700 text-gray-900">{{ item.type_str || '-' }}</span>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs text-gray-400">{{ dateLabel }}</span>
|
||||
@@ -31,7 +37,7 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const isMoneyType = computed(() => props.item.type === 4 || props.item.type === 3)
|
||||
const typeLabel = computed(() => (isMoneyType.value ? 'پول' : 'طلا'))
|
||||
const typeLabel = computed(() => (isMoneyType.value ? 'پول' : 'فلز'))
|
||||
const typeTagClass = computed(() =>
|
||||
isMoneyType.value
|
||||
? 'border-green-300 bg-green-50 text-green-700'
|
||||
|
||||
@@ -67,7 +67,7 @@ import type {TableInput} from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import {changeCustomerActivation, getCustomerTable} from '@/api/customer'
|
||||
import {cols} from '../hooks/transactionsColumns'
|
||||
import { buildTransactionsColumns } from '../hooks/transactionsColumns'
|
||||
import {useUserStore} from '@/store/userStore'
|
||||
import {usePrefix} from '@core/composable/usePrefix'
|
||||
import {message} from 'ant-design-vue'
|
||||
@@ -102,23 +102,24 @@ const {prefixCls} = usePrefix('pophoverstatus')
|
||||
const table = ref(null)
|
||||
|
||||
const tblProps = ref<any>({
|
||||
columns: cols,
|
||||
api: (params: TableInput) => {
|
||||
// console.log(props.customerId)
|
||||
columns: buildTransactionsColumns(),
|
||||
api: async (params: TableInput) => {
|
||||
if (props.customerId) {
|
||||
params.filters = []
|
||||
params.filters?.push({
|
||||
field: "customer_id",
|
||||
type: "text",
|
||||
field: 'customer_id',
|
||||
type: 'text',
|
||||
value: props.customerId,
|
||||
cr: "and",
|
||||
op: "eq",
|
||||
cr: 'and',
|
||||
op: 'eq',
|
||||
hidden: false
|
||||
})
|
||||
}
|
||||
return getTransactionTable(params)
|
||||
const data = await getTransactionTable(params)
|
||||
const metals = data?.catalogMetals ?? []
|
||||
tblProps.value.columns = buildTransactionsColumns(metals)
|
||||
return data
|
||||
}
|
||||
//bordered: true
|
||||
})
|
||||
const loading = ref<boolean>(false)
|
||||
/*async function changeCustomerActivationFunction(id: number) {
|
||||
|
||||
@@ -1,203 +1,165 @@
|
||||
import type {BaseTableColumn} from '@core/type/baseTable'
|
||||
import {toJalaliNoTime, toTime} from '@core/utils/useDataTime'
|
||||
import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import {usePrice} from '@core/utils/usePrice'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
import type { DocItem } from '@/model/docType'*/
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel, transactionMetalAmountTitle } from '@/utils/adminMetalLabels'
|
||||
import { formatMetalDisplayValuePlain, metalCellValue } from '@/utils/metalWeight'
|
||||
|
||||
//define store
|
||||
//const store = useStaticApiListsStore()
|
||||
//document types
|
||||
/*let docTypes: DocItem[]*/
|
||||
/*onMounted(async () => {
|
||||
docTypes = await store.readyDocTypeList()
|
||||
})
|
||||
const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
return {
|
||||
label: x.title,
|
||||
value: x.id
|
||||
export type TransactionMetalColumn = { metal_id: number; label: string; display_unit?: string }
|
||||
|
||||
function renderSignedMetalCell(value: number, metalId: number) {
|
||||
if (!value) {
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<div class="px-6">0</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})*/
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: any) => {
|
||||
return record.index + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'کد تراکنش',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
// className: 'column-money'
|
||||
},
|
||||
{
|
||||
title: 'نام تراکنش',
|
||||
dataIndex: 'type_str',
|
||||
key: 'type_str',
|
||||
type: 'Text'
|
||||
// className: 'column-money'
|
||||
},
|
||||
{
|
||||
title: 'علت تراکنش',
|
||||
dataIndex: 'note',
|
||||
key: 'note',
|
||||
type: 'Text',
|
||||
const cls = value > 0 ? 'text-green' : 'text-red'
|
||||
const formatted = formatMetalDisplayValuePlain(Math.abs(value), metalId)
|
||||
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<div class={`font-bold px-6 ${cls}`}>{value > 0 ? formatted : `-${formatted}`}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// className: 'column-money'
|
||||
function isMoneyTransaction(record: { type?: number }) {
|
||||
return record.type === 4 || record.type === 3
|
||||
}
|
||||
|
||||
export function buildTransactionsColumns(metals: TransactionMetalColumn[] = []): BaseTableColumn[] {
|
||||
const baseColumns: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: any) => {
|
||||
return record.index + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'نوع تراکنش',
|
||||
dataIndex: 'active',
|
||||
key: 'active',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: ({record}) => {
|
||||
return record.type === 4 || record.type === 3 ? (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
|
||||
پول
|
||||
</CustomTag>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
{/*<CustomTag textColor="rgb(255 120 0)" bgColor="rgb(255 221 0)">*/}
|
||||
<CustomTag textColor="#ca8a04" bgColor="#fef3c7" class={'font-bold px-6'}>
|
||||
طلا
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'نام مشتری',
|
||||
dataIndex: 'customer.name',
|
||||
key: 'customer.name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'مقدار طلا طی تراکنش (گرم)',
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
type: 'Number',
|
||||
customRender: ({record}) => {
|
||||
return record.gold > 0 ? (
|
||||
<div class="flex justify-center">
|
||||
<div class={'font-bold px-6 text-green'}>{record.gold}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
{/*<CustomTag textColor="rgb(255 120 0)" bgColor="rgb(255 221 0)">*/}
|
||||
<div class={'font-bold px-6 text-red'}>{record.gold}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'مقدار پول طی تراکنش (ریال)',
|
||||
dataIndex: 'cash',
|
||||
key: 'cash',
|
||||
type: 'Number',
|
||||
customRender: ({record}) => {
|
||||
return record.cash > 0 ? (
|
||||
<div class="flex justify-center">
|
||||
<div class={'font-bold px-6 text-green'}>{usePrice(record?.cash ?? 0, true, '')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
{/*<CustomTag textColor="rgb(255 120 0)" bgColor="rgb(255 221 0)">*/}
|
||||
<div class={'font-bold px-6 text-red'}>{usePrice(record?.cash ?? 0, true, '')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
/* {
|
||||
title: 'شماره سفارش',
|
||||
dataIndex: 'order_id',
|
||||
key: 'order_id',
|
||||
type: 'Text'
|
||||
},*/
|
||||
/* {
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'mobile',
|
||||
key: 'mobile',
|
||||
title: 'کد تراکنش',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
},*/
|
||||
/* {
|
||||
title: 'نام فروشگاه',
|
||||
dataIndex: 'shop_name',
|
||||
key: 'shop_name',
|
||||
type: 'Text'
|
||||
},*/
|
||||
},
|
||||
{
|
||||
title: 'تاریخ تراکنش',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (record: any) => {
|
||||
return toJalaliNoTime(record.record.created_at)
|
||||
}
|
||||
}
|
||||
/*{
|
||||
title: 'نقش',
|
||||
dataIndex: 'user_type_record',
|
||||
key: 'user_type_record',
|
||||
type: 'Select',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
filterComponentProps: {
|
||||
multiple: true
|
||||
},
|
||||
filterField: 'user_type_obj.id',
|
||||
filterComponent: ReadyUserTypeSelect,
|
||||
/!* customRender: (record: any) => {
|
||||
return record.record.user_type_record?.value
|
||||
}*!/
|
||||
customRender: ({ record }) => {
|
||||
const items = record.is_admin ? (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
ادمین
|
||||
title: 'نام تراکنش',
|
||||
dataIndex: 'type_str',
|
||||
key: 'type_str',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'علت تراکنش',
|
||||
dataIndex: 'note',
|
||||
key: 'note',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'نوع تراکنش',
|
||||
dataIndex: 'active',
|
||||
key: 'active',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: ({ record }) => {
|
||||
return isMoneyTransaction(record) ? (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
|
||||
پول
|
||||
</CustomTag>
|
||||
) : (
|
||||
record.user_type_obj.map((item: { title: any }, index: any) => {
|
||||
if (item) {
|
||||
return (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
{item?.title}
|
||||
</CustomTag>
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
return <div class="flex justify-center items-center gap-2">{items}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#ca8a04" bgColor="#fef3c7" class={'font-bold px-6'}>
|
||||
فلز
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'نوع فلز',
|
||||
dataIndex: 'transaction_metal_label',
|
||||
key: 'transaction_metal_label',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: ({ record }) => {
|
||||
if (isMoneyTransaction(record)) {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'حوزه',
|
||||
dataIndex: 'section',
|
||||
key: 'section'
|
||||
},
|
||||
{
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'mobile',
|
||||
type: 'Number',
|
||||
key: 'mobile'
|
||||
},*/
|
||||
/* {
|
||||
title: 'عملیات',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
slot: 'action',
|
||||
fixed: 'right',
|
||||
filterable: false
|
||||
}*/
|
||||
]
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#ca8a04" bgColor="#fef3c7" class={'font-bold px-6'}>
|
||||
{record.transaction_metal_label ?? metalLabel.goldUnit()}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'نام مشتری',
|
||||
dataIndex: 'customer.name',
|
||||
key: 'customer.name',
|
||||
type: 'Text'
|
||||
}
|
||||
]
|
||||
|
||||
const metalColumns: BaseTableColumn[] =
|
||||
metals.length > 0
|
||||
? metals.map((metal) => ({
|
||||
title: transactionMetalAmountTitle(metal.metal_id, metal.label),
|
||||
dataIndex: `metal_gold_${metal.metal_id}`,
|
||||
key: `metal_${metal.metal_id}`,
|
||||
type: 'Number',
|
||||
customRender: ({ record }) => renderSignedMetalCell(metalCellValue(record, metal.metal_id), metal.metal_id)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
title: metalLabel.goldInTransaction(),
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
type: 'Number',
|
||||
customRender: ({ record }) => renderSignedMetalCell(Number(record.gold ?? 0), 1)
|
||||
}
|
||||
]
|
||||
|
||||
return [
|
||||
...baseColumns,
|
||||
...metalColumns,
|
||||
{
|
||||
title: 'مقدار پول طی تراکنش (ریال)',
|
||||
dataIndex: 'cash',
|
||||
key: 'cash',
|
||||
type: 'Number',
|
||||
customRender: ({ record }) => {
|
||||
return record.cash > 0 ? (
|
||||
<div class="flex justify-center">
|
||||
<div class={'font-bold px-6 text-green'}>{usePrice(record?.cash ?? 0, true, '')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
<div class={'font-bold px-6 text-red'}>{usePrice(record?.cash ?? 0, true, '')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'تاریخ تراکنش',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (record: any) => {
|
||||
return toJalaliNoTime(record.record.created_at)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function getTransactionsColumns(): BaseTableColumn[] {
|
||||
return buildTransactionsColumns()
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ import type { Item } from '@/model/Item'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { createCustomer, deleteCustomer, updateCustomer } from '@/api/customer'
|
||||
import type { Transaction } from '@/model/transaction'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
@@ -127,7 +128,7 @@ let infoParams = reactive<Info>({})
|
||||
|
||||
function info(val: Transaction) {
|
||||
infoParams.name = val.customer.name
|
||||
infoParams.type = val.transaction_type === '1' ? 'نقدی' : 'طلا'
|
||||
infoParams.type = val.transaction_type === '1' ? 'نقدی' : metalLabel.goldUnit()
|
||||
infoParams.date = toJalaliNoTime(val.created_at)
|
||||
infoParams.time = toTime(val.created_at)
|
||||
infoParams.itemsTitle = 'اطلاعات کاربری'
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:metals="catalogMetals"
|
||||
@open="openSheet"
|
||||
/>
|
||||
</MobileCardList>
|
||||
@@ -22,36 +23,39 @@
|
||||
<WalletMobileDetailSheet
|
||||
v-model:visible="sheetVisible"
|
||||
:item="selectedItem"
|
||||
:metals="catalogMetals"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getWalletTable } from '@/api/wallet'
|
||||
import type { Wallet } from '@/model/wallet'
|
||||
import type { Wallet, WalletTableSums } from '@/model/wallet'
|
||||
import MobileCardList from '@/components/mobile/MobileCardList.vue'
|
||||
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
|
||||
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
|
||||
import type { TableInput, TableOutput } from '@core/type/baseTable'
|
||||
import type { WalletMetalColumn } from '../hooks/walletColumns'
|
||||
import WalletMobileDetailSheet from './WalletMobileDetailSheet.vue'
|
||||
import WalletMobileListItem from './WalletMobileListItem.vue'
|
||||
|
||||
type WalletTableResponse = TableOutput<Wallet> & {
|
||||
sums?: {
|
||||
cash?: number
|
||||
gold?: number
|
||||
cashToGold?: number
|
||||
goldToCash?: number
|
||||
}
|
||||
sums?: WalletTableSums
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'sums', sums: WalletTableResponse['sums']): void
|
||||
}>()
|
||||
|
||||
const catalogMetals = ref<WalletMetalColumn[]>([])
|
||||
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Wallet>()
|
||||
|
||||
async function fetchWallet(params: TableInput) {
|
||||
const data = (await getWalletTable(params)) as WalletTableResponse
|
||||
catalogMetals.value =
|
||||
data?.sums?.metalBalances?.map((metal) => ({
|
||||
metal_id: metal.metal_id,
|
||||
label: metal.label
|
||||
})) ?? []
|
||||
emit('sums', data?.sums)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -9,7 +9,15 @@
|
||||
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
<MobileStatCard bordered variant="warning" label="موجودی طلایی" :value="goldLabel(row)">
|
||||
|
||||
<MobileStatCard
|
||||
v-for="entry in metalEntries(row)"
|
||||
:key="entry.metal_id"
|
||||
bordered
|
||||
variant="warning"
|
||||
:label="walletMetalBalanceTitle(entry.metal_id, entry.label)"
|
||||
:value="entry.display"
|
||||
>
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" color="#b45309" />
|
||||
</template>
|
||||
@@ -34,10 +42,14 @@ import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
|
||||
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { walletMetalBalanceTitle } from '@/utils/adminMetalLabels'
|
||||
import { formatMetalDisplayValue, metalCellValue } from '@/utils/metalWeight'
|
||||
import type { WalletMetalColumn } from '../hooks/walletColumns'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
item: Wallet | null
|
||||
metals?: WalletMetalColumn[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -51,9 +63,20 @@ const visibleProxy = computed({
|
||||
|
||||
const sheetTitle = computed(() => props.item?.customer?.name || `کیف پول #${props.item?.id ?? '-'}`)
|
||||
|
||||
function goldLabel(row: Wallet) {
|
||||
const gold = row.gold
|
||||
if (gold == null) return '-'
|
||||
return `${Number(Number(gold).toFixed(3))} گرم`
|
||||
function metalEntries(row: Wallet) {
|
||||
const metals = props.metals?.length
|
||||
? props.metals
|
||||
: [{ metal_id: 1, label: 'طلا' }]
|
||||
|
||||
return metals
|
||||
.map((metal) => {
|
||||
const value = metalCellValue(row as Record<string, unknown>, metal.metal_id)
|
||||
return {
|
||||
metal_id: metal.metal_id,
|
||||
label: metal.label,
|
||||
display: formatMetalDisplayValue(value, metal.metal_id)
|
||||
}
|
||||
})
|
||||
.filter((entry) => metalCellValue(row as Record<string, unknown>, entry.metal_id) !== 0)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -11,9 +11,19 @@
|
||||
<span class="text-gray-500">ریال:</span>
|
||||
<span class="font-600 text-gray-900">{{ cashLabel }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span class="text-gray-500">طلا:</span>
|
||||
<span class="font-700 text-gray-900">{{ goldLabel }}</span>
|
||||
<span v-if="metalEntries.length === 1">
|
||||
<span class="text-gray-500">{{ metalEntries[0].label }}:</span>
|
||||
<span class="font-700 text-gray-900">{{ metalEntries[0].display }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="metalEntries.length > 1"
|
||||
class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-gray-600"
|
||||
>
|
||||
<span v-for="entry in metalEntries" :key="entry.metal_id">
|
||||
<span class="text-gray-500">{{ entry.label }}:</span>
|
||||
<span class="font-700 text-gray-900">{{ entry.display }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,9 +34,12 @@
|
||||
import type { Wallet } from '@/model/wallet'
|
||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { formatMetalDisplayValue, metalCellValue } from '@/utils/metalWeight'
|
||||
import type { WalletMetalColumn } from '../hooks/walletColumns'
|
||||
|
||||
const props = defineProps<{
|
||||
item: Wallet
|
||||
metals?: WalletMetalColumn[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
@@ -35,9 +48,25 @@ defineEmits<{
|
||||
|
||||
const customerName = computed(() => props.item.customer?.name || '-')
|
||||
const cashLabel = computed(() => usePrice(props.item.cash ?? 0, true, ''))
|
||||
const goldLabel = computed(() => {
|
||||
const gold = props.item.gold
|
||||
if (gold == null) return '-'
|
||||
return `${Number(Number(gold).toFixed(3))} گرم`
|
||||
|
||||
function metalValue(metalId: number): number {
|
||||
return metalCellValue(props.item as Record<string, unknown>, metalId)
|
||||
}
|
||||
|
||||
const metalEntries = computed(() => {
|
||||
const metals = props.metals?.length
|
||||
? props.metals
|
||||
: [{ metal_id: 1, label: 'طلا' }]
|
||||
|
||||
return metals
|
||||
.map((metal) => {
|
||||
const value = metalValue(metal.metal_id)
|
||||
return {
|
||||
metal_id: metal.metal_id,
|
||||
label: metal.label,
|
||||
display: formatMetalDisplayValue(value, metal.metal_id)
|
||||
}
|
||||
})
|
||||
.filter((entry) => metalValue(entry.metal_id) !== 0)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="h-full min-h-0">
|
||||
<BasicShowContext title="لیست کیف پول">
|
||||
<div class="flex h-full min-h-0 flex-col gap-3">
|
||||
<div class="hidden shrink-0 md:grid md:grid-cols-1 md:gap-3 lg:grid-cols-2">
|
||||
<div class="hidden shrink-0 md:grid md:grid-cols-1 md:gap-3 lg:grid-cols-2 xl:grid-cols-3">
|
||||
<div
|
||||
class="rounded-lg p-2.5 text-sm"
|
||||
:class="walletSumTone(sums?.cash)"
|
||||
@@ -23,20 +23,22 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="metal in metalBalanceCards"
|
||||
:key="metal.metal_id"
|
||||
class="rounded-lg p-2.5 text-sm"
|
||||
:class="walletSumTone(sums?.gold)"
|
||||
:class="walletSumTone(metal.gold)"
|
||||
>
|
||||
<div class="mb-2 font-medium">مجموع موجودی کیف طلا</div>
|
||||
<div class="mb-2 font-medium">{{ totalMetalWalletTitle(metal.metal_id, metal.label) }}</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="stat-inner rounded-lg px-2 py-1.5 text-center">
|
||||
<div class="text-xs text-gray-500">گرم</div>
|
||||
<div class="text-xs text-gray-500">{{ metal.display_unit ?? metalDisplayUnit(metal.metal_id) }}</div>
|
||||
<div class="mt-1 font-bold">
|
||||
{{ usePrice(sums?.gold ?? 0, true, 'گرم') }}
|
||||
{{ formatMetalDisplayValue(metal.gold ?? 0, metal.metal_id) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-inner rounded-lg px-2 py-1.5 text-center">
|
||||
<div class="text-xs text-gray-500">ریال</div>
|
||||
<div class="mt-1 font-bold break-all">{{ usePrice(sums?.goldToCash ?? 0) }}</div>
|
||||
<div class="mt-1 font-bold break-all">{{ usePrice(metal.goldToCash ?? 0) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,13 +79,16 @@ import type { TableInput } from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { changeCustomerActivation } from '@/api/customer'
|
||||
import { cols } from '../hooks/walletColumns'
|
||||
import { buildWalletColumns } from '../hooks/walletColumns'
|
||||
import { usePrefix } from '@core/composable/usePrefix'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { getWalletTable } from '@/api/wallet'
|
||||
import { usePrice } from '../../../../core/utils/usePrice'
|
||||
import WalletMobileCardList from './WalletMobileCardList.vue'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { totalMetalWalletTitle } from '@/utils/adminMetalLabels'
|
||||
import type { WalletTableSums } from '@/model/wallet'
|
||||
import { formatMetalDisplayValue, metalDisplayUnit } from '@/utils/metalWeight'
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||
@@ -92,13 +97,38 @@ const emits = defineEmits(['delete', 'edit', 'add', 'info', 'sums'])
|
||||
const { prefixCls } = usePrefix('pophoverstatus')
|
||||
|
||||
const table = ref(null)
|
||||
const sums = ref<any>()
|
||||
const sums = ref<WalletTableSums>()
|
||||
|
||||
const metalBalanceCards = computed(() => {
|
||||
if (sums.value?.metalBalances?.length) {
|
||||
return sums.value.metalBalances
|
||||
}
|
||||
|
||||
if (sums.value?.gold != null || sums.value?.goldToCash != null) {
|
||||
return [
|
||||
{
|
||||
metal_id: 1,
|
||||
label: 'طلا',
|
||||
gold: sums.value.gold ?? 0,
|
||||
goldToCash: sums.value.goldToCash ?? 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
|
||||
const tblProps = ref<any>({
|
||||
columns: cols,
|
||||
columns: buildWalletColumns(),
|
||||
api: async (params: TableInput) => {
|
||||
const data = await getWalletTable(params)
|
||||
sums.value = data?.sums
|
||||
const metals =
|
||||
data?.sums?.metalBalances?.map((metal) => ({
|
||||
metal_id: metal.metal_id,
|
||||
label: metal.label
|
||||
})) ?? []
|
||||
tblProps.value.columns = buildWalletColumns(metals)
|
||||
emits('sums', data?.sums)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -1,157 +1,75 @@
|
||||
import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
import type { DocItem } from '@/model/docType'*/
|
||||
import { metalLabel, walletMetalBalanceTitle } from '@/utils/adminMetalLabels'
|
||||
import { formatMetalDisplayValue, metalCellValue } from '@/utils/metalWeight'
|
||||
|
||||
//define store
|
||||
//const store = useStaticApiListsStore()
|
||||
//document types
|
||||
/*let docTypes: DocItem[]*/
|
||||
/*onMounted(async () => {
|
||||
docTypes = await store.readyDocTypeList()
|
||||
})
|
||||
const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
return {
|
||||
label: x.title,
|
||||
value: x.id
|
||||
}
|
||||
})*/
|
||||
export type WalletMetalColumn = { metal_id: number; label: string; display_unit?: string }
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: any) => {
|
||||
return record.index + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'کد کیف پول',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
// className: 'column-money'
|
||||
},
|
||||
{
|
||||
title: 'نام مشتری',
|
||||
dataIndex: 'customer.name',
|
||||
key: 'customer.name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'موجودی کیف پول (ریال)',
|
||||
dataIndex: 'cash',
|
||||
key: 'cash',
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
return usePrice(data.record.cash, true, '')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'موجودی کیف طلا (گرم)',
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
type: 'Text'
|
||||
}
|
||||
/* {
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'mobile',
|
||||
key: 'mobile',
|
||||
type: 'Number'
|
||||
},*/
|
||||
/* {
|
||||
title: 'نام فروشگاه',
|
||||
dataIndex: 'shop_name',
|
||||
key: 'shop_name',
|
||||
type: 'Text'
|
||||
},*/
|
||||
/* {
|
||||
title: 'وضعیت',
|
||||
dataIndex: 'active',
|
||||
key: 'active',
|
||||
customRender: ({ record }) => {
|
||||
return record.active ? (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
فعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
غیر فعال
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},*/
|
||||
/*{
|
||||
title: 'نقش',
|
||||
dataIndex: 'user_type_record',
|
||||
key: 'user_type_record',
|
||||
type: 'Select',
|
||||
export function buildWalletColumns(metals: WalletMetalColumn[] = []): BaseTableColumn[] {
|
||||
const baseColumns: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
filterComponentProps: {
|
||||
multiple: true
|
||||
},
|
||||
filterField: 'user_type_obj.id',
|
||||
filterComponent: ReadyUserTypeSelect,
|
||||
/!* customRender: (record: any) => {
|
||||
return record.record.user_type_record?.value
|
||||
}*!/
|
||||
customRender: ({ record }) => {
|
||||
const items = record.is_admin ? (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
ادمین
|
||||
</CustomTag>
|
||||
) : (
|
||||
record.user_type_obj.map((item: { title: any }, index: any) => {
|
||||
if (item) {
|
||||
return (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
{item?.title}
|
||||
</CustomTag>
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
return <div class="flex justify-center items-center gap-2">{items}</div>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'حوزه',
|
||||
dataIndex: 'section',
|
||||
key: 'section'
|
||||
},
|
||||
{
|
||||
title: 'شماره موبایل',
|
||||
dataIndex: 'mobile',
|
||||
type: 'Number',
|
||||
key: 'mobile'
|
||||
},
|
||||
{
|
||||
title: 'تاریخ ثبت',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (record: any) => {
|
||||
return toJalaliNoTime(record.record.created_at)
|
||||
return record.index + 1
|
||||
}
|
||||
},*/
|
||||
/* {
|
||||
title: 'عملیات',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
slot: 'action',
|
||||
fixed: 'right',
|
||||
filterable: false
|
||||
}*/
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'کد کیف پول',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'نام مشتری',
|
||||
dataIndex: 'customer.name',
|
||||
key: 'customer.name',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'موجودی کیف پول (ریال)',
|
||||
dataIndex: 'cash',
|
||||
key: 'cash',
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
return usePrice(data.record.cash, true, '')
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
if (metals.length === 0) {
|
||||
return [
|
||||
...baseColumns,
|
||||
{
|
||||
title: metalLabel.goldWalletBalanceGram(),
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
const value = Number(data.record?.gold ?? 0)
|
||||
return formatMetalDisplayValue(value, 1)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const metalColumns: BaseTableColumn[] = metals.map((metal) => ({
|
||||
title: walletMetalBalanceTitle(metal.metal_id, metal.label),
|
||||
dataIndex: `metal_gold_${metal.metal_id}`,
|
||||
key: `metal_${metal.metal_id}`,
|
||||
type: 'Text',
|
||||
customRender(data) {
|
||||
const value = metalCellValue(data.record, metal.metal_id)
|
||||
return formatMetalDisplayValue(value, metal.metal_id)
|
||||
}
|
||||
}))
|
||||
|
||||
return [...baseColumns, ...metalColumns]
|
||||
}
|
||||
|
||||
export function getWalletColumns(): BaseTableColumn[] {
|
||||
return buildWalletColumns()
|
||||
}
|
||||
|
||||
@@ -15,57 +15,45 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="gap-5 text-xs">
|
||||
<div>مجموع موجودی کیف طلا:</div>
|
||||
<div>{{ metalLabel.totalGoldWallet() }}:</div>
|
||||
<div class="font-bold">{{ usePrice(sums?.gold ?? 0, true, 'گرم') }}</div>
|
||||
</div>
|
||||
<div class="gap-5 text-xs">
|
||||
<div>مجموع موجودی کیف طلا:</div>
|
||||
<div>{{ metalLabel.totalGoldWallet() }}:</div>
|
||||
<div class="font-bold">{{ usePrice(sums?.goldToCash ?? 0) }}</div>
|
||||
</div>
|
||||
</div>-->
|
||||
<div class="grid grid-cols-3 gap-1 text-xs">
|
||||
<div class="bg-blue-600 text-white rounded p-1">مجموع</div>
|
||||
<div class="bg-blue-600 text-white rounded p-1">ریال</div>
|
||||
<div class="bg-blue-600 text-white rounded p-1">گرم</div>
|
||||
<div class="bg-blue-600 text-white rounded p-1">کیف طلا</div>
|
||||
<div
|
||||
class="text-white rounded p-1"
|
||||
:class="
|
||||
sums?.goldToCash >= 0
|
||||
? sums?.goldToCash === 0
|
||||
? 'bg-amber-600'
|
||||
: 'bg-green-600'
|
||||
: 'bg-red-600'
|
||||
"
|
||||
>
|
||||
{{ usePrice(sums?.goldToCash ?? 0, true, '') }}
|
||||
</div>
|
||||
<div
|
||||
class="text-white rounded p-1"
|
||||
:class="
|
||||
sums?.gold >= 0 ? (sums?.gold === 0 ? 'bg-amber-600' : 'bg-green-600') : 'bg-red-600'
|
||||
"
|
||||
>
|
||||
{{ usePrice(sums?.gold ?? 0, true, '') }}
|
||||
</div>
|
||||
<div class="bg-blue-600 text-white rounded p-1">وزن</div>
|
||||
|
||||
<template v-for="metal in mobileMetalRows" :key="`metal-${metal.metal_id}`">
|
||||
<div class="bg-blue-600 text-white rounded p-1">{{ metal.label }}</div>
|
||||
<div
|
||||
class="text-white rounded p-1"
|
||||
:class="sumToneClass(metal.goldToCash)"
|
||||
>
|
||||
{{ usePrice(metal.goldToCash ?? 0, true, '') }}
|
||||
</div>
|
||||
<div
|
||||
class="text-white rounded p-1"
|
||||
:class="sumToneClass(metal.gold)"
|
||||
>
|
||||
{{ formatMetalDisplayValue(metal.gold ?? 0, metal.metal_id) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="bg-blue-600 text-white rounded p-1">کیف پول</div>
|
||||
<div
|
||||
class="text-white rounded p-1"
|
||||
:class="
|
||||
sums?.cash >= 0 ? (sums?.cash === 0 ? 'bg-amber-600' : 'bg-green-600') : 'bg-red-600'
|
||||
"
|
||||
:class="sumToneClass(sums?.cash)"
|
||||
>
|
||||
{{ usePrice(sums?.cash ?? 0, true, '') }}
|
||||
</div>
|
||||
<div
|
||||
class="text-white rounded p-1"
|
||||
:class="
|
||||
sums?.cashToGold >= 0
|
||||
? sums?.cashToGold === 0
|
||||
? 'bg-amber-600'
|
||||
: 'bg-green-600'
|
||||
: 'bg-red-600'
|
||||
"
|
||||
:class="sumToneClass(sums?.cashToGold)"
|
||||
>
|
||||
{{ usePrice(sums?.cashToGold ?? 0, true, '') }}
|
||||
</div>
|
||||
@@ -165,6 +153,8 @@ import type { Item } from '@/model/Item'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { createCustomer, deleteCustomer, updateCustomer } from '@/api/customer'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import type { WalletTableSums } from '@/model/wallet'
|
||||
import { formatMetalDisplayValue } from '@/utils/metalWeight'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
@@ -172,7 +162,38 @@ const loading = ref<boolean>(false)
|
||||
//define store
|
||||
const store = useStaticApiListsStore()
|
||||
|
||||
const sums = ref()
|
||||
const sums = ref<WalletTableSums>()
|
||||
|
||||
const mobileMetalRows = computed(() => {
|
||||
if (sums.value?.metalBalances?.length) {
|
||||
return sums.value.metalBalances.map((metal) => ({
|
||||
metal_id: metal.metal_id,
|
||||
label: metal.label,
|
||||
gold: metal.gold ?? 0,
|
||||
goldToCash: metal.goldToCash ?? 0
|
||||
}))
|
||||
}
|
||||
|
||||
if (sums.value?.gold != null || sums.value?.goldToCash != null) {
|
||||
return [
|
||||
{
|
||||
metal_id: 1,
|
||||
label: 'طلا',
|
||||
gold: sums.value.gold ?? 0,
|
||||
goldToCash: sums.value.goldToCash ?? 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
|
||||
function sumToneClass(value?: number) {
|
||||
if (value == null || value >= 0) {
|
||||
return value === 0 ? 'bg-amber-600' : 'bg-green-600'
|
||||
}
|
||||
return 'bg-red-600'
|
||||
}
|
||||
|
||||
//define tbl
|
||||
const table = ref(null)
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#dc2626" />
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
<MobileStatCard bordered variant="warning" label="طلا" :value="goldLabel(row)">
|
||||
<MobileStatCard bordered variant="warning" :label="metalLabel.goldUnit()" :value="goldLabel(row)">
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" color="#b45309" />
|
||||
</template>
|
||||
@@ -107,6 +107,7 @@ import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { toNumber } from 'lodash'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ import type { TableInput } from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { changeCustomerActivation, getCustomerTable } from '@/api/customer'
|
||||
import { cols } from '../hooks/withdrawColumns'
|
||||
import { getWithdrawColumns } from '../hooks/withdrawColumns'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { usePrefix } from '@core/composable/usePrefix'
|
||||
import { message } from 'ant-design-vue'
|
||||
@@ -133,7 +133,7 @@ const confirmButtonLabel = computed(() =>
|
||||
)
|
||||
|
||||
const tblProps = computed(() => ({
|
||||
columns: cols,
|
||||
columns: getWithdrawColumns(),
|
||||
api: async (params: TableInput) => {
|
||||
const payload = { ...params, flow_type: props.flowType }
|
||||
lastTableParams.value = payload
|
||||
|
||||
@@ -3,6 +3,7 @@ import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import WithdrawStatus from "@/pages/menuPages/withdraw/components/WithdrawStatus.vue";
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
/*import BudgetSelect from '@/components/BudgetSelect.vue'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { FilterSelect } from '@/model/filterSelect'
|
||||
@@ -22,7 +23,8 @@ const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
|
||||
}
|
||||
})*/
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
export function getWithdrawColumns(): BaseTableColumn[] {
|
||||
return [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
@@ -82,7 +84,7 @@ export const cols: BaseTableColumn[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'طلا',
|
||||
title: metalLabel.goldUnit(),
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
type: 'Number',
|
||||
@@ -232,4 +234,5 @@ export const cols: BaseTableColumn[] = [
|
||||
fixed: 'right',
|
||||
filterable: false
|
||||
}*/
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,35 +1,13 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const Orders: RouteRecordRaw = {
|
||||
/** ریدایرکت آدرس قدیمی — منو زیر پیکربندی است */
|
||||
const Accounting: RouteRecordRaw = {
|
||||
name: 'accounting',
|
||||
path: '/accounting',
|
||||
component: () => import('@core/layouts/BasicLayout.vue'),
|
||||
redirect: { name: 'accountingMain' },
|
||||
meta: {
|
||||
icon: 'vuesax-linear:calculator',
|
||||
permissionsKey: 'accounting',
|
||||
title: 'حسابداری',
|
||||
disabled: false,
|
||||
hideMenu: false,
|
||||
hideChildren: true,
|
||||
order: 7
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'accountingMain',
|
||||
path: '',
|
||||
component: () => import('@/pages/menuPages/accounting/index.vue'),
|
||||
meta: {
|
||||
permissionsKey: 'project',
|
||||
icon: 'vuesax-linear:empty-wallet-time',
|
||||
order: 3,
|
||||
title: 'حسابداری',
|
||||
disabled: false,
|
||||
hideMenu: false
|
||||
//hideChildren: false
|
||||
}
|
||||
}
|
||||
]
|
||||
hideMenu: true
|
||||
}
|
||||
}
|
||||
|
||||
export default Orders
|
||||
export default Accounting
|
||||
|
||||
@@ -136,6 +136,17 @@ const Setting: RouteRecordRaw = {
|
||||
title: 'مدیران',
|
||||
order: 9
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'accountingMain',
|
||||
path: 'accounting',
|
||||
component: () => import('@/pages/menuPages/accounting/index.vue'),
|
||||
meta: {
|
||||
permissionsKey: 'accounting',
|
||||
icon: 'vuesax-linear:calculator',
|
||||
title: 'حسابداری',
|
||||
order: 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { m } from '@/utils/metalLabels'
|
||||
import { metalDisplayUnit } from '@/utils/metalWeight'
|
||||
|
||||
/** برچسبهای پرتکرار پنل ادمین که «طلا» در متن دارند */
|
||||
export const metalLabel = {
|
||||
goldBalance: () => m('تراز طلایی'),
|
||||
goldCommission: () => m('کارمزد طلایی'),
|
||||
goldWallet: () => m('کیف پول طلایی'),
|
||||
goldWalletShort: () => m('طلایی'),
|
||||
goldAmount: () => m('مقدار طلا'),
|
||||
goldInventory: () => m('موجودی طلایی'),
|
||||
goldGramPriceCustomer: () => m('قیمت هر گرم طلا برای مشتری (ریال)'),
|
||||
goldGramPriceOriginal: () => m('قیمت هر گرم طلا بدون سود (ریال)'),
|
||||
goldGramPriceOriginalSlash: () => m('/قیمت هر گرم طلا بدون سود (ریال)'),
|
||||
customersGold: () => m('طلای مشتریان'),
|
||||
goldValueInRial: () => m('ارزش طلا به ریال'),
|
||||
goldWalletCharge: () => m('شارژ کیف پول طلایی'),
|
||||
goldWalletBalance: () => m('موجودی طلایی (گرم)'),
|
||||
goldWalletBalanceGram: () => m('موجودی کیف طلا (گرم)'),
|
||||
goldInTransaction: () => m('مقدار طلا طی تراکنش (گرم)'),
|
||||
goldWalletTitle: () => m('کیف پول طلا'),
|
||||
goldPlaceholder: () => m('مقدار طلا'),
|
||||
goldUnit: () => m('طلا'),
|
||||
goldChargeShort: () => m('شارژ طلایی'),
|
||||
manageGoldCash: () => m('مدیریت موجودی ریالی و طلایی'),
|
||||
goldGifts: () => m('هدایای طلا'),
|
||||
goldTradeBuySell: (direction: 1 | 2 | number) =>
|
||||
m(`قیمت ${direction === 1 ? 'خرید' : 'فروش'} طلا:`),
|
||||
totalGoldWallet: () => m('مجموع موجودی کیف طلا'),
|
||||
goldWalletTab: () => m('کیف طلا'),
|
||||
}
|
||||
|
||||
export function walletMetalBalanceTitle(metalId: number, label?: string) {
|
||||
const unit = metalDisplayUnit(metalId)
|
||||
switch (metalId) {
|
||||
case 1:
|
||||
return m(`موجودی کیف طلا (${unit})`)
|
||||
case 2:
|
||||
return m(`موجودی کیف نقره (${unit})`)
|
||||
case 3:
|
||||
return m(`موجودی کیف مس (${unit})`)
|
||||
default:
|
||||
return label ? `موجودی کیف ${label} (${unit})` : metalLabel.goldWalletBalanceGram()
|
||||
}
|
||||
}
|
||||
|
||||
export function customerWalletMetalBalanceTitle(metalId: number, label?: string) {
|
||||
const unit = metalDisplayUnit(metalId)
|
||||
switch (metalId) {
|
||||
case 1:
|
||||
return m(`موجودی طلایی (${unit})`)
|
||||
case 2:
|
||||
return m(`موجودی نقره (${unit})`)
|
||||
case 3:
|
||||
return m(`موجودی مس (${unit})`)
|
||||
default:
|
||||
return label ? `موجودی ${label} (${unit})` : metalLabel.goldWalletBalance()
|
||||
}
|
||||
}
|
||||
|
||||
export function totalMetalWalletTitle(metalId: number, label?: string) {
|
||||
const unit = metalDisplayUnit(metalId)
|
||||
switch (metalId) {
|
||||
case 1:
|
||||
return m(`مجموع موجودی کیف طلا`)
|
||||
case 2:
|
||||
return m(`مجموع موجودی کیف نقره`)
|
||||
case 3:
|
||||
return m(`مجموع موجودی کیف مس`)
|
||||
default:
|
||||
return label ? `مجموع موجودی کیف ${label}` : metalLabel.totalGoldWallet()
|
||||
}
|
||||
}
|
||||
|
||||
export function metalBalanceTitle(metalId: number, label?: string) {
|
||||
switch (metalId) {
|
||||
case 1:
|
||||
return metalLabel.goldBalance()
|
||||
case 2:
|
||||
return m('تراز نقره')
|
||||
case 3:
|
||||
return m('تراز مس')
|
||||
default:
|
||||
return label ? `تراز ${label}` : metalLabel.goldBalance()
|
||||
}
|
||||
}
|
||||
|
||||
export function metalBalanceTitleWithUnit(metalId: number, label?: string) {
|
||||
const unit = metalDisplayUnit(metalId)
|
||||
return `${metalBalanceTitle(metalId, label)} (${unit})`
|
||||
}
|
||||
|
||||
export function transactionMetalAmountTitle(metalId: number, label?: string) {
|
||||
const unit = metalDisplayUnit(metalId)
|
||||
switch (metalId) {
|
||||
case 1:
|
||||
return m(`مقدار طلا طی تراکنش (${unit})`)
|
||||
case 2:
|
||||
return m(`مقدار نقره طی تراکنش (${unit})`)
|
||||
case 3:
|
||||
return m(`مقدار مس طی تراکنش (${unit})`)
|
||||
default:
|
||||
return label ? `مقدار ${label} طی تراکنش (${unit})` : metalLabel.goldInTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
export function dealPriceAtTransactionTitle(metalId: number, label?: string) {
|
||||
switch (metalId) {
|
||||
case 1:
|
||||
return m('قیمت زمان معامله طلا (ریال)')
|
||||
case 2:
|
||||
return m('قیمت زمان معامله نقره (ریال)')
|
||||
case 3:
|
||||
return m('قیمت زمان معامله مس (ریال)')
|
||||
default:
|
||||
return label ? `قیمت زمان معامله ${label} (ریال)` : 'قیمت زمان معامله (ریال)'
|
||||
}
|
||||
}
|
||||
|
||||
export function metalCommissionTitle(metalId: number, label?: string) {
|
||||
switch (metalId) {
|
||||
case 1:
|
||||
return metalLabel.goldCommission()
|
||||
case 2:
|
||||
return m('کارمزد نقره')
|
||||
case 3:
|
||||
return m('کارمزد مس')
|
||||
default:
|
||||
return label ? `کارمزد ${label}` : metalLabel.goldCommission()
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export function mapCategoriesToSelectItems(
|
||||
}))
|
||||
}
|
||||
|
||||
/** همان منبع جدول دستهبندیها — قبل از خواندن، همگامسازی میکند */
|
||||
/** همان منبع جدول دستهبندیها — دستههای جدید از محصولات اضافه میشوند؛ ترتیب دستی حفظ میشود */
|
||||
export async function syncAndLoadCategories(options?: {
|
||||
selectOnly?: boolean
|
||||
metalHint?: string
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { setCachedMainMetal } from '@/utils/metalLabels'
|
||||
|
||||
export function getConfigApp(key: string) {
|
||||
const config = window.__APP_CONFIG__ ? window.__APP_CONFIG__.user : {}
|
||||
|
||||
@@ -29,11 +31,14 @@ async function bootstrapSettingsFromApi(): Promise<void> {
|
||||
const list = Array.isArray(rows) ? rows : []
|
||||
const isStoreRow = list.find((item) => item.key === 'is_store')
|
||||
const mainWalletRow = list.find((item) => item.key === 'main_wallet_products')
|
||||
const mainMetalRow = list.find((item) => item.key === 'main_metal')
|
||||
storeModeFromSettings = isStoreRow ? isSwitchOn(isStoreRow.value) : false
|
||||
setCachedMainMetal(mainMetalRow?.value)
|
||||
const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||
setCachedMainWalletProductIds(parseMainWalletProductIds(mainWalletRow?.value))
|
||||
} catch {
|
||||
storeModeFromSettings = false
|
||||
setCachedMainMetal(null)
|
||||
const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||
setCachedMainWalletProductIds([])
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const DEFAULT_MAIN_METAL = 'طلا'
|
||||
|
||||
const mainMetalRef = ref(DEFAULT_MAIN_METAL)
|
||||
|
||||
export function resolveMainMetal(value?: string | number | null): string {
|
||||
const normalized = value?.toString().trim()
|
||||
return normalized || DEFAULT_MAIN_METAL
|
||||
}
|
||||
|
||||
export function setCachedMainMetal(value?: string | number | null): void {
|
||||
mainMetalRef.value = resolveMainMetal(value)
|
||||
}
|
||||
|
||||
export function getMainMetal(): string {
|
||||
return mainMetalRef.value
|
||||
}
|
||||
|
||||
export function useMainMetalRef() {
|
||||
return mainMetalRef
|
||||
}
|
||||
|
||||
/** جایگزینی طلایی/طلای/طلا با نام فلز — بدون صفت مسی/طلایی */
|
||||
export function applyMainMetalText(text: string, mainMetal = getMainMetal()): string {
|
||||
if (!text || mainMetal === DEFAULT_MAIN_METAL) return text
|
||||
|
||||
return text
|
||||
.replace(/طلایی/g, mainMetal)
|
||||
.replace(/طلای/g, mainMetal)
|
||||
.replace(/طلا/g, mainMetal)
|
||||
}
|
||||
|
||||
export function m(text: string): string {
|
||||
return applyMainMetalText(text)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { TRADE_WEIGHT_UNIT_GRAM, TRADE_WEIGHT_UNIT_KILOGRAM } from '@/utils/weightUnit'
|
||||
|
||||
export const METAL_ID_GOLD = 1
|
||||
export const METAL_ID_SILVER = 2
|
||||
export const METAL_ID_COPPER = 3
|
||||
|
||||
/** واحد نمایش موجودی/تراز هر فلز — مس کیلوگرم، بقیه گرم */
|
||||
export function metalDisplayUnit(metalId: number): string {
|
||||
return metalId === METAL_ID_COPPER ? TRADE_WEIGHT_UNIT_KILOGRAM : TRADE_WEIGHT_UNIT_GRAM
|
||||
}
|
||||
|
||||
export function isMetalKilogram(metalId: number): boolean {
|
||||
return metalId === METAL_ID_COPPER
|
||||
}
|
||||
|
||||
/** تبدیل مقدار ذخیرهشده به گرم (برای محاسبات ریالی) */
|
||||
export function gramsToMetalDisplay(grams: number, metalId: number): number {
|
||||
return isMetalKilogram(metalId) ? grams / 1000 : grams
|
||||
}
|
||||
|
||||
/** مقدار نمایشی از گرم */
|
||||
export function formatMetalWeightFromGrams(
|
||||
grams: number,
|
||||
metalId: number,
|
||||
decimals = 3
|
||||
): string {
|
||||
const value = gramsToMetalDisplay(grams, metalId)
|
||||
return formatMetalDisplayValue(value, metalId, decimals)
|
||||
}
|
||||
|
||||
/** مقدار از قبل در واحد نمایشی فلز */
|
||||
export function formatMetalDisplayValue(
|
||||
value: number,
|
||||
metalId: number,
|
||||
decimals = 3
|
||||
): string {
|
||||
const unit = metalDisplayUnit(metalId)
|
||||
return `${Number(Number(value).toFixed(decimals))} ${unit}`
|
||||
}
|
||||
|
||||
export function formatMetalDisplayValuePlain(
|
||||
value: number,
|
||||
metalId: number,
|
||||
decimals = 3
|
||||
): string {
|
||||
return Number(Number(value).toFixed(decimals)).toLocaleString('fa-IR', {
|
||||
maximumFractionDigits: decimals
|
||||
})
|
||||
}
|
||||
|
||||
export function metalCellValue(
|
||||
record: Record<string, unknown>,
|
||||
metalId: number,
|
||||
prefix = 'metal_gold'
|
||||
): number {
|
||||
const flatKey = `${prefix}_${metalId}`
|
||||
if (record[flatKey] != null) {
|
||||
return Number(record[flatKey])
|
||||
}
|
||||
|
||||
const mapKey = prefix === 'wallet_metal_gold' ? 'wallet_metal_gold' : 'metal_gold'
|
||||
const map = record[mapKey] as Record<string, number> | undefined
|
||||
if (!map) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Number(map[String(metalId)] ?? 0)
|
||||
}
|
||||
@@ -13,3 +13,17 @@ export function resolveTradeWeightUnit(settingValue?: string | number | null) {
|
||||
export function weightFieldLabelForUnit(unit?: string | null) {
|
||||
return isKilogramUnit(unit) ? 'وزن به کیلوگرم' : 'وزن به گرم'
|
||||
}
|
||||
|
||||
export function formatAmountWithUnit(
|
||||
value: number | null | undefined,
|
||||
unit?: string | null,
|
||||
decimals = 3
|
||||
): string {
|
||||
if (value == null || Number.isNaN(Number(value))) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const resolvedUnit = (unit ?? TRADE_WEIGHT_UNIT_GRAM).trim() || TRADE_WEIGHT_UNIT_GRAM
|
||||
|
||||
return `${Number(Number(value).toFixed(decimals))} ${resolvedUnit}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user