fix some bugs
This commit is contained in:
Vendored
+1
@@ -20,6 +20,7 @@ declare module '@vue/runtime-core' {
|
||||
ACollapse: typeof import('ant-design-vue/es')['Collapse']
|
||||
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
AdminFieldHelp: typeof import('./src/components/AdminFieldHelp.vue')['default']
|
||||
AdminHelpTooltipContent: typeof import('./src/components/AdminHelpTooltipContent.vue')['default']
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import useAxios from '@core/utils/useAxios'
|
||||
import type { ConditionalOrder } from '@/model/conditionalOrders'
|
||||
|
||||
export const getConditionalOrdersTable = async (params: TableInput) =>
|
||||
await useAxios.post<Array<ConditionalOrder>>('/admin/conditional_orders/table', params)
|
||||
|
||||
export const createConditionalOrder = async (payload: Partial<ConditionalOrder>) =>
|
||||
await useAxios.post<ConditionalOrder>('/admin/conditional_orders', payload)
|
||||
|
||||
export const updateConditionalOrder = async (id: number, payload: Partial<ConditionalOrder>) =>
|
||||
await useAxios.put(`/admin/conditional_orders/${id}`, payload)
|
||||
|
||||
export const toggleConditionalOrderActive = async (id: number) =>
|
||||
await useAxios.put<{ is_active: boolean }>(`/admin/conditional_orders/${id}/toggle`)
|
||||
|
||||
export const deleteConditionalOrder = async (id: number) =>
|
||||
await useAxios.delete(`/admin/conditional_orders/${id}`)
|
||||
@@ -25,6 +25,17 @@ export const changeCustomerActivation = async (id: number) =>
|
||||
export const changeCustomerOrderActivation = async (id: number) =>
|
||||
await useAxios.get(`/admin/customer/trade/${id}`)
|
||||
|
||||
export const changeCustomerConditionalOrderActive = async (id: number) =>
|
||||
await useAxios.get(`/admin/customer/conditional_order/${id}`)
|
||||
|
||||
export const updateCustomerConditionalOrderLimit = async (
|
||||
id: number,
|
||||
conditional_order_limit: number | null
|
||||
) =>
|
||||
await useAxios.put(`/admin/customer/conditional_order_limit/${id}`, {
|
||||
conditional_order_limit,
|
||||
})
|
||||
|
||||
export const changeCustomerArchive = async (id: number) =>
|
||||
await useAxios.get(`/admin/customer/archive/${id}`)
|
||||
|
||||
|
||||
+7
-1
@@ -39,7 +39,13 @@ export interface PargarSyncWalletResult {
|
||||
ok: boolean
|
||||
message: string
|
||||
result?: {
|
||||
documents: { applied: number; skipped: number; errors: string[]; logs: number[] }
|
||||
documents: {
|
||||
applied: number
|
||||
skipped: number
|
||||
batches?: number
|
||||
acknowledged?: number
|
||||
errors: string[]
|
||||
}
|
||||
mande: { applied: boolean; gold_delta?: number; rial_delta?: number; message: string }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
<article class="customer-wallet-balance__summary-card customer-wallet-balance__group--cash">
|
||||
<span class="customer-wallet-balance__summary-label">ریالی</span>
|
||||
<span class="customer-wallet-balance__summary-value">{{ usePrice(summary.total_rial_value ?? 0, true, '') }}</span>
|
||||
<span class="customer-wallet-balance__summary-value">{{ usePrice(summary.cash_wallet ?? 0, true, '') }}</span>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface ConditionalOrder {
|
||||
id: number
|
||||
customer_id: number
|
||||
product_id: number
|
||||
direction: 1 | 2
|
||||
price_condition: 'gte' | 'lte'
|
||||
trigger_price: number
|
||||
amount?: number | null
|
||||
number?: number | null
|
||||
expires_at?: string | null
|
||||
description?: string | null
|
||||
is_active: boolean
|
||||
status: 'pending' | 'executed' | 'failed' | 'expired'
|
||||
fail_reason?: string | null
|
||||
order_id?: number | null
|
||||
fee_charged?: number
|
||||
triggered_price?: number | null
|
||||
executed_at?: string | null
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
customer?: { id: number; name?: string; mobile?: string }
|
||||
product?: { id: number; title?: string }
|
||||
}
|
||||
@@ -34,6 +34,8 @@ export interface Customer {
|
||||
profit_amount: number
|
||||
add_user_limit: number
|
||||
trade_active: boolean
|
||||
conditional_order_active?: boolean
|
||||
conditional_order_limit?: number | null
|
||||
type?: number
|
||||
type_str: string
|
||||
gold: number
|
||||
|
||||
@@ -17,6 +17,9 @@ export interface Transaction {
|
||||
metal_gold?: Record<string, number>
|
||||
transaction_metal_id?: number
|
||||
transaction_metal_label?: string
|
||||
product_id?: number | null
|
||||
product_title?: string | null
|
||||
product?: { id?: number; title?: string | null; group_type?: string | null; metal_id?: number | null } | null
|
||||
order_id: number
|
||||
transaction_type: string
|
||||
type: number
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<a-form :model="form" ref="formRef" layout="vertical" autocomplete="off">
|
||||
<a-form-item label="مشتری" name="customer_id" :rules="inputRules()">
|
||||
<BasicSelect
|
||||
v-model:value="form.customer_id"
|
||||
:items="customerItems"
|
||||
key-name="id"
|
||||
value-name="label"
|
||||
placeholder="انتخاب مشتری"
|
||||
:disabled="Boolean(editId)"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="محصول" name="product_id" :rules="inputRules()">
|
||||
<BasicSelect
|
||||
v-model:value="form.product_id"
|
||||
:items="productItems"
|
||||
key-name="id"
|
||||
value-name="label"
|
||||
placeholder="انتخاب محصول"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="جهت معامله" name="direction" :rules="inputRules()">
|
||||
<a-radio-group v-model:value="form.direction" button-style="solid">
|
||||
<a-radio-button :value="1">خرید</a-radio-button>
|
||||
<a-radio-button :value="2">فروش</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="شرط اجرا" name="price_condition" :rules="inputRules()">
|
||||
<a-radio-group v-model:value="form.price_condition">
|
||||
<a-radio value="gte">وقتی مظنه به این عدد یا بالاتر رسید</a-radio>
|
||||
<a-radio value="lte">وقتی مظنه به این عدد یا پایینتر رسید</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="مظنه تریگر" name="trigger_price" :rules="inputRulesNumber()">
|
||||
<BasicInput
|
||||
v-model:value="form.trigger_price"
|
||||
type="price"
|
||||
addon-before="ریال"
|
||||
placeholder="مثلاً ۸۰۲۰۰۰۰۰۰"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="مقدار (گرم یا تعداد)" name="amount" :rules="inputRulesNumber()">
|
||||
<BasicInput v-model:value="form.amount" type="number" placeholder="مقدار" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="اعتبار تا" name="expires_at">
|
||||
<a-date-picker
|
||||
v-model:value="expiresAtModel"
|
||||
show-time
|
||||
class="w-full"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="تاریخ و ساعت انقضا"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="توضیحات" name="description">
|
||||
<a-textarea v-model:value="form.description" :rows="3" placeholder="اختیاری" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="وضعیت" name="is_active">
|
||||
<a-switch v-model:checked="form.is_active" checked-children="فعال" un-checked-children="غیرفعال" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import type { ConditionalOrder } from '@/model/conditionalOrders'
|
||||
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
|
||||
const props = defineProps<{
|
||||
form: Partial<ConditionalOrder>
|
||||
customerItems: Array<{ id: number; label: string }>
|
||||
productItems: Array<{ id: number; label: string }>
|
||||
editId?: number
|
||||
}>()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const expiresAtModel = computed({
|
||||
get: () => props.form.expires_at ?? undefined,
|
||||
set: (v: string | undefined) => {
|
||||
props.form.expires_at = v || null
|
||||
},
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validateFields: () => formRef.value?.validateFields(),
|
||||
resetFields: () => formRef.value?.resetFields(),
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicShowContext title="سفارش شرطی">
|
||||
<template #extra>
|
||||
<div class="flex items-center gap-2">
|
||||
<BaseButton type="primary" size="small" @click="emits('add')">
|
||||
<span>افزودن تریگر</span>
|
||||
<BaseIcon icon="vuesax-linear:add" size="16" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>ویرایش</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit"
|
||||
color="#4572E9"
|
||||
class="cursor-pointer"
|
||||
@click="emits('edit', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||
<Icon
|
||||
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
||||
:color="record.is_active ? '#C53030' : '#25855A'"
|
||||
class="cursor-pointer"
|
||||
@click="emits('toggle', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>حذف</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:trash"
|
||||
color="#C53030"
|
||||
class="cursor-pointer"
|
||||
@click="emits('delete', record.id)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</BaseTable>
|
||||
</BasicShowContext>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx" setup>
|
||||
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/conditionalOrdersColumns'
|
||||
import { getConditionalOrdersTable } from '@/api/conditionalOrders'
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
const emits = defineEmits<{
|
||||
add: []
|
||||
edit: [record: Record<string, unknown>]
|
||||
toggle: [record: Record<string, unknown>]
|
||||
delete: [id: number]
|
||||
}>()
|
||||
|
||||
const tblProps = {
|
||||
columns: cols,
|
||||
api: (params: TableInput) => getConditionalOrdersTable(params),
|
||||
}
|
||||
|
||||
function fetch() {
|
||||
reload()
|
||||
}
|
||||
|
||||
defineExpose({ fetch })
|
||||
</script>
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
|
||||
export const cols: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
customRender: (record: any) => record.index + 1,
|
||||
},
|
||||
{
|
||||
title: 'مشتری',
|
||||
dataIndex: 'customer',
|
||||
key: 'customer',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => {
|
||||
const name = record.customer?.name?.trim()
|
||||
return name ? `${name} (${record.customer_id})` : String(record.customer_id ?? '-')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'محصول',
|
||||
dataIndex: 'product',
|
||||
key: 'product',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => record.product?.title || record.product_id || '-',
|
||||
},
|
||||
{
|
||||
title: 'جهت',
|
||||
dataIndex: 'direction',
|
||||
key: 'direction',
|
||||
customRender: ({ record }) =>
|
||||
Number(record.direction) === 1 ? (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
خرید
|
||||
</CustomTag>
|
||||
) : (
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
فروش
|
||||
</CustomTag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'شرط',
|
||||
dataIndex: 'price_condition',
|
||||
key: 'price_condition',
|
||||
customRender: ({ record }) =>
|
||||
record.price_condition === 'gte' ? (
|
||||
<CustomTag>یا بالاتر</CustomTag>
|
||||
) : (
|
||||
<CustomTag>یا پایینتر</CustomTag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'مظنه تریگر',
|
||||
dataIndex: 'trigger_price',
|
||||
key: 'trigger_price',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
title: 'مقدار',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
customRender: ({ record }) => {
|
||||
if (record.number != null && Number(record.number) > 0) return `${record.number} عدد`
|
||||
if (record.amount != null && Number(record.amount) > 0) return `${record.amount} گرم`
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'وضعیت اجرا',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
customRender: ({ record }) => {
|
||||
const map: Record<string, { label: string; text: string; bg: string }> = {
|
||||
pending: { label: 'در انتظار', text: '#1E40AF', bg: '#DBEAFE' },
|
||||
executed: { label: 'انجام شد', text: '#25855A', bg: '#E3F4E9' },
|
||||
failed: { label: 'ناموفق', text: '#C53030', bg: '#FECACA' },
|
||||
expired: { label: 'منقضی', text: '#6B7280', bg: '#F3F4F6' },
|
||||
}
|
||||
const s = map[record.status] || map.pending
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<CustomTag textColor={s.text} bgColor={s.bg} class={'font-bold px-4'}>
|
||||
{s.label}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'اعتبار تا',
|
||||
dataIndex: 'expires_at',
|
||||
key: 'expires_at',
|
||||
customRender: ({ record }) => (record.expires_at ? toJalali(record.expires_at) : '—'),
|
||||
},
|
||||
{
|
||||
title: 'فعال',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
type: 'Boolean',
|
||||
customRender: ({ record }) =>
|
||||
record.is_active ? (
|
||||
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
|
||||
فعال
|
||||
</CustomTag>
|
||||
) : (
|
||||
<CustomTag textColor="#C53030" bgColor="#FECACA">
|
||||
غیرفعال
|
||||
</CustomTag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'row_action',
|
||||
key: 'row_action',
|
||||
slot: 'action',
|
||||
fixed: 'right',
|
||||
filterable: false,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div class="flex h-full w-full">
|
||||
<tbl
|
||||
ref="tableRef"
|
||||
class="h-full w-full"
|
||||
@add="openAdd"
|
||||
@edit="openEdit"
|
||||
@toggle="toggleActive"
|
||||
@delete="deleteItem"
|
||||
/>
|
||||
|
||||
<BasicActionSheet
|
||||
v-model:visible="editorVisible"
|
||||
:title="sheetTitle"
|
||||
modal-width="36rem"
|
||||
drawer-height="90vh"
|
||||
:loading="loading"
|
||||
:show-button="true"
|
||||
:show-cancel="true"
|
||||
:show-ok="true"
|
||||
ok-text="ذخیره"
|
||||
cancel-text="انصراف"
|
||||
@ok="submitForm"
|
||||
@close="closeEditor"
|
||||
@cancel="closeEditor"
|
||||
>
|
||||
<div v-if="isMobile" class="mb-3 text-base font-bold">{{ sheetTitle }}</div>
|
||||
<ConditionalOrderEditorFormBody
|
||||
ref="editorRef"
|
||||
:form="form"
|
||||
:customer-items="customerItems"
|
||||
:product-items="productItems"
|
||||
:edit-id="editId"
|
||||
/>
|
||||
</BasicActionSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import tbl from './components/table.vue'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import ConditionalOrderEditorFormBody from './components/ConditionalOrderEditorFormBody.vue'
|
||||
import type { ConditionalOrder } from '@/model/conditionalOrders'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { getCustomersList } from '@/api/customer'
|
||||
import { getCustomersList as getProductsList } from '@/api/product'
|
||||
import {
|
||||
createConditionalOrder,
|
||||
deleteConditionalOrder,
|
||||
toggleConditionalOrderActive,
|
||||
updateConditionalOrder,
|
||||
} from '@/api/conditionalOrders'
|
||||
import { confirmDeleteItem } from '@/utils/confirmModal'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
|
||||
const tableRef = ref<null | { fetch: () => void }>(null)
|
||||
const editorRef = ref<{
|
||||
validateFields?: () => Promise<unknown>
|
||||
resetFields?: () => void
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
const editorVisible = ref(false)
|
||||
const editId = ref<number | undefined>()
|
||||
const customers = ref<Customer[]>([])
|
||||
const products = ref<Array<{ id: number; title?: string }>>([])
|
||||
|
||||
const defaultForm = (): Partial<ConditionalOrder> => ({
|
||||
customer_id: undefined,
|
||||
product_id: undefined,
|
||||
direction: 1,
|
||||
price_condition: 'lte',
|
||||
trigger_price: undefined,
|
||||
amount: undefined,
|
||||
expires_at: null,
|
||||
description: '',
|
||||
is_active: true,
|
||||
})
|
||||
|
||||
const form = ref<Partial<ConditionalOrder>>(defaultForm())
|
||||
|
||||
const sheetTitle = computed(() =>
|
||||
editId.value ? 'ویرایش سفارش شرطی' : 'افزودن سفارش شرطی'
|
||||
)
|
||||
|
||||
const customerItems = computed(() =>
|
||||
customers.value.map((customer) => ({
|
||||
id: customer.id,
|
||||
label: `${customer.name || 'بدون نام'} — ${customer.mobile || customer.id}`,
|
||||
}))
|
||||
)
|
||||
|
||||
const productItems = computed(() =>
|
||||
products.value.map((p) => ({
|
||||
id: p.id,
|
||||
label: p.title || String(p.id),
|
||||
}))
|
||||
)
|
||||
|
||||
function refreshTable() {
|
||||
tableRef.value?.fetch()
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
editorVisible.value = false
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
editId.value = undefined
|
||||
form.value = defaultForm()
|
||||
editorRef.value?.resetFields?.()
|
||||
editorVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(record: ConditionalOrder) {
|
||||
editId.value = record.id
|
||||
form.value = cloneDeep({
|
||||
customer_id: record.customer_id,
|
||||
product_id: record.product_id,
|
||||
direction: record.direction,
|
||||
price_condition: record.price_condition,
|
||||
trigger_price: record.trigger_price,
|
||||
amount: record.amount ?? record.number ?? undefined,
|
||||
expires_at: record.expires_at,
|
||||
description: record.description,
|
||||
is_active: record.is_active,
|
||||
})
|
||||
editorVisible.value = true
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
try {
|
||||
await editorRef.value?.validateFields?.()
|
||||
loading.value = true
|
||||
|
||||
const payload: Partial<ConditionalOrder> = {
|
||||
customer_id: Number(form.value.customer_id),
|
||||
product_id: Number(form.value.product_id),
|
||||
direction: Number(form.value.direction) as 1 | 2,
|
||||
price_condition: form.value.price_condition,
|
||||
trigger_price: Number(form.value.trigger_price),
|
||||
amount: Number(form.value.amount),
|
||||
expires_at: form.value.expires_at || null,
|
||||
description: form.value.description || null,
|
||||
is_active: Boolean(form.value.is_active),
|
||||
}
|
||||
|
||||
if (editId.value) {
|
||||
await updateConditionalOrder(editId.value, payload)
|
||||
} else {
|
||||
await createConditionalOrder(payload)
|
||||
}
|
||||
|
||||
message.success('سفارش شرطی ذخیره شد')
|
||||
editorVisible.value = false
|
||||
refreshTable()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleActive(record: ConditionalOrder) {
|
||||
if (!record.id) return
|
||||
try {
|
||||
loading.value = true
|
||||
await toggleConditionalOrderActive(record.id)
|
||||
message.success(record.is_active ? 'غیرفعال شد' : 'فعال شد')
|
||||
refreshTable()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
message.error('خطا در تغییر وضعیت')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function deleteItem(id: number) {
|
||||
confirmDeleteItem('سفارش شرطی', id, deleteConditionalOrder, refreshTable)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
customers.value = (await getCustomersList()) ?? []
|
||||
products.value = ((await getProductsList()) as Array<{ id: number; title?: string }>) ?? []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -119,7 +119,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|permision__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|customer_direct_sms|has_page_earn_money|sms_panel|main_wallet_products)/.test(
|
||||
/^(name_fa|name_en|app_url|admin_url|apk_download_link|permision__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|customer_direct_sms|has_page_earn_money|sms_panel|main_wallet_products|enable_conditional_order)/.test(
|
||||
k
|
||||
)
|
||||
},
|
||||
|
||||
@@ -236,6 +236,8 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
||||
buy_fee: 'price',
|
||||
sell_fee: 'price',
|
||||
fee_free: 'number',
|
||||
fee_conditional_order: 'price',
|
||||
enable_conditional_order: 'switch',
|
||||
min_withdraw: 'price',
|
||||
min_deposit: 'price',
|
||||
min_first_buy: 'price',
|
||||
@@ -555,6 +557,10 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
'روشن: اگر مانده طلای کیف اصلی صفر یا زیر صفر باشد (بدهی)، مشتری ابتدا باید آبشده/مانده کیف اصلی را صفر کند و بعد میتواند محصول دیگر بخرد. خاموش: بدون این محدودیت. معامله خودِ محصولات کیف اصلی همیشه مجاز است.',
|
||||
fee_free:
|
||||
'تعداد اولین سفارشهای هر کاربر که از کارمزد معاف میشوند. صفر یعنی غیرفعال.',
|
||||
fee_conditional_order:
|
||||
'مبلغ ریالی که با هر بار ثبت تریگر سفارش شرطی از کیف پول ریالی مشتری کم میشود. صفر = رایگان.',
|
||||
enable_conditional_order:
|
||||
'روشن: قابلیت سفارش شرطی سراسری فعال است. خاموش: منو و ارزیابی اجرا نمیشود. فقط از صفحه دیپلوی قابل تغییر است.',
|
||||
min_withdraw:
|
||||
'حداقل مبلغ درخواست برداشت (ریال). درخواستهای کمتر پذیرفته نمیشوند.',
|
||||
min_deposit:
|
||||
@@ -783,6 +789,8 @@ export const KEY_LABEL_FALLBACK: Record<string, string> = {
|
||||
fee_deduct_from_gold: 'اعمال مبلغ دقیق در معامله مشتری',
|
||||
require_zero_main_gold_before_other_buy: 'الزام صفر بودن کیف اصلی قبل از خرید محصول دیگر',
|
||||
fee_free: 'معافیت کارمزد سفارش اول',
|
||||
fee_conditional_order: 'کارمزد سفارش شرطی',
|
||||
enable_conditional_order: 'سفارش شرطی',
|
||||
min_withdraw: 'حداقل مبلغ برداشت',
|
||||
min_deposit: 'حداقل مبلغ واریز کیف پول',
|
||||
min_first_buy: 'حداقل اولین خرید',
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
<div class="pargar-wallet-sync__copy min-w-0 flex-1">
|
||||
<p class="pargar-wallet-sync__title">همگامسازی با پرگار</p>
|
||||
<p class="pargar-wallet-sync__hint">
|
||||
{{
|
||||
canSync
|
||||
? 'اول اسنادِ اعمالنشده، بعد همترازی مانده — خودکار از پرگار کیف عوض نمیشود'
|
||||
: 'ابتدا کد حسابداری پرگار را برای این مشتری ثبت کنید'
|
||||
}}
|
||||
{{
|
||||
canSync
|
||||
? 'موجودی را با ماندهٔ پرگار یکی میکند — تاریخچهٔ اسناد روی کیف replay نمیشود'
|
||||
: 'ابتدا کد حسابداری پرگار را برای این مشتری ثبت کنید'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,10 +21,10 @@
|
||||
size="small"
|
||||
class="pargar-wallet-sync__btn"
|
||||
:loading="loading"
|
||||
:disabled="!canSync"
|
||||
:disabled="!canSync || loading"
|
||||
@click="$emit('sync')"
|
||||
>
|
||||
همگامسازی مانده و اسناد
|
||||
همگامسازی مانده از پرگار
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -113,6 +113,47 @@
|
||||
:color="record.trade_active === 1 ? '#54AD06' : '#ad0627'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
:key="`co-${record.conditional_order_active}-${record.id}`"
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="changeConditionalOrderActiveFunction(record.id)"
|
||||
>
|
||||
<template #title>
|
||||
<span>
|
||||
{{
|
||||
record.conditional_order_active
|
||||
? 'محدود کردن سفارش شرطی'
|
||||
: 'مجاز کردن سفارش شرطی'
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<Icon
|
||||
:icon="
|
||||
record.conditional_order_active
|
||||
? 'vuesax-linear:timer'
|
||||
: 'vuesax-linear:timer-pause'
|
||||
"
|
||||
:color="record.conditional_order_active ? '#54AD06' : '#ad0627'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
@click="openConditionalOrderLimit(record)"
|
||||
>
|
||||
<template #title>
|
||||
<span>
|
||||
سقف سفارش شرطی
|
||||
{{
|
||||
record.conditional_order_limit
|
||||
? `(${record.conditional_order_limit})`
|
||||
: '(نامحدود)'
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:slider-horizontal" color="#4572E9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="customerDirectSmsEnabled"
|
||||
placement="bottom"
|
||||
@@ -211,6 +252,8 @@ import {
|
||||
changeCustomerActivation,
|
||||
changeCustomerArchive,
|
||||
changeCustomerOrderActivation,
|
||||
changeCustomerConditionalOrderActive,
|
||||
updateCustomerConditionalOrderLimit,
|
||||
confirmUser,
|
||||
getCustomerTable
|
||||
} from '@/api/customer'
|
||||
@@ -384,6 +427,49 @@ async function changeCustomerOrderActivationFunction(id: number) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function changeConditionalOrderActiveFunction(id: number) {
|
||||
try {
|
||||
loading.value = true
|
||||
await changeCustomerConditionalOrderActive(id)
|
||||
await fetch()
|
||||
message.success('مجوز سفارش شرطی بهروز شد')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openConditionalOrderLimit(customer: Customer) {
|
||||
const current = customer.conditional_order_limit
|
||||
Modal.confirm({
|
||||
title: 'سقف تعداد سفارش شرطی',
|
||||
content: `برای «${customer.name || customer.id}» عدد سقف را وارد کنید (خالی = نامحدود). فعلی: ${
|
||||
current ? current : 'نامحدود'
|
||||
}`,
|
||||
okText: 'ذخیره',
|
||||
cancelText: 'انصراف',
|
||||
centered: true,
|
||||
zIndex: MODAL_ABOVE_ACTION_SHEET_Z_INDEX,
|
||||
onOk: async () => {
|
||||
const raw = window.prompt(
|
||||
'سقف تعداد تریگر pending (خالی = نامحدود):',
|
||||
current != null ? String(current) : ''
|
||||
)
|
||||
if (raw === null) return
|
||||
const trimmed = raw.trim()
|
||||
const limit = trimmed === '' ? null : Number(trimmed)
|
||||
if (limit !== null && (!Number.isFinite(limit) || limit < 1)) {
|
||||
message.error('عدد نامعتبر است')
|
||||
return Promise.reject()
|
||||
}
|
||||
await updateCustomerConditionalOrderLimit(customer.id, limit)
|
||||
await fetch()
|
||||
message.success('سقف سفارش شرطی ذخیره شد')
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
@@ -44,7 +44,8 @@ export const cols: BaseTableColumn[] = [
|
||||
dataIndex: 'product.title',
|
||||
key: 'product.title',
|
||||
type: 'Text',
|
||||
customRender: (record: any) => record.record?.product?.title ?? '-'
|
||||
customRender: (record: any) =>
|
||||
record.record?.product_title || record.record?.product?.title || '-'
|
||||
},
|
||||
{
|
||||
title: 'موجودی',
|
||||
|
||||
@@ -596,12 +596,13 @@ async function refreshWalletPanels() {
|
||||
}
|
||||
|
||||
async function syncPargarWallet() {
|
||||
if (!userInfo.value?.id || !pargarWalletCanSync.value) return
|
||||
if (!userInfo.value?.id || !pargarWalletCanSync.value || pargarSyncLoading.value) return
|
||||
pargarSyncLoading.value = true
|
||||
try {
|
||||
const res = await syncCustomerWalletFromPargar(userInfo.value.id)
|
||||
const applied =
|
||||
res.ok ||
|
||||
(res.result?.documents?.acknowledged ?? 0) > 0 ||
|
||||
(res.result?.documents?.applied ?? 0) > 0 ||
|
||||
Boolean(res.result?.mande?.applied)
|
||||
|
||||
@@ -620,6 +621,7 @@ async function syncPargarWallet() {
|
||||
const status = (e as { response?: { status?: number } })?.response?.status
|
||||
const applied =
|
||||
Boolean(data?.ok) ||
|
||||
(data?.result?.documents?.acknowledged ?? 0) > 0 ||
|
||||
(data?.result?.documents?.applied ?? 0) > 0 ||
|
||||
Boolean(data?.result?.mande?.applied)
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
{{ directionLabel }}
|
||||
</span>
|
||||
<span
|
||||
v-if="item.order_metal_label"
|
||||
v-if="item.product_title || 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 }}
|
||||
{{ item.product_title || item.order_metal_label }}
|
||||
</span>
|
||||
<span class="truncate text-sm font-700 text-gray-900">{{ customerName }}</span>
|
||||
</div>
|
||||
|
||||
@@ -62,6 +62,17 @@ export function buildOrderColumns(_metals: OrderMetalColumn[] = []): BaseTableCo
|
||||
key: 'customer.mobile',
|
||||
type: 'Number'
|
||||
},
|
||||
{
|
||||
title: 'محصول',
|
||||
dataIndex: 'product_title',
|
||||
key: 'product_title',
|
||||
type: 'Text',
|
||||
filterable: false,
|
||||
customRender({ record }) {
|
||||
const title = (record.product_title || record.product?.title || '').trim()
|
||||
return title || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'نوع فلز',
|
||||
dataIndex: 'order_metal_label',
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
</div>
|
||||
|
||||
<MobileInfoGrid>
|
||||
<MobileDetailRow
|
||||
label="محصول"
|
||||
:value="isMoneyType(row) ? '-' : (row.product_title || row.product?.title || '-')"
|
||||
/>
|
||||
<MobileDetailRow label="نوع فلز" :value="isMoneyType(row) ? '-' : (row.transaction_metal_label ?? '-')" />
|
||||
<MobileDetailRow label="کد تراکنش" :value="row.id ?? '-'" />
|
||||
<MobileDetailRow label="نام تراکنش" :value="row.type_str || '-'" />
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
{{ typeLabel }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!isMoneyType && item.transaction_metal_label"
|
||||
v-if="!isMoneyType && (item.product_title || 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 }}
|
||||
{{ item.product_title || item.transaction_metal_label }}
|
||||
</span>
|
||||
<span class="truncate text-sm font-700 text-gray-900">{{ item.type_str || '-' }}</span>
|
||||
</div>
|
||||
|
||||
@@ -101,6 +101,21 @@ export function buildTransactionsColumns(metals: TransactionMetalColumn[] = []):
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'محصول',
|
||||
dataIndex: 'product_title',
|
||||
key: 'product_title',
|
||||
type: 'Text',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: ({ record }) => {
|
||||
if (isMoneyTransaction(record)) {
|
||||
return '-'
|
||||
}
|
||||
const title = (record.product_title || record.product?.title || '').trim()
|
||||
return title || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'نام مشتری',
|
||||
dataIndex: 'customer.name',
|
||||
|
||||
@@ -89,6 +89,17 @@ const HesabKetab: RouteRecordRaw = {
|
||||
order: 6
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'conditionalOrders',
|
||||
path: 'conditionalOrders',
|
||||
component: () => import('@/pages/menuPages/conditionalOrders/index.vue'),
|
||||
meta: {
|
||||
permissionsKey: 'priceTriggers',
|
||||
icon: 'vuesax-linear:timer',
|
||||
title: 'سفارش شرطی',
|
||||
order: 6.5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'accountingMain',
|
||||
path: 'accounting',
|
||||
|
||||
Reference in New Issue
Block a user