fix some bugs

This commit is contained in:
2026-07-21 22:26:46 +03:30
parent 7c2248bb00
commit 2f64c6cce7
24 changed files with 707 additions and 16 deletions
+1
View File
@@ -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']
+18
View File
@@ -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}`)
+11
View File
@@ -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
View File
@@ -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>
+23
View File
@@ -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 }
}
+2
View File
@@ -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
+3
View File
@@ -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: 'موجودی',
+3 -1
View File
@@ -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',
+11
View File
@@ -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',