added pdf in withdraw and deposit and fix bug

This commit is contained in:
2026-06-09 21:06:29 +03:30
parent 8582ce4d1b
commit cf3dbbceb1
21 changed files with 454 additions and 85 deletions
+20
View File
@@ -12,3 +12,23 @@ export const getPriceTable = async (params: TableInput) =>
export const getLastPrice = async () => await useAxios.get<last>(`/api/price/last`)
export const methodePrices = async () => await useAxios.get<MethodePrices[]>('/admin/p')
export interface ZaarboxProductOption {
key: string
name: string
id: string
category: string
active: boolean
}
export interface ZaarboxProductsResponse {
code: number
message: string
data: {
items: ZaarboxProductOption[]
api_url: string
}
}
export const getZaarboxProducts = async () =>
await useAxios.get<ZaarboxProductsResponse>('/admin/zaarbox/products')
+6 -1
View File
@@ -10,9 +10,14 @@ export const chargeWallet = async (params: Charge, type: 'gold' | 'money') =>
? await useAxios.post<boolean>('/admin/wallet/charge', params)
: await useAxios.post<boolean>('/admin/wallet/charge_gold', params)
export const getWithdrawTable = async (params: TableInput) =>
export const getWithdrawTable = async (params: TableInput & { flow_type?: string }) =>
await useAxios.post<Array<Withdraw>>('/admin/wallet/withdraw/table', params)
export const exportWithdrawReport = async (params: TableInput & { flow_type?: string }) =>
await useAxios.post<string>('/admin/wallet/withdraw/report', params, {
responseType: 'text'
})
export const completeWithdraw = async (params: Withdraw) =>
await useAxios.post<boolean>('/admin/wallet/withdraw/complete', params)
+7 -2
View File
@@ -79,8 +79,13 @@
z-index: 1100 !important;
}
.ant-picker-dropdown{
z-index: 1100 !important;
// dropdown سلکت و date picker داخل مودال (باید بالاتر از 1100 باشد)
.ant-select-dropdown {
z-index: 1200 !important;
}
.ant-picker-dropdown {
z-index: 1200 !important;
}
@@ -193,6 +193,7 @@
<div>
<div class="flex items-center gap-2">
<ASelect
placeholder="مرتب سازی"
class="flex-1"
:options="sortableColumns"
v-model:value="orderColumnKey"
+1
View File
@@ -23,6 +23,7 @@ export interface Product{
open_buy: boolean
open_sell: boolean
show: boolean
show_in_assets?: boolean
title: string
updated_at: string
methodOption: number
+2 -2
View File
@@ -20,7 +20,7 @@ export interface Withdraw {
cash?: number
gold?: number
note?: string
tracked_number?: number
tracked_number?: string
date_at?: string
sts?: number
created_at?: string
@@ -28,7 +28,7 @@ export interface Withdraw {
sts_str?: string
daj?: string
customer?: User
bank_account?: { cart_number?: string }
bank_account?: { cart_number?: string; shaba_number?: string }
transaction?: WithdrawTransaction[]
}
@@ -10,6 +10,13 @@
<template #unCheckedChildren>غیرفعال</template>
</a-switch>
<ZaarboxPriceKeyField
v-else-if="isZaarboxPriceKey"
:model-value="modelValue"
:disabled="disabled"
@update:value="emit('update:value', $event)"
/>
<a-select
v-else-if="fieldType === 'select'"
:value="modelValue || undefined"
@@ -54,6 +61,7 @@
</template>
<script setup lang="ts">
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
import {
isSwitchOn,
parseSelectOptions,
@@ -83,6 +91,8 @@ const fieldType = computed<SettingFieldType>(() =>
})
)
const isZaarboxPriceKey = computed(() => props.settingKey === 'zaarbox_price_key')
const selectItems = computed(() =>
parseSelectOptions({
key: props.settingKey,
@@ -0,0 +1,76 @@
<template>
<a-select
:value="modelValue || undefined"
:disabled="disabled"
:loading="loading"
allow-clear
show-search
placeholder="انتخاب محصول زرباکس"
class="w-full"
option-filter-prop="label"
@update:value="onSelectChange"
>
<a-select-option
v-for="item in allOptions"
:key="item.key"
:value="item.key"
:label="`${item.name} ${item.key}`"
>
<div class="leading-snug">
<div>{{ item.name }}</div>
<div class="text-xs text-gray-400 direction-ltr text-right">{{ item.key }}</div>
</div>
</a-select-option>
</a-select>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { getZaarboxProducts, type ZaarboxProductOption } from '@/api/price'
const props = withDefaults(
defineProps<{
modelValue: string
disabled?: boolean
}>(),
{ disabled: false }
)
const emit = defineEmits<{ 'update:value': [value: string] }>()
const loading = ref(false)
const options = ref<ZaarboxProductOption[]>([])
const allOptions = computed(() => {
const current = (props.modelValue ?? '').trim()
if (!current || options.value.some((item) => item.key === current)) {
return options.value
}
return [
{ key: current, name: current, id: '', category: '', active: true },
...options.value,
]
})
onMounted(() => {
void loadOptions()
})
async function loadOptions() {
loading.value = true
try {
const response = await getZaarboxProducts()
options.value = response?.data?.items ?? []
} catch (e) {
console.error(e)
options.value = []
} finally {
loading.value = false
}
}
function onSelectChange(val: string | undefined) {
emit('update:value', val ?? '')
}
</script>
+1 -1
View File
@@ -289,7 +289,7 @@ const SETTING_GROUPS: Array<{
id: 'general',
title: 'عمومی و اطلاعات اپ',
icon: 'vuesax-linear:home-2',
match: (k) => /^(name_|app_|admin_|server_|bace_|key$|color_|logo_|title_pwa|title_fav|metal_type)/.test(k)
match: (k) => /^(name_|app_|admin_|server_|bace_|key$|color_|logo_|title_pwa|title_fav|metal_type|unit$)/.test(k)
},
{
id: 'pricing',
@@ -75,6 +75,7 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
external_order_api_enabled: 'switch',
store_status: 'select',
credit_rial_price_unit: 'select',
zaarbox_price_key: 'select',
node_client_id: 'text',
node_shared_secret: 'text',
support_messenger_id: 'text',
@@ -99,6 +100,7 @@ const SELECT_OPTIONS_BY_KEY: Record<string, string> = {
payping_gateway_name: 'sepal,',
credit_rial_price_unit: 'gram,mesghal,unit',
store_status: 'باز,بسته',
unit: 'گرم,کیلوگرم',
support_messenger: 'bale,telegram,whatsapp,rubika,eitaa,soroush'
}
@@ -3,7 +3,57 @@
<BaseCollapse title="اطلاعات محصول" active-key="0" :actives="activeKey">
<template #content>
<div
v-if="form.group_type === '' || form.group_type !== 'آبشده'"
v-if="isMeltedCategory"
class="md:flex flex-col gap-3 border-1 border-solid rounded-lg border-gray-200 p-3"
>
<div class="items-center">
<span class="ml-4">مرجع قیمت :</span>
<a-radio-group :value="meltedPriceOption" name="meltedPriceGroup" @update:value="$emit('update:meltedPriceOption', $event)">
<a-radio value="app">قیمت اپ (صفحه اصلی)</a-radio>
<a-radio value="fixed">ثابت</a-radio>
<a-radio value="reference">مرجع متفاوت</a-radio>
</a-radio-group>
</div>
<div v-if="meltedPriceOption === 'fixed'" class="md:flex items-center gap-3">
<span>قیمت ثابت :</span>
<BasicInput class="md:w-[20rem]" addon-before="ریال" type="price" v-model:value="form.price" />
</div>
<div v-if="meltedPriceOption === 'reference'" class="flex flex-col gap-2">
<div class="md:flex justify-between items-center">
<div class="ml-4">مرجع قیمت :</div>
<BasicSelect class="w-11rem method_style" allowClear :items="pricesList" valueName="name" keyName="id" v-model:value="form.reading_method" />
</div>
<div class="md:flex justify-between items-center">
<div class="ml-4">تعدیل نسبت به قیمت مرجع (٪) :</div>
<BasicInput
class="w-11rem"
type="number"
v-model:value="form.price_offset_percent"
placeholder="مثلاً 5 یا -3"
/>
</div>
<div class="text-xs text-gray-500">
مثبت = گران‌تر، منفی = ارزان‌تر نسبت به قیمت مرجع.
</div>
</div>
<div v-if="meltedPriceOption === 'app'" class="flex flex-col gap-2">
<div class="md:flex justify-between items-center">
<div class="ml-4">تعدیل نسبت به قیمت اپ (٪) :</div>
<BasicInput
class="w-11rem"
type="number"
v-model:value="form.price_offset_percent"
placeholder="مثلاً 5 یا -3"
/>
</div>
<div class="text-xs text-gray-500">
قیمت خرید/فروش از قیمت صفحه اصلی اپ محاسبه می‌شود. مثبت = گران‌تر، منفی = ارزان‌تر.
</div>
</div>
</div>
<div
v-if="!isMeltedCategory"
class="md:flex justify-between border-1 border-solid rounded-lg border-gray-200 p-2"
>
<div class="items-center">
@@ -46,7 +96,7 @@
</div>
<div
v-if="form.group_type === '' || form.group_type !== 'آبشده'"
v-if="!isMeltedCategory"
class="md:flex justify-between p-2 border-1 border-solid rounded-lg border-gray-200 mt-2"
>
<div>
@@ -59,36 +109,30 @@
<div v-if="weightOption === '1'" class="flex items-center">
<span class="ml-4 ">{{ weightFieldLabel }} :</span>
<BasicInput v-model:value="form.amount" type="number" />
<div class="mt-1 mr-4" v-if="form.amount && form.amount < 1 && 0 < form.amount && !isBourseCuProject">معادل {{ form.amount * 1000 }} سوت</div>
<div class="mt-1 mr-4" v-if="form.amount && form.amount < 1 && 0 < form.amount && tradeWeightUnit === 'گرم'">معادل {{ form.amount * 1000 }} سوت</div>
</div>
</div>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="grid md:grid-cols-2 gap-4">
<a-form-item label="عنوان" name="title" :rules="inputRules()"><BasicInput v-model:value="form.title" /></a-form-item>
<a-form-item v-if="getConfigApp('name') === 'boursecu'" label="دسته بندی" name="group_type" :rules="inputRules()">
<BasicSelect v-model:value="form.group_type" :items="boursemesCategoryItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item v-else-if="getConfigApp('name') === 'garoosi'" label="دسته بندی" name="group_type" :rules="inputRules()">
<BasicSelect v-model:value="form.group_type" :items="garoosiCategoryItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item v-else label="دسته بندی" name="group_type" :rules="inputRules()">
<BasicSelect v-model:value="form.group_type" :items="categoryItems" valueName="value" keyName="value" />
<a-form-item label="دسته بندی" name="group_type" :rules="inputRules()">
<BasicSelect v-model:value="form.group_type" :items="categoryItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item label="جنس فلز" name="metal_id" :rules="inputRules()">
<BasicSelect v-model:value="form.metal_id" :items="metalItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item label="نوع قیمت" name="direct_price" :rules="inputRules()">
<a-form-item v-if="!isMeltedCategory" label="نوع قیمت" name="direct_price" :rules="inputRules()">
<BasicSelect v-model:value="form.direct_price" :items="priceItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item label="واحد" name="unit" :rules="inputRules()">
<BasicSelect v-model:value="form.unit" :items="unitItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item v-if="form.group_type === '' || form.group_type !== 'آبشده'" label="هزینه نقدی موقع خرید" name="cash_fee_buy" :rules="inputRules()">
<BasicInput v-model:value="form.cash_fee_buy" type="number" />
<a-form-item label="هزینه نقدی موقع خرید" name="cash_fee_buy" :rules="inputRules()">
<BasicInput v-model:value="form.cash_fee_buy" addon-before="ریال" type="price" />
</a-form-item>
<a-form-item v-if="form.group_type === '' || form.group_type !== 'آبشده'" label="هزینه نقدی موقع فروش" name="cash_fee_sell" :rules="inputRules()">
<BasicInput v-model:value="form.cash_fee_sell" type="number" />
<a-form-item label="هزینه نقدی موقع فروش" name="cash_fee_sell" :rules="inputRules()">
<BasicInput v-model:value="form.cash_fee_sell" addon-before="ریال" type="price" />
</a-form-item>
<a-form-item label="زمان شروع فعال سازی" name="auto_open_at">
<TimePicker v-model:value="form.auto_open_at" :locale="customLocale" placeholder="انتخاب زمان" :defaultValue="dayjs('00:00:00', 'HH:mm:ss')" />
@@ -99,6 +143,9 @@
<a-form-item label="محدودیت مبلغ سفارش" name="order_limit_enabled">
<a-switch v-model:checked="form.order_limit_enabled" @change="onOrderLimitChange" />
</a-form-item>
<a-form-item label="نمایش در صفحه دارایی‌ها" name="show_in_assets">
<a-switch v-model:checked="form.show_in_assets" />
</a-form-item>
<template v-if="form.order_limit_enabled">
<div class="col-span-2 text-xs text-gray-500 -mt-2 mb-1">
حداقل یکی از «حداقل» یا «حداکثر» الزامی است؛ می‌توانید فقط یکی را پر کنید.
@@ -135,19 +182,20 @@ import { inputRules } from '@/utils/inputRules'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import BaseCollapse from '@/components/BaseCollapse.vue'
import IndexImageUploader from '@/components/ProductUploader/IndexImageUploader.vue'
import { getConfigApp } from '@/utils/config'
import { getConfiguration } from '@/api/configuration'
import { isMeltedTradeGroup } from '@/utils/productCategories'
import { resolveTradeWeightUnit, weightFieldLabelForUnit } from '@/utils/weightUnit'
import dayjs from 'dayjs'
import { computed } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
const props = defineProps<{
form: any
formRef: any
methodOption: string
weightOption: string
meltedPriceOption: string
activeKey: string[]
pricesList: Array<{ id: string; name: string }>
boursemesCategoryItems: any[]
garoosiCategoryItems: any[]
categoryItems: any[]
metalItems: any[]
priceItems: any[]
@@ -157,13 +205,47 @@ const props = defineProps<{
editId?: number
}>()
const isBourseCuProject = computed(() => getConfigApp('name') === 'boursecu')
const weightFieldLabel = computed(() =>
isBourseCuProject.value && (props.form.unit === 'کیلوگرم' || !props.form.unit)
? 'وزن به کیلوگرم'
: 'وزن به گرم'
const tradeWeightUnit = ref('گرم')
const isMeltedCategory = computed(() => isMeltedTradeGroup(props.form.group_type))
watch(
() => props.meltedPriceOption,
(option) => {
if (option === 'app' || option === 'fixed') {
props.form.reading_method = null
}
if (option === 'app') {
props.form.direct_price = 0
}
if (option === 'fixed') {
props.form.direct_price = 1
props.form.price_offset_percent = 0
}
if (option === 'reference') {
props.form.direct_price = 0
}
}
)
onMounted(async () => {
try {
const rows = await getConfiguration()
const unitRow = Array.isArray(rows) ? rows.find((row) => row.key === 'unit') : null
tradeWeightUnit.value = resolveTradeWeightUnit(unitRow?.value)
} catch {
tradeWeightUnit.value = 'گرم'
}
})
const weightFieldLabel = computed(() => {
const productUnit = props.form.unit?.trim()
if (productUnit === 'کیلوگرم' || productUnit === 'گرم') {
return weightFieldLabelForUnit(productUnit)
}
return weightFieldLabelForUnit(tradeWeightUnit.value)
})
function onOrderLimitChange(checked: boolean) {
if (!checked) {
props.form.min_order_cash = undefined
@@ -176,6 +258,7 @@ defineEmits<{
(e: 'minusPrice'): void
(e: 'update:methodOption', value: string): void
(e: 'update:weightOption', value: string): void
(e: 'update:meltedPriceOption', value: string): void
(e: 'update:activeKey', value: string[]): void
(e: 'update:productIndexImage', value: any[]): void
(e: 'uploadedIndexFile', id: number, response?: Record<string, any>): void
+37 -37
View File
@@ -23,17 +23,17 @@
:weightOption="weightOption"
:activeKey="activeKey_"
:pricesList="pricesList"
:boursemesCategoryItems="BoursemesCategoryItems"
:garoosiCategoryItems="GaroosiCategoryItems"
:categoryItems="categoryItems"
:categoryItems="categorySelectItems"
:metalItems="metalItems"
:priceItems="priceItems"
:unitItems="unitItems"
:customLocale="customLocale"
:productIndexImage="ProductIndexImage"
:editId="editId"
:meltedPriceOption="meltedPriceOption"
@update:methodOption="methodOption = $event"
@update:weightOption="weightOption = $event"
@update:meltedPriceOption="meltedPriceOption = $event"
@update:activeKey="activeKey_ = $event"
@update:productIndexImage="ProductIndexImage = $event"
@addPrice="addPrice"
@@ -73,7 +73,7 @@ import {
unwrapApiRecord
} from '@/utils/file'
import { normalizeProductOrderLimitForm, validateProductOrderLimitForm } from '@/utils/productOrderLimit'
import { getConfigApp } from '@/utils/config'
import { buildCategorySelectItems, isMeltedTradeGroup, resolveMeltedPriceSource } from '@/utils/productCategories'
dayjs.extend(customParseFormat)
@@ -83,6 +83,7 @@ const refreshTable = () => table.value?.fetch()
const methodOption = ref('1')
const weightOption = ref('1')
const meltedPriceOption = ref('app')
const activeKey_ = ref(['0'])
const ProductIndexImage = ref<Array<FileList>>([])
const addUserVisible = ref(false)
@@ -104,42 +105,18 @@ const customLocale = ref({
}
})
const unitItems = computed<Array<Item>>(() => {
if (getConfigApp('name') === 'boursecu') {
return [{ value: 'کیلوگرم' }, { value: 'عدد' }, { value: 'گرم' }]
}
const unitItems = computed<Array<Item>>(() => [
{ value: 'گرم' },
{ value: 'کیلوگرم' },
{ value: 'عدد' }
])
return [{ value: 'عدد' }, { value: 'گرم' }]
})
const GaroosiCategoryItems: Array<Item> = [
{ value: 'طلا' },
{ value: 'مکعب' },
{ value: 'گوی' },
{ value: 'سکه' },
{ value: 'سکه پارسیان' },
{ value: 'مس' },
{ value: 'نقره' }
]
const BoursemesCategoryItems: Array<Item> = [
{ value: 'آبشده' },
{ value: 'کاتد مس' },
{ value: 'مفتول مس' }
]
const categoryItems: Array<Item> = [
{ value: 'آبشده' },
{ value: 'مکعب' },
{ value: 'گوی' },
{ value: 'سکه' },
{ value: 'سکه پارسیان' },
{ value: 'مس' },
{ value: 'نقره' }
]
const metalItems: Array<Item> = [
{ key: 1, value: 'طلا' },
{ key: 2, value: 'نقره' },
{ key: 3, value: 'مس' }
{ key: 3, value: 'مس' },
]
const categorySelectItems = computed(() => buildCategorySelectItems(metalItems))
const priceItems: Array<Item> = [
{ key: 0, value: 'متغیر' },
{ key: 1, value: 'ثابت' }
@@ -241,6 +218,10 @@ async function edit(record: Product) {
}
methodOption.value = productData.reading_method ? '2' : '1'
meltedPriceOption.value = resolveMeltedPriceSource(productData)
if (isMeltedTradeGroup(productData.group_type)) {
methodOption.value = '1'
}
weightOption.value = productData.amount && productData.amount !== 1 ? '1' : '2'
const start = productData.auto_open_at
@@ -277,6 +258,7 @@ function resetData() {
ProductIndexImage.value = []
methodOption.value = '1'
weightOption.value = '1'
meltedPriceOption.value = 'app'
form.value = {
unit: '',
group_type: '',
@@ -291,6 +273,7 @@ function resetData() {
file_id: undefined,
file: undefined,
order_limit_enabled: false,
show_in_assets: true,
min_order_cash: undefined,
max_order_cash: undefined,
auto_open_at: dayjs('00:00:00', 'HH:mm:ss'),
@@ -314,8 +297,25 @@ async function ok() {
loading.value = false
return
}
if (methodOption.value === '1') {
delete payload.reading_method
if (isMeltedTradeGroup(payload.group_type)) {
payload.amount = 1
payload.direct_price = meltedPriceOption.value === 'fixed' ? 1 : 0
payload.cash_fee_buy = payload.cash_fee_buy ?? 0
payload.cash_fee_sell = payload.cash_fee_sell ?? 0
if (meltedPriceOption.value === 'app') {
payload.reading_method = null
delete payload.price
payload.price_offset_percent = Number(payload.price_offset_percent ?? 0)
} else if (meltedPriceOption.value === 'fixed') {
payload.reading_method = null
payload.price_offset_percent = 0
} else {
delete payload.price
payload.price_offset_percent = Number(payload.price_offset_percent ?? 0)
}
} else if (methodOption.value === '1') {
payload.reading_method = null
payload.price_offset_percent = 0
} else {
delete payload.price
@@ -32,13 +32,24 @@
import { message } from 'ant-design-vue'
import { getWithdrawTable, updateSts } from '@/api/wallet'
import type { Withdraw } from '@/model/wallet'
import type { TableInput } from '@core/type/baseTable'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
import type { WithdrawFlowType } from '@/pages/menuPages/withdraw/types'
import WithdrawMobileDetailSheet from './WithdrawMobileDetailSheet.vue'
import WithdrawMobileListItem from './WithdrawMobileListItem.vue'
const props = withDefaults(
defineProps<{
flowType?: WithdrawFlowType
}>(),
{
flowType: 'all'
}
)
const emit = defineEmits<{
(e: 'withdraw', item: Withdraw): void
(e: 'weight', item: Withdraw): void
@@ -47,10 +58,17 @@ const emit = defineEmits<{
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Withdraw>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Withdraw>({
fetchApi: getWithdrawTable,
fetchApi: (params: TableInput) => getWithdrawTable({ ...params, flow_type: props.flowType }),
onFetched: (rows) => syncSelectedItem(rows)
})
watch(
() => props.flowType,
() => {
reload()
}
)
async function changeStatusHandler(
item: Withdraw,
statusEntry: [string, (typeof status)[keyof typeof status]]
@@ -74,6 +74,7 @@
<MobileDetailRow label="کد کاربر" :value="row.customer_id ?? '-'" />
<MobileDetailRow label="نام و نام خانوادگی" full-width :value="row.customer?.name || '-'" />
<MobileDetailRow label="شماره کارت" full-width :value="row.bank_account?.cart_number || '-'" />
<MobileDetailRow label="شماره شبا" full-width :value="row.bank_account?.shaba_number || '-'" />
<MobileDetailRow label="تاریخ ثبت" full-width :value="dateLabel(row)" />
</MobileInfoGrid>
</template>
@@ -2,7 +2,7 @@
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست برداشت و واریز">
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<BaseTable v-if="!isMobile" :key="flowType" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<!-- <BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مشتری عمده</div>
@@ -12,7 +12,7 @@
<template #tracked="{ column, record }">
<div v-if="record.tracked_number">{{ record.tracked_number }}</div>
<div v-else>
<BaseButton @click="emits('withdraw', record.id)">تایید برداشت</BaseButton>
<BaseButton @click="emits('withdraw', record)">{{ confirmButtonLabel }}</BaseButton>
</div>
</template>
<template #action="{ column, record }">
@@ -67,6 +67,7 @@
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
:flow-type="props.flowType"
@withdraw="(row) => emits('withdraw', row)"
@weight="(row) => emits('weight', row)"
/>
@@ -88,6 +89,16 @@ import { getOrderTable } from '@/api/order'
import {getWithdrawTable, updateSts} from '@/api/wallet'
import WithdrawMobileCardList from './WithdrawMobileCardList.vue'
import { isMobile } from '@core/utils/is'
import type { WithdrawFlowType } from '../types'
const props = withDefaults(
defineProps<{
flowType?: WithdrawFlowType
}>(),
{
flowType: 'all'
}
)
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
@@ -107,14 +118,28 @@ function checkPermission(temp: string) {
//define table
const table = ref(null)
const lastTableParams = ref<TableInput & { flow_type?: string }>({})
const tblProps = ref<any>({
const confirmButtonLabel = computed(() =>
props.flowType === 'deposit' ? 'تایید واریز' : 'تایید برداشت'
)
const tblProps = computed(() => ({
columns: cols,
api: (params: TableInput) => {
return getWithdrawTable(params)
api: async (params: TableInput) => {
const payload = { ...params, flow_type: props.flowType }
lastTableParams.value = payload
return getWithdrawTable(payload)
}
//bordered: true
})
}))
watch(
() => props.flowType,
() => {
reload()
},
{ flush: 'post' }
)
const loading = ref<boolean>(false)
async function changeCustomerActivationFunction(id: number) {
@@ -138,8 +163,8 @@ async function fetch() {
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
fetch,
getExportParams: () => ({ ...lastTableParams.value, flow_type: props.flowType })
})
import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
@@ -96,6 +96,15 @@ export const cols: BaseTableColumn[] = [
return data.record.bank_account?.cart_number ?? '_'
}
},
{
title: 'شماره شبا',
dataIndex: 'bank_account.shaba_number',
key: 'bank_account.shaba_number',
type: 'Text',
customRender: (data) => {
return data.record.bank_account?.shaba_number ?? '_'
}
},
{
title: 'وضعیت',
dataIndex: 'sts',
+44 -7
View File
@@ -1,10 +1,25 @@
<template>
<div>
<BasicMainCard>
<div class="flex w-full h-full">
<div class="flex w-full h-full flex-col">
<div class="mb-3 flex shrink-0 flex-wrap items-center justify-between gap-3 px-1">
<ARadioGroup v-model:value="flowType" button-style="solid">
<ARadioButton value="all">همه</ARadioButton>
<ARadioButton value="withdraw">برداشت</ARadioButton>
<ARadioButton value="deposit">واریز</ARadioButton>
</ARadioGroup>
<BaseButton
icon="vuesax-linear:document-download"
:loading="exportLoading"
@click="exportPdf"
>
خروجی PDF
</BaseButton>
</div>
<tbl
ref="table"
class="w-full h-full"
class="min-h-0 flex-1 w-full"
:flow-type="flowType"
@edit="edit"
@delete="deleteItem"
@withdraw="openWithdraw"
@@ -28,7 +43,7 @@
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="">
<a-form-item label="شماره پیگیری" name="tracked_number" :rules="inputRules()">
<BasicInput v-model:value="form.tracked_number" type="number" />
<BasicInput v-model:value="form.tracked_number" />
</a-form-item>
<a-form-item label="تاریخ" name="date_at">
<BaseDatePicker v-model:value="form.date_at" show-time format="YYYY-MM-DD HH:mm" />
@@ -99,8 +114,10 @@ import type { Customer } from '@/model/customer'
import { createCustomer, deleteCustomer, updateCustomer } from '@/api/customer'
import type { Order } from '@/model/order'
import type { Wallet, Withdraw } from '@/model/wallet'
import {completeWithdraw, getWithdrawById, sendWeights} from '@/api/wallet'
import {completeWithdraw, exportWithdrawReport, getWithdrawById, sendWeights} from '@/api/wallet'
import Button from "@core/components/BaseButton";
import type { WithdrawFlowType } from '@/pages/menuPages/withdraw/types'
import { openHtmlPrintWindow } from '@/pages/menuPages/withdraw/utils/openHtmlPrintWindow'
//import DatePicker from '@core/components/_sample/components/datePicker.vue'
//loading
@@ -110,9 +127,29 @@ const loading = ref<boolean>(false)
const store = useStaticApiListsStore()
//define tbl
const table = ref(null)
const table = ref<{ fetch: () => Promise<void>; getExportParams?: () => Record<string, unknown> } | null>(null)
const refreshTable = () => table?.value?.fetch()
const flowType = ref<WithdrawFlowType>('all')
const exportLoading = ref(false)
async function exportPdf() {
try {
exportLoading.value = true
const params = {
...(table.value?.getExportParams?.() ?? {}),
flow_type: flowType.value
}
const html = await exportWithdrawReport(params as any)
openHtmlPrintWindow(html)
} catch (e) {
console.error(e)
message.error('خطا در تهیه خروجی PDF')
} finally {
exportLoading.value = false
}
}
let updateKey = ref<number>(0)
//form
@@ -215,8 +252,8 @@ async function edit(record: Customer) {
loading.value = false
}*/
function openWithdraw(val: Withdraw) {
confirmWithdraw.value = val
function openWithdraw(val: Withdraw | number) {
confirmWithdraw.value = typeof val === 'number' ? { id: val } : val
resetData()
confirmWithdrawVisible.value = true
}
+1
View File
@@ -0,0 +1 @@
export type WithdrawFlowType = 'all' | 'withdraw' | 'deposit'
@@ -0,0 +1,11 @@
export function openHtmlPrintWindow(html: string) {
const printWindow = window.open('', '_blank')
if (!printWindow) {
throw new Error('popup blocked')
}
printWindow.document.open()
printWindow.document.write(html)
printWindow.document.close()
printWindow.focus()
}
+49
View File
@@ -0,0 +1,49 @@
/** دسته‌بندی یکسان محصول در همه اپ‌های ماژولار */
export const PRODUCT_CATEGORY_ABSHODE = 'آبشده'
export const UNIFIED_PRODUCT_CATEGORIES = [
PRODUCT_CATEGORY_ABSHODE,
'مکعب',
'گوی',
'سکه',
'سکه پارسیان',
'مس',
'نقره',
] as const
export const LEGACY_GROUP_KATOD = 'کاتد مس'
export const LEGACY_GROUP_MAFTOL = 'مفتول مس'
export const LEGACY_GROUP_GOLD = 'طلا'
export function normalizeProductGroupType(groupType?: string | null) {
return (groupType ?? '').trim()
}
export function isMeltedTradeGroup(groupType?: string | null) {
const group = normalizeProductGroupType(groupType)
return group === PRODUCT_CATEGORY_ABSHODE || group === LEGACY_GROUP_KATOD
}
export type MeltedPriceSource = 'app' | 'fixed' | 'reference'
export function resolveMeltedPriceSource(product?: {
reading_method?: string | null
direct_price?: number | null
} | null): MeltedPriceSource {
if (product?.reading_method) {
return 'reference'
}
if (Number(product?.direct_price) === 1) {
return 'fixed'
}
return 'app'
}
export function buildCategorySelectItems(metalItems: Array<{ key: number; value: string }>) {
const metalHint = metalItems.map((item) => item.value).join('، ')
return UNIFIED_PRODUCT_CATEGORIES.map((category) => ({
key: category,
value: category === PRODUCT_CATEGORY_ABSHODE ? `${category} (${metalHint})` : category,
}))
}
+15
View File
@@ -0,0 +1,15 @@
export const TRADE_WEIGHT_UNIT_GRAM = 'گرم'
export const TRADE_WEIGHT_UNIT_KILOGRAM = 'کیلوگرم'
export function isKilogramUnit(unit?: string | null) {
const value = (unit ?? '').trim()
return value === TRADE_WEIGHT_UNIT_KILOGRAM || value === 'kilogram' || value === 'kg'
}
export function resolveTradeWeightUnit(settingValue?: string | number | null) {
return isKilogramUnit(settingValue?.toString()) ? TRADE_WEIGHT_UNIT_KILOGRAM : TRADE_WEIGHT_UNIT_GRAM
}
export function weightFieldLabelForUnit(unit?: string | null) {
return isKilogramUnit(unit) ? 'وزن به کیلوگرم' : 'وزن به گرم'
}